如何手寫一個bind方法

bind方法簡介

關于bind() 方法的介紹,可以參照這里;

bind() 方法創(chuàng)建一個新的函數(shù)获三,在 bind() 被調用時勇劣,這個新函數(shù)的 this 被指定為 bind() 的第一個參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調用時使用屎慢。

語法

function.bind(thisArg[, arg1[, arg2[, ...]]])

參數(shù)

  • thisArg

調用綁定函數(shù)時作為this參數(shù)傳遞給目標函數(shù)的值亚享。

如果使用new運算符構造綁定函數(shù)咽块,則忽略該值。當使用bindsetTimeout中創(chuàng)建一個函數(shù)(作為回調提供)時欺税,作為thisArg傳遞的任何原始值都將轉換為object侈沪。如果bind函數(shù)的參數(shù)列表為空,或者thisArgnullundefined魄衅,執(zhí)行作用域的this將被視為新函數(shù)的 thisArg峭竣。

  • arg1, arg2, ...

當目標函數(shù)被調用時,被預置入綁定函數(shù)的參數(shù)列表中的參數(shù)晃虫。

返回值

返回一個原函數(shù)的拷貝皆撩,并擁有指定的 this 值和初始參數(shù)。

實現(xiàn)bind方法1.0

  1. 首先哲银,我們定義一個函數(shù)扛吞,這個函數(shù)的第一個參數(shù)作為thisArg,后面的參數(shù)作為返回的新方法的參數(shù):
Function.prototype.mybind = function () {
  let args = Array.from(arguments);
  let thisArg = args.shift(); // 一箭雙雕的寫法
}

也可以利用剩余參數(shù)

Function.prototype.mybind = function (thisArg, ...args) {
  // ...
}

剩余參數(shù)是ES6的新特性荆责,一般說來滥比,不支持bind的情況一般也不支持剩余參數(shù),所以做院,不推薦這種寫法盲泛。

  1. 然后,mybind方法會返回一個新函數(shù)键耕,該函數(shù)將外層函數(shù)的參數(shù)與內層函數(shù)的參數(shù)連接起來一起作為參數(shù):
Function.prototype.mybind = function () {
  let args = Array.from(arguments);
  let thisArg = args.shift();
  
  return function () {
    newArgs = args.concat(Array.from(arguments));
    // ...
  }
}
  1. 我們可以使用apply來完成this指向變更寺滚,在那之前可以使用變量thisFunc先保存原函數(shù):
Function.prototype.mybind = function () {
  let args = Array.from(arguments);
  let thisArg = args.shift();
  let thisFunc = this;
  
  return function () {
    newArgs = args.concat(Array.from(arguments));
    return thisFunc.apply(thisArg, newArgs);
  }
}

在調用apply必須先檢測thisFunc是不是Function,這里沒寫是因為我懶屈雄。

低版本瀏覽器在不支持bind的情況下會支持apply嗎村视?還真會。

支持apply的最低瀏覽器版本均低于支持bind的最低瀏覽器版本

當然酒奶,你也可以手動實現(xiàn)apply:

Function.prototype.mybind = function () {
  let args = Array.from(arguments);
  // 手動實現(xiàn)需要在第一個參數(shù)為null時
  let thisArg = args.shift() || window;
  let thisFunc = this;
  
  return function () {
    newArgs = args.concat(Array.from(arguments));
    let fn = Symbol('thisFunc');
    thisArg[fn] = _Func;
    let res = thisArg[fn](...newArgs);
    delete thisArg[fn];
    return res;
  }
}

數(shù)組解構也是個ES6的語法蚁孔,感覺又把自己繞進去了。

這樣惋嚎,一個類似于bind的方法就寫好了杠氢,下面我們調用一下它。

使用mybind 1.0

  • 情景1:普通函數(shù)
var age = 18;

let myfn = function (a, b, c) {
  console.log(this.age, a, b, c);
}

myfn();  // 18 undefined undefined undefined

let xiaohuang = {
  age: 12
}

let myfn1 = myfn.mybind(xiaohuang, 1);

myfn1(3); // 12 1 3 undefined

let myfn2 = myfn.bind({ age: 114514 });

myfn2(19, 19, 810);  // 114514 19 19 810

myfn2(19, 19, 810);  // 114514 19 19 810

沒有問題另伍,一切正常修然。

  • 情景2: 構造函數(shù)
let Animal = function (name) {
  this.name = name;
}

let buly = {
  name: 'buly'
}

let Cat = Animal.mybind(buly);

let tom = new Cat('tom');

console.log(tom, buly); // {} {name: "tom"}
// expected output: {name: "tom"} {name: "buly"}

哦吼,出問題了。

實現(xiàn)bind方法2.0

看來我們根據具體的情況采取不同的策略愕宋,當傳入的函數(shù)是一個構造函數(shù)時玻靡,我們不需要更改this的指向。

如何判斷是否為構造函數(shù)呢中贝?只需要判斷this instanceof 構造方法的值就可以了囤捻。

Function.prototype.mybind = function () {
  let args = Array.from(arguments);
  let thisArg = args.shift();
  let thisFunc = this;
  
  // 因為需要構造函數(shù),所以不能是匿名函數(shù)了
  let fBound = function () {
    newArgs = args.concat(Array.from(arguments));
    // 判斷是否為構造函數(shù)
    thisArg = this instanceof fBound ? this : thisArg;
    return thisFunc.apply(thisArg, newArgs);
  }
  
  return fBound;
}

使用bind2.0

  • 情景2: 構造函數(shù)
let Animal = function (name) {
  this.name = name;
}

let buly = {
  name: 'buly'
}

let Cat = Animal.mybind(buly);

let tom = new Cat('tom');

console.log(tom, buly); // {name: "tom"} {name: "buly"}

— 我很高興你完成了手寫bind的全部內容邻寿。
— 不好意思蝎土,你高興的太早了。

  • 情景3: 帶原型對象(prototype绣否,下同)的構造函數(shù)
let Animal = function (name) {
  this.name = name;
}

// 箭頭函數(shù)中的this會穿透作用域誊涯,所以不要用箭頭函數(shù)哦
Animal.prototype.say  = function() {
  console.log('hello, my name is ' + this.name);
}

let buly = {
  name: 'buly'
}

let Cat = Animal.mybind(buly);

let tom = new Cat('tom');

tom.say(); // Error: tom.say is not a function

新返回的函數(shù)與原函數(shù)的原型對象并沒有建立聯(lián)系,所以new出來的對象不能訪問到原函數(shù)的原型對象上的方法蒜撮。

實現(xiàn)bind方法3.0

讓我們簡單粗暴一點:

    Function.prototype.mybind = function () {
      let args = Array.from(arguments);
      let thisArg = args.shift();
      let thisFunc = this;

      // 因為需要構造函數(shù)暴构,所以不能是匿名函數(shù)了
      let fBound = function () {
        newArgs = args.concat(Array.from(arguments));
        // 判斷是否為構造函數(shù)
        thisArg = this instanceof fBound ? this : thisArg;
        return thisFunc.apply(thisArg, newArgs);
      }

      // 直接將原函數(shù)的prototype賦值給綁定函數(shù) 
      fBound.prototype = this.prototype;

      return fBound;
    }

當然,我們不推薦這種粗暴的繼承方式段磨,這種情況下取逾,若更改新函數(shù)的原型對象,則原函數(shù)的原型對象也會被改變苹支。

  • 推薦方法1: 原型式繼承
Function.prototype.mybind = function () {
  let args = Array.from(arguments);
  let thisArg = args.shift();
  let thisFunc = this;
  // 中間函數(shù)
  let fNop = function () { };
  
  // 因為需要構造函數(shù)砾隅,所以不能是匿名函數(shù)了
  let fBound = function () {
    newArgs = args.concat(Array.from(arguments));
    // 判斷是否為構造函數(shù)
    thisArg = this instanceof fBound ? this : thisArg;
    return thisFunc.apply(thisArg, newArgs);
  }
  
  fNop.prototype = this.prototype;
  // 原型式繼承
  fBound.prototype = new fNop();
  
  return fBound;
}

你可能不太能理解這段東西,我試著簡單的解釋一下:

如果我們采用ES6中的class的話债蜜,是這樣的:

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Cat extends Animal {
  constructor(name) {
    super(name);
  }
}

let tom = new Cat('tom');

這里晴埂,tom__proto__指向Cat的原型對象,tom__proto____proto__指向Animal的原型對象

但ES5只支持構造函數(shù)寻定,不支持class儒洛,所以們先創(chuàng)建一個空函數(shù)fNop,然后使其原型對象指向原函數(shù)的原型對象特姐。在使得fBound的原型對象指向fNop的實例,這也變相實現(xiàn)了fBound extends fNop黍氮。

以上純屬個人理解唐含,僅供參考。

  • 推薦方法2: Object.create

方便書寫又方便理解沫浆,其實就是淺拷貝捷枯。

Function.prototype.mybind = function () {
  let args = Array.from(arguments);
  let thisArg = args.shift();
  let thisFunc = this;
  
  // 因為需要構造函數(shù),所以不能是匿名函數(shù)了
  let fBound = function () {
    newArgs = args.concat(Array.from(arguments));
    // 判斷是否為構造函數(shù)
    thisArg = this instanceof fBound ? this : thisArg;
    return thisFunc.apply(thisArg, newArgs);
  }
  
  // Object.create拷貝原型對象
  fBound.prototype = Object.create(this.prototype);
  
  return fBound;
}

使用bind3.0

  • 情景3: 帶原型對象(prototype专执,下同)的構造函數(shù)
let Animal = function (name) {
  this.name = name;
}

// 箭頭函數(shù)中的this會穿透作用域淮捆,所以不要用箭頭函數(shù)哦
Animal.prototype.say  = function() {
  console.log('hello, my name is ' + this.name);
}

let buly = {
  name: 'buly'
}

let Cat = Animal.mybind(buly);

let tom = new Cat('tom');

tom.say(); // hello, my name is tom

和真正的bind不同,使用我們手動實現(xiàn)的bind,最后實例化的對象的構造函數(shù)是fBound而不是原構造函數(shù)案站。

總結

實現(xiàn)bind的技術要點如下:

  1. 將剩余的參數(shù)和傳入的參數(shù)拼接,作為新的參數(shù)石挂。知識點有:解構賦值Array.prototype.concat里伯、Array.prototype.apply

  2. 判斷是否為構造函數(shù),使用instanceof操作符肩碟;

  3. 實現(xiàn)原型繼承,參考JS中的繼承與原型鏈

其他的技術要點吨拍,可以參考我之前的文章《如何手寫一個call方法》

MDN提供的polyFill代碼

polyfill:補丁。意思是在瀏覽器不支持bind時燥撞,采用該代碼以解決此問題:

全部代碼如下:

//  Yes, it does work with `new (funcA.bind(thisArg, args))`
if (!Function.prototype.bind) (function(){
  var ArrayPrototypeSlice = Array.prototype.slice;
  Function.prototype.bind = function(otherThis) {
    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 baseArgs= ArrayPrototypeSlice.call(arguments, 1),
        baseArgsLength = baseArgs.length,
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          baseArgs.length = baseArgsLength; // reset to default base arguments
          baseArgs.push.apply(baseArgs, arguments);
          return fToBind.apply(
                 fNOP.prototype.isPrototypeOf(this) ? this : otherThis, baseArgs
          );
        };

    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();

    return fBound;
  };
})();

這里提出2條解讀:

  1. 最外層是匿名函數(shù)自調用冠胯,是防止浪費存儲資源的做法。
  2. baseArgs.length = baseArgsLength;這一步的原因有二。一是閉包導致了所有函數(shù)共用一個baseArgs;二是因為push是一種會修改原數(shù)組的API,所以不可避免的會改動baseArgs。使用concat或者數(shù)組解構可以更好地解決此問題沃于。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蒋困,一起剝皮案震驚了整個濱河市溉跃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖僻弹,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粘咖,死亡現(xiàn)場離奇詭異忠聚,居然都是意外死亡,警方通過查閱死者的電腦和手機唱捣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門两蟀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人震缭,你說我怎么就攤上這事赂毯。” “怎么了拣宰?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵党涕,是天一觀的道長。 經常有香客問我巡社,道長膛堤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任晌该,我火速辦了婚禮肥荔,結果婚禮上绿渣,老公的妹妹穿的比我還像新娘。我一直安慰自己燕耿,他們只是感情好中符,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著誉帅,像睡著了一般淀散。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蚜锨,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天档插,我揣著相機與錄音,去河邊找鬼亚再。 笑死阀捅,一個胖子當著我的面吹牛,可吹牛的內容都是我干的针余。 我是一名探鬼主播饲鄙,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼圆雁!你這毒婦竟也來了忍级?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤伪朽,失蹤者是張志新(化名)和其女友劉穎轴咱,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烈涮,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡朴肺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坚洽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戈稿。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖讶舰,靈堂內的尸體忽然破棺而出鞍盗,到底是詐尸還是另有隱情,我是刑警寧澤跳昼,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布般甲,位于F島的核電站,受9級特大地震影響鹅颊,放射性物質發(fā)生泄漏敷存。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一堪伍、第九天 我趴在偏房一處隱蔽的房頂上張望锚烦。 院中可真熱鬧滔岳,春花似錦、人聲如沸挽牢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽禽拔。三九已至,卻和暖如春室叉,著一層夾襖步出監(jiān)牢的瞬間睹栖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工茧痕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留野来,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓踪旷,卻偏偏與公主長得像曼氛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子令野,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容

  • call方法簡介 關于call() 方法的介紹舀患,可以參照這里[https://developer.mozilla....
    之幸甘木閱讀 3,920評論 0 5
  • 最近的面試中被問到了 bind 這個方法,并寫出其 Polyfill气破,感覺回答的不太好聊浅,在此重新整理一下。Func...
    Crazy_Urus閱讀 1,341評論 1 1
  • 本文將從以下十一個維度為讀者總結前端基礎知識 JS基礎 如何在ES5環(huán)境下實現(xiàn)let對于這個問題现使,我們可以直接查看...
    WEB前端含光閱讀 1,043評論 1 16
  • 博客園整理了一下低匙,有好的面試題歡迎大家發(fā)在評論區(qū)喲1. 閉包2. 數(shù)組去重3. 原型和原型鏈4. call,ap...
    Daeeman閱讀 1,712評論 4 42
  • bind()方法創(chuàng)建一個新的函數(shù), 當被調用時,將其this關鍵字設置為提供的值碳锈,在調用新函數(shù)時顽冶,在任何提供之前提...
    騎騎小飛豬閱讀 1,458評論 0 0