如何模擬實現一個call、apply、bind函數

首先強烈推薦這位大佬的文章,寫的至關棒,後續討論內容的也是你來我往,分析的很透徹。
JavaScript深刻之call和apply的模擬實現
JavaScript深刻之bind的模擬實現前端

call, apply, bind這三個方法均可以改變函數內部this指向。區別是call, apply是當即指向該函數,而bind是返回一個新的函數,用於下次調用。git

其中,call和apply的區別是傳遞函數參數的方式不一樣,call是一個一個傳入,例如call(this, arg1, arg2, arg3)這樣。可是apply是經過一個數組傳遞,好比apply(this, [arg1, arg2, arg3])。github

call模擬實現

首先咱們實現綁定this功能。數組

// 好比咱們有一個foo函數
function getName() {
  return this.name;
}

// 還有一個wechat對象
const wechat = { name: 'fedaily' };

// 咱們但願實現
getName.call(wechat); // fedaily

那麼在call內部如何設計可以實現這個功能呢,咱們能夠聯想到將getName方法變成wechat對象裏的一個方法,而後經過wechat.getName(),是否是就能夠實現這個效果了。微信

咱們來實踐一下:app

Function.prototype.call = function (context) {
  context.fn = this;
  context.fn();
  delete context.fn;
}

wechatgetName這個爲例,這裏的this即getName,context即wechat。
咱們將getName賦值給wechat對象的fn熟悉,而後經過wechat對象調用,最後刪除這個fn屬性。(實際中咱們確定不能用fn這個名字,避免和對象本來重複,咱們能夠用Symbol實現)函數

而後咱們綁定this的功能就實現。還有傳遞參數,這個也好辦。this

Function.prototype.call = function () {
  const [ctx, ...args] = arguments;
  ctx.fn = this;
  ctx.fn(...args);
  delete ctx.fn;
}
// 這裏其實用了ES6的語法,可是主要爲了更好的說明整個實現過程,理解原理就好

這裏經過arguments對象去獲取傳進來的參數以及thisspa

而後考慮到函數多是有返回值的,因此ctx.fn()的執行結果也須要返回。同時this可能爲null,那麼咱們須要將this指向window。prototype

因此咱們再來調整一下:

Function.prototype.call = function () {
  const [ctx, ...args] = arguments;
  ctx.fn = this || window;
  const result = ctx.fn(...args);
  delete ctx.fn;
  return result;
}

這樣,咱們就模擬實現了一個call方法。

apply模擬實現

接上文,咱們再來實現一個apply就很容易了。咱們直接上代碼:

Function.prototype.apply = function () {
  const [ctx, args] = arguments; // args區別
  ctx.fn = this || window;
  const result = ctx.fn(...args);
  delete ctx.fn;
  return result;
}

bind模擬實現

bind會返回一個新函數,新函數執行時的this是bind方法的第一個參數。

根據這些特徵,咱們能夠想到使用apply幫助實現。仍是以上面的getName函數爲例。

Function.prototype.bind = function () {
  const [ctx, ...args] = arguments;
  const self = this; // 這裏的this即getName函數
  return function () {
    self.applay(ctx, args);
  }
}

這樣,綁定this的功能咱們就實現了。而後bind實際上是能夠傳遞參數的,bind返回的函數調用的時候也是能夠再傳遞參數的,同時調用bind的方法多是有返回值的,因此咱們處理一下

Function.prototype.bind = function () {
  const [ctx, ...args] = arguments;
  const self = this; // 這裏的this即getName函數
  return function () {
    // 這下面的arguments實際上是調用bind返回的函數,當這個函數調用時傳遞的參數
    // 同時返回函數執行結果
    return self.applay(ctx, args.concat(arguments));
  }
}

bind函數還有一個特性,就是bind返回的函數是能夠做爲構造函數的,當它做爲構造函數時,它以前綁定的this會被忽略。
爲了保證bind返回的函數可以繼承到調用函數的原型(即getName的原型)。因此咱們須要修改bind返回函數的原型爲this的原型(即getName的原型)。

咱們再來嘗試一下:

Function.prototype.bind = function () {
  const [ctx, args] = arguments;
  const self = this;
  const fBond = function () {
    // 注意這裏的this不是getName了,而是調用bind以後方法的this
    // const newGetName = getName.bind(this)
    // const n = new newGetName()
    // this就是n
    return self.apply(this instanceof fBond ? this : ctx, args.concat(arguments));
  }
  fBond.prototype = self.prototype;
  return fBond;
}

而後爲了避免會修改原來函數的原型,即getName的原型。咱們能夠經過一箇中間函數來繼承。

Function.prototype.bind = function () {
  if (typeof this !== "function") {
    throw new Error("Must be function to call bind");
  }

  const [ctx, args] = arguments;
  const self = this;
  var fNOP = function () {};
  const fBond = function () {
    return self.apply(this instanceof fBond ? this : ctx, args.concat(arguments));
  }
  // 這裏將self的原型賦值給來fNOP,後面fBond的原型賦值爲new fNOP(),這樣就和self的原型斷開了
  // 後面再修改fBond的原型也不會影響到self,即getName.prototype
  fNOP.prototype = self.prototype;
  fBond.prototype = new fNOP();
  return fBond;
}

這樣,咱們就實現了一個比較完整的bind方法了。

前端收藏家(微信號: fedaily)

收集全網優秀前端技術資訊,與你分享,共同成長。

前端收藏家.jpg

相關文章
相關標籤/搜索