第7期 call、apply和bind的实现原理

117 阅读3分钟

call

主要是第2步

Function.prototype.call2 = function(bindObj,...params){
    // 1、如果bindObj没有传就默认为window 如果传入的不是对象 就转成对象
    bindObj = bindObj ? Object(bindObj) : window;
    // 2、bindObj添加一个fn函数 为this 即要执行的原函数
    // 这里直接设置fn有可能会产生bug 因为人家就是有一个方法叫做fn咋办 
    // 可以设置fnxx xx为随机数 生成后先检查有没有fnxx 没有再进行设置
    // 可以使用Symbol Symbol就是个独一无二的值 var fn = Symbol(); // added context[fn] = this; // changed
    bindObj.fn = this;
    // 3、通过这种方式执行原函数 this即使bindObj 通过这种方式改变this 并进行传参
    let respon = bindObj.fn(...params)
    // 4、使用后删除刚刚添加的fn函数
    delete bindObj.fn;
    // 5、返回执行原函数后的返回值
    return respon;
}
// call2实现如下效果
var value = 2;
var obj = {
    value: 1
}
function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}
function foo() {
    console.log(this);
}
// 此处没有传入要绑定的对象 默认为window
bar.call2(null); // 2
// 此处传入的不是对象 转成对象 此处还未执行删除fn操作 所以this为一个包含fn的对象
foo.call2(123); // Number {123, fn: ƒ}
// 此处bar还会return一个对象
bar.call2(obj, 'kevin', 18);
// 1
// {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

apply

与call的原理差不多,只是参数的形式改变了

Function.prototype.apply2 = function(bindObj,params){
    bindObj = bindObj ? Object(bindObj) : window;
    bindObj.fn = this;
    // 判断一下是否传参
    let respon = params ? bindObj.fn(...params) : bindObj.fn();
    delete bindObj.fn;
    return respon;
}
// apply2实现如下效果
var value = 2;
var obj = {
    value: 1
}
function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}
function foo() {
    console.log(this);
}
bar.apply2(null); // 2
foo.apply2(123); // Number {123, fn: ƒ}
bar.apply2(obj, ['kevin', 18]);
// 1
// {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

bind

需要考虑将一个绑定好并返回的函数 当做构造函数使用 需要考虑绑定前的函数的原型方法 在返回的绑定后返回的函数也需要能够调用

Function.prototype.bind2 = function(bindObj, ...params) {
  // 1、考虑this不是函数 抛出一个异常
  if (typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  }
  // 2、保存this 不然等到执行函数的时候 this就会指向window
  let self = this;
  // 3、返回一个函数 调用的时候执行 因为bind不像call和apply 不是立即执行的
  function returnFun(params2) {
    // 4、判断当前this是否是returnFun的实例对象
    // true 则证明returnFun作为构造函数使用 绑定对象为当前this 即实例对象
    // false 则证明returnFun作为普通函数使用 绑定对象为需要绑定的对象 即bingObj
    self.apply(this instanceof returnFun ? this : bindObj, [...params, params2]);
  }

  // 5、当需要绑定的函数 即bar的原型有一些特有的方法的时候 为了让绑定后也能够使用 故将返回的函数的原型也指向bar的原型
  // 方法一 该方法有个缺点就是 当returnFun.prototype被更改的时候 bar的原型也会被更改 因为指向同一个原型
  // returnFun.prototype = this.prototype;
  // 方法二
  returnFun.prototype = Object.create(this.prototype);

  return returnFun;
};

// 测试用例
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.bind2(foo, 'Jack');
// bindFoo(20); // 1 Jack 20

// 当做构造函数使用
var bindFoo = bar.bind2(foo, 'Jack');
let obj = new bindFoo(20); // undefined Jack 20 因为this指向实例对象 所以找不到value这个字段

// 测试更改原型是否会影响bar的原型 方法二并不会影响
bindFoo.prototype.friend = 'test';
console.log(bindFoo.prototype, bar.prototype, obj.habit);
// {friend: "test"} {friend: "kevin", constructor: ƒ} "shopping"

参考

muyiy.cn/blog/3/3.4.…