bind,call,apply的实现

apply、call 和 bind 实现

在前面的文章中介绍过了,apply 和bind 的作用就是显示改变函数执行的this的绑定。
apply 和 call 是执行函数并该改变this,二者的参数有所区别
而bind则是 返回一个待执行的新函数, 当新函数执行的时候改变了this的指向。
所以,bind的情况会与apply和call不同。

  1. 我们知道在js里函数是可以被new的 因此情况会比 apply 和 call 多一种情况。
  2. bind 是可以预设参数,
    相同情况是:
    这三个函数对于没有参数 都指向的window 对于原始类型 都会包装成对象的形式

apply的实现

Function.prototype.myapply = function(context){
    context = context ? Object(context) : window;  // 确定对象
    var fn = Symbol(); 
    context[fn] = this;  // 绑定到当前对象上
    let result;
    let arr = [...arguments].slice(1);  // 获取参数
    // 执行函数
    if (!arr.length) {
        result = context[fn]();
    } else {
        result = context[fn](arr); 
    }
    delete context[fn];  // 删除绑定
    return result;
}

call的实现

Function.prototype.mycall = function(context){
    context = context ? Object(context) : window;
    let fn = Symbol();
    // 绑定到当前对象上
    context[fn] = this;
    // 获取参数
    let args = [...arguments].slice(1);
    // 执行函数
    let result = context[fn](...args);
    // 删除绑定
    delete context[fn] 
    return result;
}

bind的实现

如果参照 apply 和 call 的想法,实现方式如下:

Function.prototype.mybind = function(context, ...perAgrs){
    context = context ? Object(context) : window;
    let fn = this; // 记录函数
    let fnBound = function(...args){
        fn.apply(context, perAgrs.concat(args) )
    }
    return fnBound;
}

此时我们正常的绑定函数是没有问题的。
但是在 new 绑定后的函数时候就会出现问题

function fn(a){
    this.a = a;
}
let t1 = {a:2};
let t2 = {a:2};
let mybindFn = fn.mybind(t1);
let bindFn = fn.bind(t2);
let objmybindFn = new mybindFn(1);
let objbindFn = new bindFn(1);
let objn = new fn(1);
console.log(objmybindFn);  // fn {a: 1}
console.log(objbindFn);   // fnBound {}

我们会发现这两结果是不一样的,
objmybindFn是 fnBound 实例化后的对象。
objbindFn则是 fn实例化后的对象。
其实对于objmybindFn的结果,我们也很容易理解,构造函数就是fnBound。而在fnBound的函数体内执行语句是fn.apply(context, perAgrs.concat(args) ) 所以new值设置在t2上了。
可是我们发现原生bind的实现并不是这样的。他使用的构造函数是fn, 而且他也不会改变t1。你可以认为是直接new fn(2).
到此,我们需要解决两个问题。

  1. 判断 new 还是 函数执行,从而确定 fn.apply(context, perAgrs.concat(args) ) 的context是谁。
  2. 原型对象的改写。
    解决方法:
  3. instanceof 可以做原型链的检查, 判断当前对象是都 new 出来的。 用于确定 context是谁。
  4. 重写 fnBound 的原型对象(方法很多)
    1. 直接让 fnBound.prototype = fn.prototype, 这样在改写 fnBound.prototype时候会影响 fn.prototype
    2. fnBound.prototype = new fn(),
    3. fnBound.prototype = Object.create(fn.prototype)
      其实是 2和3 是在 1的中间多加了一个对象, 但是原型链却相连接,这样在改写fnBound.prototype的时候只会改写创建出来的对象,但是访问的时候却可以通过原型链访问到 fn.prototype
      因此注意构建出来的对象,不能覆盖 fn.prototype 上的属性和方法。

实现

Function.prototype.mybind = function(context, ...perAgrs){
    context = context ? Object(context) : window;
    let fn = this; // 记录函数
    let fnBound = function(...args){
        fn.apply( this instanceof fnBound ? this : context, perAgrs.concat(args) )
    }
    fnBound.prototype = Object.create(fn.prototype);
    return fnBound;
}

依然存在问题

function fn(a){
    this.a = a;
}
let mybindFn = fn.mybind({a:2});
let bindFn = fn.bind({a:2});
let objmybindFn = new mybindFn(1);
let objbindFn = new bindFn(1);
let objn = new fn(1);
console.log(mybindFn.prototype) // {}
console.log(bindFn.prototype) // undefined

这里你就会发现,其实我们实现的结果和bind还是有所差异。

原文地址:https://www.cnblogs.com/cyrus-br/p/14031185.html