最近面頭條的時候,要求本身手動實現一個bind函數,而後又問了bind還能幹嗎。這裏就圍繞這兩個好好寫一下。面試
首先bind的文檔說明: (連接:傳送門 )數組
bind()方法建立一個新的函數, 當被調用時,將其this關鍵字設置爲提供的值,在調用新函數時,在任何提供以前提供一個給定的參數序列。
fun.bind(thisArg[, arg1[, arg2[, ...]]])
參數app
bind() 函數會建立(返回)一個新函數(稱爲綁定函數),新函數與被調函數(綁定函數的目標函數)具備相同的函數體(在 ECMAScript 5 規範中內置的call屬性)。當新函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。(注意這句話是重點,也就是說bind支持了這個功能)函數
通常新手分不清於call,apply的關係,call/apply通常會伴隨這函數的調用的,而bind只是返回了帶新this的函數,將在下次調用。通常用法是調用bind後賦給一個變量,支持調用的時候繼續傳參。this
咱們通常快速實現一個bind:prototype
/* 函數體內的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:code
function sum(a, b) { console.log(a + b) } var sum2 = sum.bind(null,2); // 固定參數a, 值爲2 sum2(4) // 傳入參數b, 值爲4, 結果爲6 重在咱們在調用函數的時候能夠預設傳參
那麼咱們將上個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+zip
AddBySeven =Otherbind(Add, 7);
AddBySeven(5, 10); // returns 22;
這是偏函數,固定了你函數的某一個或幾個參數,返回一個新的函數,接收剩下的參數, 參數個數多是1個,也多是2個,甚至更多。
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的這些事到此結束,歡迎在下方交流評論。