js 實現 bind 的這五層,你在第幾層?

js 實現 bind 的這五層,你在第幾層?

最近在幫朋友複習 JS 相關的基礎知識,遇到不會的問題,她就會來問我。前端

image-20210511013512798

這不是很簡單?三下五除二,分分鐘解決。git

function bind(fn, obj, ...arr) {
    return fn.apply(obj, arr)
}

因而我就將這段代碼發了過去github

image-20210511013602940

這時候立馬被女友進行了一連串的靈魂拷問。面試

image-20210511013907433

這個時候,我馬老師就坐不住了,我不服氣,我就去複習了一下 bind,發現過久不寫基礎代碼,仍是會須要一點時間複習,這一次我得寫一個有深度的 bind,深的馬老師的真傳,給他分紅了五層速記法。segmentfault

dasima

第一層 - 綁定在原型上的方法

這一層很是的簡單,得益於 JS 原型鏈的特性。因爲 function xxx 的原型鏈 指向的是 Function.prototype , 所以咱們在調用 xxx.bind 的時候,調用的是 Function.prototype 上的方法。閉包

Function.prototype._bind = function() {}

這樣,咱們就能夠在一個構造函數上直接調用咱們的bind方法啦~例如像這樣。app

funciton myfun(){}
myfun._bind();

想要詳細理解這方面的能夠看這張圖和這篇文章(https://github.com/mqyqingfen...函數

js-prototype

第二層 - 改變 this 的指向

這能夠說是 bind 最核心的特性了,就是改變 this 的指向,而且返回一個函數。而改變 this , 咱們能夠經過已知的 apply 和 call 來實現,這裏咱們就暫且使用 apply 來進行模擬。首先經過 self 來保存當前 this,也就是傳入的函數。由於咱們知道 this 具備 隱式綁定的規則(摘自 《你不知道的JavaScript(上)》2.2.2 ),this

function foo() {console.log(this.a)}
var obj = {a: 2, foo};
obj.foo(); // 2

經過以上特性,咱們就能夠來寫咱們的 _bind 函數。spa

Function.prototype._bind = function(thisObj) {
    const self = this;
    return function () {
    self.apply(thisObj);
  }
}
var obj = {a:1}
function myname() {console.log(this.a)}
myname._bind(obj)(); // 1

可能不少朋友都止步於此了,由於在通常的面試中,特別是一些校招面試中,可能你只須要知道前面兩個就差很少了。可是想要在面試中驚豔全部人,仍然是不夠的,接下來咱們繼續咱們的探索與研究。

第三層 - 支持柯里化

函數柯里化是一個老生常談的話題,在這裏再複習一下。

function fn(x) {
    return function (y) {
        return x + y;
    }
}
var fn1 = fn(1);
fn1(2) // 3

不難發現,柯里化使用了閉包,當咱們執行 fn1 的時候,函數內使用了外層函數的 x, 從而造成了閉包。

而咱們的 bind 函數也是相似,咱們經過獲取當前外部函數的 arguments ,而且去除了綁定的對象,保存成變量 args,最後 return 的方法,再一次獲取當前函數的 arguments, 最終用 finalArgs 進行了一次合併。

Function.prototype._bind = function(thisObj) {
    const self = this;
  const args = [...arguments].slice(1)
    return function () {
    const finalArgs = [...args, ...arguments]
    self.apply(thisObj, finalArgs);
  }
}

經過以上代碼,讓咱們 bind 方法,愈來愈健壯了。

var obj = { i: 1}
function myFun(a, b, c) {
  console.log(this.i + a + b + c);
}
var myFun1 = myFun._bind(obj, 1, 2);
myFun1(3); // 7

通常到了這層,能夠說很是棒了,可是再堅持一下下,就變成了完美的答卷。

第四層 - 考慮 new 的調用

要知道,咱們的方法,經過 bind 綁定以後,依然是能夠經過 new 來進行實例化的, new 的優先級會高於 bind摘自 《你不知道的JavaScript(上)》2.3 優先級)。

這一點咱們經過原生 bind 和咱們第四層的 _bind 來進行驗證對比。

// 原生
var obj = { i: 1}
function myFun(a, b, c) {
  // 此處用new方法,this指向的是當前函數 myFun 
  console.log(this.i + a + b + c);
}
var myFun1 = myFun.bind(obj, 1, 2);
new myFun1(3); // NAN

// 第四層的 bind
var obj = { i: 1}
function myFun(a, b, c) {
  console.log(this.i + a + b + c);
}
var myFun1 = myFun._bind(obj, 1, 2);
new myFun1(3); // 7

注意,這裏使用的是 bind方法

所以咱們須要在 bind 內部,對 new 的進行處理。而 new.target 屬性,正好是用來檢測構造方法是不是經過 new 運算符來被調用的。

接下來咱們還須要本身實現一個 new ,

而根據 MDNnew 關鍵字會進行以下的操做:

1.建立一個空的簡單JavaScript對象(即{});

2.連接該對象(設置該對象的constructor)到另外一個對象 ;

3.將步驟1新建立的對象做爲this的上下文 ;

4.若是該函數沒有返回對象,則返回this

Function.prototype._bind = function(thisObj) {
    const self = this;
  const args = [...arguments].slice(1);
    return function () {
    const finalArgs = [...args, ...arguments];
        // new.target 用來檢測是不是被 new 調用
    if(new.target !== undefined) {
      // this 指向的爲構造函數自己
      var result = self.apply(this, finalArgs);
      // 判斷改函數是否返回對象
      if(result instanceof Object) {
        return reuslt;
      }
      // 沒有返回對象就返回 this
      return this;
    } else {
      // 若是不是 new 就原來的邏輯
      return self.apply(thisArg, finalArgs);
    }
  }
}

看到這裏,你的造詣已經如火純情了,可是最後還有一個小細節。

第五層 - 保留函數原型

以上的方法在大部分的場景下都沒有什麼問題了,可是,當咱們的構造函數有 prototype 屬性的時候,就出問題啦。所以咱們須要給 prototype 補上,還有就是調用對象必須爲函數。

Function.prototype._bind = function (thisObj) {
  // 判斷是否爲函數調用
  if (typeof target !== 'function' || Object.prototype.toString.call(target) !== '[object Function]') {
    throw new TypeError(this + ' must be a function');
  }
  const self = this;
  const args = [...arguments].slice(1);
  var bound = function () {
    var finalArgs = [...args, ...arguments];
    // new.target 用來檢測是不是被 new 調用
    if (new.target !== undefined) {
      // 說明是用new來調用的
      var result = self.apply(this, finalArgs);
      if (result instanceof Object) {
        return result;
      }
      return this;
    } else {
      return self.apply(thisArg, finalArgs);
    }
  };
  if (self.prototype) {
    // 爲何使用了 Object.create? 由於咱們要防止,bound.prototype 的修改而致使self.prototype 被修改。不要寫成 bound.prototype = self.prototype; 這樣可能會致使原函數的原型被修改。
    bound.prototype = Object.create(self.prototype);
    bound.prototype.constructor = self;
  }
  return bound;
};

以上就是一個比較完整的 bind 實現了,若是你想了解更多細節的實踐,能夠查看。(也是 MDN 推薦的)

https://github.com/Raynos/fun...

本次探索雖然比較簡單,可是仔細深刻探究仍是有一些注意事項,前端老鳥也可能回答不全。

結語

❤️關注+點贊+收藏+評論+轉發❤️ ,原創不易,鼓勵筆者創做更好的文章

關注公衆號秋風的筆記,一個專一於前端面試、工程化、開源的前端公衆號

相關文章
相關標籤/搜索