JavaScript深入之bind的模擬實現

StanleyCoch 7年前發布 | 22K 次閱讀 JavaScript開發 JavaScript

bind

一句話介紹bind:

bind()方法會創建一個新函數。當這個新函數被調用時,bind()的第一個參數將作為它運行時的 this, 之后的一序列參數將會在傳遞的實參前傳入作為它的參數。(來自于MDN)

由此我們可以首先得出bind函數的兩個特點:

  1. 返回一個函數

  2. 可以傳入參數

返回函數的模擬實現

從第一個特點開始,我們舉個例子:

var foo = {
    value: 1
};

function bar() { console.log(this.value); }

// 返回了一個函數 var bindFoo = bar.bind(foo);

bindFoo(); // 1</code></pre>

關于指定this的指向,我們可以使用call或者apply實現,關于call和apply的模擬實現,可以查看《JavaScript深入之call和apply的模擬實現》,底部有相關鏈接。我們來寫第一版的代碼:

// 第一版
Function.prototype.bind2 = function (context) {
    var self = this;
    return function () {
        self.apply(context);
    }

}</code></pre>

傳參的模擬實現

接下來看第二點,可以傳入參數。這個就有點讓人費解了,我在bind的時候,可以傳參,我執行bind返回的函數的時候,可不可以傳參呢?讓我們看個例子:

var foo = {
    value: 1
};

function bar(name, age) { console.log(this.value); console.log(name); console.log(age);

}

var bindFoo = bar.bind(foo, 'daisy'); bindFoo('18'); // 1 // daisy // 18</code></pre>

函數需要傳name和age兩個參數,竟然還可以在bind的時候,只傳一個name,在執行返回的函數的時候,再傳另一個參數age!

這可咋辦,不急,我們用arguments對象進行處理:

// 第二版
Function.prototype.bind2 = function (context) {

var self = this;
// 獲取bind2函數從第二個參數到最后一個參數
var args = Array.prototype.slice.call(arguments, 1);

return function () {
    // 這個時候的arguments是指bind返回的函數傳入的參數
    var bindArgs = Array.prototype.slice.call(arguments);
    self.apply(context, args.concat(bindArgs));
}

}</code></pre>

構造函數效果的模擬實現

完成了這兩點,最難的部分到啦!因為bind還有一個特點,就是

一個綁定函數也能使用new操作符創建對象:這種行為就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。

也就是說當bind返回的函數作為構造函數的時候,bind時指定的this值會失效,但傳入的參數依然生效。舉個例子:

var value = 2;

var foo = { value: 1 };

function bar(name, age) { this.hobbit = 'shopping'; console.log(this.value); console.log(name); console.log(age); }

bar.prototype.friend = 'kevin';

var bindFoo = bar.bind(foo, 'daisy');

var obj = new bindFoo('18'); // undefined // daisy // 18 console.log(obj.hobbit); console.log(obj.friend); // shopping // kevin</code></pre>

注意:盡管在全局和foo中都聲明了value值,最后依然返回了undefind,說明綁定的this失效了,如果大家了解new的模擬實現,就會知道這個時候的this已經指向了obj。

(哈哈,我這是為我的下一篇文章《JavaScript深入系列之new的模擬實現》打廣告)。

所以我們可以通過修改返回的函數的原型來實現,讓我們寫一下:

// 第三版
Function.prototype.bind2 = function (context) {

var self = this;
var args = Array.prototype.slice.call(arguments, 1);

var fbound = function () {

    var bindArgs = Array.prototype.slice.call(arguments);
    // 當通過構造函數new出一個實例的時候,會執行this instanceof self的判斷,此時結果為true,將this指向構造的實例。
    self.apply(this instanceof self ? this : context, args.concat(bindArgs));
    // 如果想不明白為什么new的時候,會執行instanceof的判斷以及為什么this instanceof self的結果會為true,就只能看下一篇文章《JavaScript深入系列之new的模擬實現》,雖然明天才發布……
}
// 修改返回函數的prototype為函數的prototype,new的實例就可以繼承函數的原型中的值
fbound.prototype = this.prototype;
return fbound;

}</code></pre>

如果對原型鏈稍有困惑,可以查看《JavaScript深入之從原型到原型鏈》。

構造函數效果的優化實現

但是在這個寫法中,我們直接將fbound.prototype = this.prototype,我們直接修改fbound.prototype的時候,也會直接修改函數的prototype。這個時候,我們可以通過一個空函數來進行中轉:

// 第四版
Function.prototype.bind2 = function (context) {

var self = this;
var args = Array.prototype.slice.call(arguments, 1);

var fNOP = function () {};

var fbound = function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    self.apply(this instanceof self ? this : context, args.concat(bindArgs));
}
fNOP.prototype = this.prototype;
fbound.prototype = new fNOP();
return fbound;

}</code></pre>

到此為止,大的問題都已經解決,給自己一個贊!o( ̄▽ ̄)d

三個小問題

接下來處理些小問題:

1.apply這段代碼跟MDN上的稍有不同

在MDN中文版講bind的模擬實現時,apply這里的代碼是:

self.apply(this instanceof self ? this : context || this, args.concat(bindArgs))

多了一個關于context是否存在的判斷,然而這個是錯誤的!

舉個例子:

var value = 2;
var foo = {
    value: 1,
    bar: bar.bind(null)
};

function bar() { console.log(this.value); }

foo.bar() // 2</code></pre>

以上代碼正常情況下會打印2,如果換成了context || this,這段代碼就會打印1!

所以這里不應該進行context的判斷,大家查看MDN同樣內容的英文版,就不存在這個判斷!

2.調用bind的不是函數咋辦?

不行,我們要報錯!

if (typeof this !== "function") {
  throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

3.我要在線上用

那別忘了做個兼容:

Function.prototype.bind = Function.prototype.bind || function () {
    ……
};

當然最好是用 es5-shim 啦。

所以最最后的代碼就是:

Function.prototype.bind2 = function (context) {

if (typeof this !== "function") {
  throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fNOP = function () {};

var fbound = function () {
    self.apply(this instanceof self ? this : context, args.concat(Array.prototype.slice.call(arguments)));
}

fNOP.prototype = this.prototype;
fbound.prototype = new fNOP();

return fbound;

}</code></pre>

 

 

 

來自:https://segmentfault.com/a/1190000009271416

 

 本文由用戶 StanleyCoch 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!