bind的那些事

最近面頭條的時候,要求本身手動實現一個bind函數,而後又問了bind還能幹嗎。這裏就圍繞這兩個好好寫一下。面試

首先bind的文檔說明: (連接:傳送門數組

bind()方法建立一個新的函數, 當被調用時,將其this關鍵字設置爲提供的值,在調用新函數時,在任何提供以前提供一個給定的參數序列。
fun.bind(thisArg[, arg1[, arg2[, ...]]])
參數app

  • thisArg
    當綁定函數被調用時,該參數會做爲原函數運行時的 this 指向。當使用new 操做符調用綁定函數時,該參數無效。
  • arg1, arg2, ...
    當綁定函數被調用時,這些參數將置於實參以前傳遞給被綁定的方法。
  • 返回值
    返回由指定的this值和初始化參數改造的原函數拷貝

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個,甚至更多。

  • 柯里化:把一個有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;
    }

這裏的點:

  1. 第一個this是第一次調用bind的函數,也必須是函數,因此在以前就作了一個容錯判斷。

  2. 若是最後咱們是以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的這些事到此結束,歡迎在下方交流評論。

相關文章
相關標籤/搜索