bind的那些事
最近面頭條的時候,要求自己手動實現一個bind函數,然后又問了bind還能干嘛。這里就圍繞這兩個好好寫一下。
首先bind的文檔說明: (鏈接:傳送門 )
bind()方法創建一個新的函數, 當被調用時,將其this關鍵字設置為提供的值,在調用新函數時,在任何提供之前提供一個給定的參數序列。
fun.bind(thisArg[, arg1[, arg2[, ...]]])
參數
- thisArg
當綁定函數被調用時,該參數會作為原函數運行時的 this 指向。當使用new 操作符調用綁定函數時,該參數無效。 - arg1, arg2, ...
當綁定函數被調用時,這些參數將置于實參之前傳遞給被綁定的方法。 - 返回值
返回由指定的this值和初始化參數改造的原函數拷貝
bind() 函數會創建(返回)一個新函數(稱為綁定函數),新函數與被調函數(綁定函數的目標函數)具有相同的函數體(在 ECMAScript 5 規范中內置的call屬性)。當新函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。(注意這句話是重點,也就是說bind支持了這個功能)
一般新手分不清于call,apply的關系,call/apply一般會伴隨這函數的調用的,而bind只是返回了帶新this的函數,將在下次調用。一般用法是調用bind后賦給一個變量,支持調用的時候繼續傳參。
我們一般快速實現一個bind:
/*函數體內的this,就是需要綁定this的實例函數,或者說是原函數。最后我們使用apply來進行參數(context)綁定,并返回。 同時,將第一個參數(context)以外的其他參數,作為提供給原函數的預設參數,這也是基本的柯里化基礎。(應該是偏函數)*/ Function.prototype.bind = Function.prototype.bind || function(context) {var self = this;arg = Array.prototype.slice.call(arguments,1);return function() {return self.apply(context,arr);} }但是這個實現有個問題,我們將參數限定了arguments.slice(1),我們返回的綁定函數中,如果想實現預設傳參,上個代碼就不能滿足了。
eg:
那么我們將上個bind的實現更完善一下:
Function.prototype.bind = Function.prototype.bind || function(context) {var self = this;var args = Array.prototype.slice.call(arguments,1);return function() {var innerArgs = Array.prototype.slice.call(arguments);var FinalArgs = args.concat(innerArgs);return self.apply(context,FinalArgs);} }這樣就是實現了偏函數功能,在一些資料里是說“柯里化”,我覺得還是有點區別的,共同特點是實現了參數復用。
關于柯里化和偏函數舉個小例子就知道了:
假設有一個Add(x,y,z)函數,接收x,y,z三個參數,返回x+y+z
- 偏函數
AddBySeven =Otherbind(Add, 7);
AddBySeven(5, 10); // returns 22;
這是偏函數,固定了你函數的某一個或幾個參數,返回一個新的函數,接收剩下的參數, 參數個數可能是1個,也可能是2個,甚至更多。
- 柯里化:把一個有n個參數的函數變成n個只有1個參數的函數
curryAdd = Curry(Add);
AddBySeven = curryAdd(7);
AddBySeven(5)(10); // returns 22
// curryAdd(7)(5)(10)
Add = (x, y, z) => x + y + z
變成了CurryAdd = x => y => z => x + y + z
很多資料有的叫柯里化有的叫偏函數,這點我覺得還是讀者自己判斷把。
到這里可能大家覺得上面的實現已經完美了,但是JS的坑是補不完的,問題又來了!
看過文檔的就知道,在文檔上介紹bind時還說了這點:
一個綁定函數也能使用new操作符創建對象:這種行為就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。
那么如果bind返回的函數當做構造函數調用的時候,我們就需要在內部重新構造原型鏈了。所以更兼容的寫法來了:
Function.prototype.bind = Function.prototype.bind || function(context) {if(typeof this !== 'function') {throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');}var self = this;var args = Array.prototype.slice.call(arguments,1);var Fun = {};Fun.prototype = this.prototype;//繼承原來函數var Comb = function(){var innerArgs = Array.prototype.slice.call(arguments);var FinalArgs = args.concat(innerArgs);return self.apply(this instanceof Fun ? this : context || this ,FinalArgs);}Comb.prototype = new Fun();return Comb;}這里的點:
第一個this是第一次調用bind的函數,也必須是函數,所以在之前就做了一個容錯判斷。
如果最后我們是以new 調用bind返回的函數,即當做構造函數調用,那么這里的this就是Comb的實例,這時候因為Fun繼承了之前調用的函數,Comb又new了Fun,Comb即是Fun的派生類,因此 this instanceof fNOP === true,這時候無視 bind 效果,因此 this 該是什么還是什么。模仿了原本bind的feature,如果這個條件判斷失敗,則使用context 去替換 this。如果context沒傳,那么this該是什么就是什么。
但這里再想想面試官當時的的問題,"bind還能干嘛",其實這個bind就是改變上下文還能干嘛,其實面試管的意思是利用bind的這個feature可以干嘛
把bind本身的作用講講,在把上面bind本身的偏函數功能(允許第一次傳參不完全,后面調用可以繼續傳參),自己實現的偏函數,作為new 構造函數來調用這些講講,就夠了。
最后再加一個bind的小功能把,平常我們轉換偽數組,通常是使用:
var slice = Array.prototype.slice;// ...slice.call(arguments);//arguments是一個偽數組如果我們用bind把對象參數綁定到call上返回給slice,每次就不用調用call了,而且還不影響原函數的this:
var combSlice = Array.prototype.slice; var slice = Function.prototype.call.bind(combSlice);// ...slice(arguments);//slice是一個新函數好了,bind的這些事到此結束,歡迎在下方交流評論。
轉載于:https://www.cnblogs.com/zhangmingzhao/p/8660985.html
總結
- 上一篇: [CF396E]On Iteration
- 下一篇: 《王者荣耀》技术总监复盘回炉历程:没跨过