實現 call、apply、bind

實現 call、apply、bind

在以前一篇文章寫了這三個參數的區別,可是其實面試更常考察如何實現。其實全部的原生函數的 polyfill 如何實現,只須要考慮 4 點便可:javascript

  1. 基本功能
  2. 原型
  3. this
  4. 返回值

call

  1. call 的基本功能:html

    call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。java

  2. 原型
    不涉及原型鏈的轉移,不用管
  3. this
    本質上,call 就是 this 的轉移
  4. 返回值git

簡單實現:github

Function.prototype.myCall = function(context = window, ...args) {
  context.fn = this; // 先將fn掛在context上,
  var res = context.fn(...args); // 而後經過context調用fn,使得fn中的this指向指到context上
  delete context.fn; // 最後刪除掉context上的fn
  return res; // 返回原函數的返回值
};

上面爲了簡單,使用了 ES6 的剩餘參數和展開語法,基本用這個回答面試官就行了。固然,若是不讓使用剩餘參數,那就只能使用eval或者new Function的字符串拼接大法了,能夠參考這篇模板引擎
再就是 fn 可能會和 context 重名,整一個不會重名的 uniqueID 掛上去,執行完畢後刪除。面試

apply

以前提過 apply 和 call 區別,只有一些入參和性能上的區別。直接上代碼:app

Function.prototype.myApply = function(context = window, args) {
  context.fn = this; // 先將fn掛在context上,
  var res = context.fn(...args); // 而後經過context調用fn,使得fn中的this指向指到context上
  delete context.fn; // 最後刪除掉context上的fn
  return res; // 返回原函數的返回值
};

bind

bind 有點不同,它會返回一個綁定了 this 的函數。函數

bind()方法建立一個新的函數,在 bind()被調用時,這個新函數的 this 被 bind 的第一個參數指定,其他的參數將做爲新函數的參數供調用時使用。性能

Function.prototype.myBind = function(context, ...args) {
  var fn = this;

  var newFn = function(...restArgs) {
    // 使用call去調用fn,由於bind可能會bind一部分參數,因此把restArgs也傳進去
    return fn.call(context, ...args, ...restArgs);
  };

  return newFn;
};

上面的函數基本上覆蓋了大部分場景,可是不能支持new調用——this

綁定函數自動適應於使用 new 操做符去構造一個由目標函數建立的新實例。當一個綁定函數是用來構建一個值的,原來提供的 this 就會被忽略。不過提供的參數列表仍然會插入到構造函數調用時的參數列表以前。

若是直接使用咱們上面所寫的bind,就會返回

function Person(age, name) {
  this.name = name;
  this.age = age;
}

var Age18Person = Person.myBind(null, 18);

var a = {};
var Age20Person = Person.myBind(a, 20);

var p18 = new Age18Person("test18"); // newFn {}
var p20 = new Age20Person("test20"); // newFn {}
// a {name: "test20", age: 20}
// window {name: "test18", age: 18}

顯然,返回了以newFn生成的對象,而且,由於傳入的是null,因此,對context的賦值轉移到了window
這裏須要判斷是否被 new 調用,而後丟棄沒用的 context。

Function.prototype.myBind = function(context, ...args) {
  var fn = this;

  var newFn = function(...restArgs) {
    // 若是是new構造,則使用new構造的實例
    if (new.target) {
      return fn.call(this, ...args, ...restArgs);
    }
    // 使用call去調用fn,由於bind可能會bind一部分參數,因此把restArgs也傳進去
    return fn.call(context, ...args, ...restArgs);
  };

  return newFn;
};

再次調用上面的new構造,發現實例的原型不是指向咱們但願的 Person

var Age18Person = Person.myBind(null, 18);

var p18 = new Age18Person("test18"); // newFn {}

p instanceof Person; // false
p instanceof Age18Person; // false

記錄一下原型鏈,再來一遍

Function.prototype.myBind = function(context, ...args) {
  var fn = this;

  var newFn = function(...restArgs) {
    // 若是是new構造,則使用new構造的實例
    if (new.target) {
      return fn.call(this, ...args, ...restArgs);
    }
    // 使用call去調用fn,由於bind可能會bind一部分參數,因此把restArgs也傳進去
    return fn.call(context, ...args, ...restArgs);
  };

  // 綁定原型鏈
  newFn.prototype = this.prototype;

  return newFn;
};

可是這裏還有個問題,若是改了Age18Personprototype,也會影響到Personprototype
因此,須要作一箇中轉——

Function.prototype.myBind = function(context, ...args) {
  var fn = this;

  var newFn = function(...restArgs) {
    // 若是是new構造,則使用new構造的實例
    if (new.target) {
      return fn.call(this, ...args, ...restArgs);
    }
    // 使用call去調用fn,由於bind可能會bind一部分參數,因此把restArgs也傳進去
    return fn.call(context, ...args, ...restArgs);
  };

  var NOOP = function() {};

  // 綁定原型鏈
  NOOP.prototype = this.prototype;
  newFn.prototype = new NOOP();

  return newFn;
};

這樣基本上就算完成了,固然更推薦function-bind方案。 完

相關文章
相關標籤/搜索