1. 程式人生 > >原生JS實現bind()函數

原生JS實現bind()函數

利用 all turn bin end 選擇 數組 his http

一、bind()函數的兩個特性:

1、bind和curring,函數科裏化

function add(a, b, c) {
    var i = a+b+c;
    console.log(i);
    return i;
}

var func = add.bind(undefined, 100);//給add()傳了第一個參數a
func(1, 2);//103,繼續傳入b和c

var func2 = func.bind(undefined, 200);//給func2傳入第一個參數,也就是b,此前func已有參數a=100
func2(10);//310,繼續傳入c,100+200+10

  可以利用此種特性方便代碼重用,如下,可以不同的頁面中只需要配置某幾項,前面幾項固定的配置可以選擇用bind函數先綁定好

,講一個復雜的函數拆分成簡單的子函數。

技術分享圖片

2、bind和new

function foo() {
    this.b = 100;
    console.log(this.a);
    return this.a;
}

var func =  foo.bind({a:1});
func();//1
new func();//undefined   {b:100},可以看到此時上面的bind並不起作用

  函數中的return除非返回的是個對象,否則通過new返回的是個this,指向一個空對象,空對象原型指向foo.prototype,空對象的b屬性是100。也就是說通過new的方式創建一個對象,bind()函數在this層面上並不起作用,但是需要註意在參數層面上仍起作用

,如下:

function foo(c) {
    this.b = 100;
    console.log(this.a);
    console.log(c);
    return this.a;
}

var func =  foo.bind({a:1},20);
new func();//undefined 20,通過new創建對象func,bind綁定的c依舊起作用

二、bind實現

  了解完以上兩個特性,再來看看bind()的實現:

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    
if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis || window, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }; }

技術分享圖片

  第3行:傳入oThis就是foo.bind({a:1})中傳入的對象{a:1};

  第4行:判斷調用此bind方法的對象是不是一個函數function,若不是則報錯;

  第10行:

  (1)因為函數自帶的arguments屬性並不是一個數組,只是一個類數組,不具有slice這些方法,所以用call方法給slice()指定this為arguments,讓arguments也可以實現slice()方法。
  (2)後面傳入參數1,是slice(start, end)中的一個參數start,表示從arguments的小標為1,即第二個參數開始切割。 這裏是將bind函數的參數數組取出來,第一個參數不要(就是不要oThis)也就是要被綁定方法的那個對象

  (3)arguments參數只有在函數調用執行的時候才存在,也就是當var func = foo.bind({a:1});的時候,調用了bind,此時aArgs是一個空數組。如果是var func = foo.bind({a:1}, 2),那麽aArgs = [2];

  第12行,13行,20行,21行:創建了一個空對象FNOP,並將這個空對象的原型指向foo的原型;

  然後又將func/fBound的原型指向一個新的FNOP實例,這個步驟完成了給func/fBound拷貝一個FNOP的prototype即this/foo的prototype。

  其實這幾句就相當於fBound.prototype = Object.create(this.prototype);

  第14行:

  (1)給 fBound/func return一個fToBind/foo對象;

  (2)這裏的this指的是調用func()時的執行環境;直接調用func()的時候,this指向的是全局對象,那麽結果是oThis/{a:1},這樣就可以讓這個fToBind的this指向這個傳進來的對象oThis;

  (3)bind()同時也會傳參數:aArgs.concat(Array.prototype.slice.call(arguments))

  【註意】這裏的arguments是調用此函數時的arguments,也就是func()的執行環境,和上面的arguments(bind的執行環境)不一樣,在此例中,此時的arguments是空數組,因為並沒有給func()傳參數。這段contact的意思就是把bind()中傳的參數和func()中傳的參數連起來,來實現上面提到的bind的科裏性。

  (4)如果通過new func()來調用,this會指向一個空對象,這個空對象的原型會指向構造器的prototype的屬性,也就是func/fBound的prototype屬性。此時this instanceof fNOP 為true,那麽返回的是this就是當前正常的this;相當於忽略掉bind的this的影響,實現了上述的bind特性二:bind和new。

  那麽想想為什麽要給func/fBound拷貝一個FNOP的prototype即this/foo的prototype?沒有實現這個會怎樣?

  我們知道bind()函數其實是實現了this的指定和參數的傳遞; 實際中的new func()其實相當於創建了func()一個新實例,使用的構造函數是func,它只為新對象定義了【默認的】屬性和方法。也就是Object.create(this.prototype)的作用,如果不把foo的prototype拷貝個func,那麽這裏的new func()就沒法得到foo默認的屬性。 如圖我把那兩行註釋掉後,za沒辦法獲取到this.b的值

技術分享圖片

原生JS實現bind()函數