手写call,apply,bind函数

280 阅读3分钟

定义

相同点

  • 这三个都是为了修改this指向的。
  • 接受的第一个参数都是要绑定的this指向.

不同点

  • apply的第二个参数是一个参数数组,而call和bind的第二个及之后的参数作为函数实参按顺序传入。
  • call和apply会立即调用,而bind是返回一个已被改变this指向的新函数,对旧函数没有影响。

实现

手写call函数

问题:

  1. 函数定义的位置?
    • call可以被任何函数调用,所以是在函数的prototype
  2. 如何显示绑定this?
    • 可以将函数绑到对象上,那通过obj.fn这样调用时,函数的this就指向了obj
  3. 如何传参
    • 通过arguments获取除index为0以外的参数

代码

Function.prototype.myCall = function(context) {
    //先做异常处理,判断是否函数
    if(typeof this !== 'function) {
        //类型错误
        throw new TypeError('Error')
    }
    //首先判断上下文环境是否存在,不存在默认对象为 window,就是传入对象,就下方demo而言就是对象lisi
    const ctx = context || window;
    
    //this就是被调用的方法,就下方demo而言就是getName方法
    ctx.fn = this;
    
    //获取实参,因为arguments是伪数组,所以需要转成真数组,Array.from也可以
    const arg = [...arguments].slice(1);
    
    //以对象调用形式调用方法,此时this指向ctx,也就是传入的需要绑定的this指向
    //arguments.length > 1,是为了判断是否存在实参
    const res = arguments.length > 1 ? ctx.fn(...arg) : ctx.fn();
    
    //删除该方法,保持传入对象纯净,不然会对传入对象造成污染
    delete ctx.fn;
    
    //最后返回执行结果
    return res;
}

demo

const zhangsan = {
    name: 'zhangsan',
    getName: function() {
        console.log(this.name)
    }
}

const lisi = {
    name: 'lisi'
}

console.log(zhangsan.getName.myCall(lisi))   //lisi

手写apply函数

问题:

  1. 函数定义的位置?
    • 参考call函数
  2. 如何显示绑定this?
    • 参考call函数
  3. 如何传参
    • apply接受的是一个参数数组

代码

Function.prototype.myApply = function(context) {
    if(typeof !== 'function') {
        throw new TypeError('Error')
    }
    const ctx = context || window;
    ctx.fn = this;
    //因为apply接受两个参数,context和一个参数数组
    const arg = argument[1];
    const res = arg ? ctx.fn(...arg) : ctx.fn();
    delete ctx.fn;
    return res;
    
}

demo

const zhangsan = {
    name: 'zhangsan',
    getName: function() {
        console.log(this.name)
    }
}

const lisi = {
    name: 'lisi'
}

console.log(zhangsan.getName.myApply(lisi))   //lisi

手写bind函数

问题:

  1. 函数定义的位置?
    • call可以被任何函数调用,所以是在函数的prototype
  2. 如何显示绑定this?
    • 可以将函数绑到对象上,那通过obj.fn这样调用时,函数的this就指向了obj
  3. 如何传参
    • bind函数返回一个绑定函数,最终调用需要传入函数实参和绑定函数的实参

代码

Function.prototype.myBind = function(context) {
    if(typeOf this !== 'function) {
        throw new TypeError('error')
    }
    const ctx = context || window;
    const arg = [...arguments].slice(1);
    const _this = this;
    return function F() {
        //检测 New , 如果当前函数的this指向的是构造函数中的this 则判定为new 操作
        if(this instanceOf F) {
            return new _this(...ars, ...arguments)
        }
        return _this.applay(ctx, arg.contact(...arguments))
    }
    
}
  • bind返回一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过new的方式,我们先来说直接调用的方式
  • 对于直接调用来说,这里选择了apply的方式,但是对于参数需要注意以下情况:因为bind可以实现类似这样的代码 f.bind(obj,1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(…arguments)
  • new的方式,我们先判断this,对于new的情况,不会被任何方式改变this,所以对于这种情况我们需要忽略传入的this,此时this应该指向构造出的实例,而不是bind绑定的第一个参数

demo

    function getName(name) {
       this.name = name;
    }
    const animal = {};
    let person = getName.myBind(animal);
    person('人');
    console.log(`animal.name`, animal.name);// 人
   
    const zhangsan = new person('zhangsan');
    console.log(`zhangsan`, zhangsan);  //getName {name: "zhangsan"}
    console.log(`animal.name`, animal.name); //人

涉及到的知识点:

  • error类型定义等
  • arguments相关,
  • typeOf和instanceOf

参考