JavaScript專題之模擬實現bind

南波發表於2018-10-31

本文共 1100 字,讀完只需 4 分鐘

概述

前一篇文章我們嘗試模擬實現了 call 和 apply 方法,其實 bind 函式也可以用來改變 this 的指向。bind 和 call和 apply 兩者的區別在於,bind 會返回一個被改變了 this 指向的函式。

本文介紹如何模擬實現 bind 函式:

首先觀察 bind 函式有什麼特點:

var person = {
    name: 'jayChou'
}

function say(age, sex) {
    console.log(this.name, age, sex);
}

var foo = say.bind(person, '男', 39);

foo();  // jayChou 男 39
複製程式碼
  1. 返回一個函式
  2. 函式引數以逗號的形式傳入
  3. 改變了 this 的指向

一、call 簡單實現

既然 bind 內部也要用改變 this 指向,我們可以用現成的 call 函式來實現這一功能。

Function.prototype.newBind = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
    var self = this;
    return function () {
        return self.call(context)
      }
}
複製程式碼

說明一下:
if 判斷是為了校驗,只能讓函式來呼叫此方法,並丟擲錯誤。 第二個 return 是為了讓返回的函式有返回值的功能。

測試一下:

var person = {
    name: 'jayChou'
};

var say = function() {
    console.log(this.name);
}

var foo = say.newBind(person);
foo();  // jayChou
複製程式碼

成功啦。

二、傳遞引數

剛才的函式是沒有傳遞引數,當然不行,所以我們想辦法把函式的引數也傳遞進去。

bind 函式有個特點,就是在繫結的時候可以傳參,返回的函式還可以繼續傳參。

var person = {
    name: 'jayChou'
};

var say = function(p1, p2) {
    console.log(this.name, p1, p2);
}

var foo = say.bind(person, 18);
foo(20);  // jayChou 18 20
複製程式碼

還可以寫成:

say.bind(person, 18)(20); // jayChou 18 20
複製程式碼

好,進入正題,考慮傳參的事。在前面的文章,我們講過 arguments 類陣列物件的一些特性,不能直接呼叫陣列的方法,但是可以用原型方法間接來呼叫,我們採用 apply 的方式來傳遞引數。

Function.prototype.newBind = 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);  // 間接呼叫陣列方法,獲取第一次傳的引數
    
    return function () {
        var innerArgs = Array.prototype.slice.call(arguments);
        return self.apply(context, args.concat(innerArgs))
      }
}


var person = {
    name: 'jayChou'
};

var say = function(p1, p2) {
    console.log(this.name, p1, p2);
}

var foo = say.newBind(person, 18);  // 第一次傳參
foo(20);  // 第二次傳參
複製程式碼

最後輸出:

jayChou 18 20

結果正確,以上就完成了 bind 函式基本功能的實現。

但是!

三、作為建構函式時?

bind 函式其實還有一個非常重要的特點:

bind返回的函式如果作為建構函式,搭配new關鍵字出現的話,我們的繫結this就需要“被忽略”。

意思就是指:當使用 nuw 關鍵字把 bind 返回的函式作為建構函式,之前改變了指向的 this 就失效了。返回的函式的 this 就關聯到了建構函式的例項物件上。

針對這個特點,需要再做一些修改:

Function.prototype.newBind = 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);  // 間接呼叫陣列方法,獲取第一次傳的引數
    
    let tempFn = function {};  // 利用一個空函式作為中轉
    
    tempFn.prototype = this.prototype;  // 修改返回函式的 prototype 為繫結函式的 prototype,例項就可以繼承繫結函式的原型中的值
    
    var resultFn = function () {
        var innerArgs = Array.prototype.slice.call(arguments);
        if (this instanceof tempFn) {  // 如果 返回函式被當做建構函式後,生成的物件是 tempFn 的例項,此時應該將 this 的指向指向建立的例項。
            return self.apply(this, args.concat(innerArgs));
        } else {
            return self.apply(context, args.concat(innerArgs))
        }
      }
      
    resultFn = new tempFn();
    return resultFn;
}
複製程式碼

總結

本文嘗試模擬實現了 bind 函式,bind 函式與 call,apply 函式的區別在於,bind 函式返回一個指定了 this 的函式,函式並未執行。其次,當返回的函式作為建構函式時,之前繫結的 this 會失效。

歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。

JavaScript專題之模擬實現bind

掘金專欄 JavaScript 系列文章

  1. JavaScript之變數及作用域
  2. JavaScript之宣告提升
  3. JavaScript之執行上下文
  4. JavaScript之變數物件
  5. JavaScript之原型與原型鏈
  6. JavaScript之作用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中徹底理解this
  12. JavaScript專題之模擬實現call和apply
  13. JavaScript專題之模擬實現bind

相關文章