js模擬實現(xiàn)bind

bind

原文詳見: JavaScript深入之bind的模擬實現(xiàn)
一句話介紹 bind (來自于 MDN ):

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

由此我們可以首先得出 bind 函數(shù)的兩個特點:
1.返回一個新函數(shù)
2.可以傳入?yún)?shù)

返回函數(shù)的模擬實現(xiàn)

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

var value = 2;
var foo = {
  value: 1
}
function bar() {
  console.log(this.value);
}
//返回了一個新函數(shù)
var bindFoo = bar.bind(foo);
bindFoo(); 
//1

關于指定 this 的指向,我們可以使用 call 或者 apply 實現(xiàn)。我們來寫第一版的代碼:

//第一版
Function.prototype.bind2 = function(context) {
    var self = this;
    return function() {
    //之所以 return self.apply(context),
    //是考慮到綁定函數(shù)可能是有返回值的  
      return self.apply(context);
    }
}
var value = 2;
var foo = {
  value: 1
}
function bar() {
  console.log(this.value);
}
var bindFoo = bar.bind2(foo);
bindFoo();
//1

傳參的模擬實現(xiàn)

接下來看第二點,可以傳入?yún)?shù)。這個就有點讓人費解了,我在 bind 的時候,是否可以傳參呢?我在執(zhí)行 bind 返回的函數(shù)的時候,可不可以傳參呢?讓我們看個例子:

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

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

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

//第二版
Function.prototype.bind2 = function(context) {
  var self = this;
  var args = Array.prototype.slice.call(arguments,1);

  //執(zhí)行bind返回的函數(shù)的時候才會走到執(zhí)行function
  return function() {
    //這時候的arguments是指向bind返回函數(shù)傳入的參數(shù)
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(context, args.concat(bindArgs));
  }
}

注意:Array.prototype.slice.call 改變this的指向指向arguments 并且slice能轉化arguments類數(shù)組為數(shù)組實現(xiàn)slice截取的作用,并返回一個新數(shù)組。還可以寫成[].slice.call(); 因為[]是Array的一個實例。

構造函數(shù)效果的模擬實現(xiàn)

bind 還有一個特點,就是

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

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

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = '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.habit);
console.log(obj.friend);
// shopping
// kevin

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

所以我們可以通過修改返回的函數(shù)的原型來實現(xiàn),讓我們寫一下:

//第三版
Function.prototype.bind2 = function(context) {
  var self= this;
  var args = [].slice.call(arguments,1);
  var fBood = function() {
     var newArgs = [].slice.call(arguments);
 // 當作為構造函數(shù)時,this 指向實例,此時結果為 true,將綁定函數(shù)的 this 指向該實例,
//可以讓實例獲得來自綁定函數(shù)的值
// 以上面的是 demo 為例,如果改成 `this instanceof fBound ? null : context`,
//實例只是一個空對象,將 null 改成 this ,實例會具有 habit 屬性
 // 當作為普通函數(shù)時,this 指向 window
//此時結果為 false,將綁定函數(shù)的 this 指向 context
     return self.apply(this instanceof fBood? this: context,args.concat(newArgs));
  }
  // 修改返回函數(shù)的 prototype 為綁定函數(shù)的 prototype,
//實例就可以繼承綁定函數(shù)的原型中的值
   fBood.prototype = this.prototype;
  return fBood;
}

注意1:this instanceof fBound 用來檢測 構造函數(shù)的prototype屬性是否出現(xiàn)在某個實例對象的原型鏈上。
如果你用new 來調用一個bind過的函數(shù),這時候this instanceof fBood === true ,這時候就無視bind的效果(this失效),因此this該是什么就是什么。

注意2:fBood.prototype = this.prototype 為了讓fBood構造函數(shù)實例能夠繼承綁定函數(shù)的原型中的值

構造函數(shù)效果的優(yōu)化實現(xiàn)

但是在這個寫法中,我們直接將 fBound.prototype = this.prototype,我們直接修改 fBound.prototype 的時候,也會直接修改綁定函數(shù)的 prototype。
舉個例子:

Function.prototype.bind2 = function(context) {
  var self = this;
  var args = Array.prototype.slice.call(arguments,1);
  var fBood = function() {
    var newArgs = [].slice.call(arguments);
    self.apply(this instanceof fBood? this: context, args.concat(newArgs));
  }
  fBood.prototype = this.prototype;
  return fBood;
}
function bar() {}
var bindFoo = bar.bind(null);
bindFoo.prototype.value = 1;
console.log(bar.prototype.value); //1

(你會發(fā)現(xiàn)我們明明修改的是 bindFoo.prototype ,但是 bar.prototype 的值也被修改了,這就是因為 fBound.prototype = this.prototype導致的。)

//第四版
Function.prototype.bind2 = function(context) {
  var self = this;
  var args = [].slice.call(arguments, 1);
  var fBood = function() {
    var newArgs = [].slice.call(arguments);
    self.apply(this instanceof fBood? this: context,args.concat(newArgs));
  }
  fNOP.peototype = this.prototype;
  fBood.prototype = new fNOP();
  return fBood;
}

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

小問題

調用 bind 的不是函數(shù)咋辦?

不行,我們要報錯!

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

我要在線上用
那別忘了做個兼容:

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

最終代碼

Function.prototype.bind2 = Function.prototype.bind|| function() {
  if(typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  }
  var self = this;
  var args = [].slice.call(arguments, 1);
  var fNOP = function () {};
  var fBood = function() {
    var newArgs = [].slice.call(arguments);
    self.apply(this instanceof fBood? this: context, args.concat(newArgs));
  }
  fNOP.prototype = this.prototype;
  fBood.prototype= new fNOP();
  return fBood;
}

加油~~~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 第5章 引用類型(返回首頁) 本章內容 使用對象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學一百閱讀 3,679評論 0 4
  • 本文首發(fā)我的個人博客:前端小密圈,評論交流送1024邀請碼,嘿嘿嘿??。 來自朋友去某信用卡管家的做的一道面試題,用...
    微醺歲月閱讀 3,357評論 4 32
  • title: js面向對象date: 2017年8月17日 18:58:05updated: 2017年8月27日...
    lu900618閱讀 692評論 0 2
  • 第一章 錯誤處理: 錯誤: 程序運行過程中,導致程序無法正常執(zhí)行的現(xiàn)象(即bug) 現(xiàn)象: 程序一旦出錯,默認會報...
    fastwe閱讀 1,252評論 0 1
  • 歷史節(jié)點負責加載歷史segment,使segment數(shù)據(jù)能夠被查詢。 Running io.druid.cli.M...
    PowerMe閱讀 1,189評論 0 1

友情鏈接更多精彩內容