手动实现call、apply、bind、new

222 阅读3分钟

写在前面

在实现这三个方法之前,我们首先要清楚这三个方法的具体作用以及相同点和不同点。

方法相同点不同点
call改变this指向call 方法第一个参数是要绑定给this的值,后面传入的是一个参数列表。当第一个参数为null、undefined的时候,默认指向window
apply改变this指向apply接受两个参数,第一个参数是要绑定给this的值,第二个参数是一个参数数组。当第一个参数为null、undefined的时候,默认指向window。
bind改变this指向和call很相似,第一个参数是this的指向,从第二个参数开始是接收的参数列表。区别在于bind方法返回值是函数以及bind接收的参数列表的使用。

了解了它们各自的作用之后,接下来我们来看看具体的实现方式

手动实现call方法

代码实现

	/**
     *  手动实现call方法
     * @param {*} obj 需要改变的对象
     * @param  {...any} arg 参数
     */
    Function.prototype.mycall = function(obj, ...arg) {
        obj._fn = this;
        let val = obj._fn(...arg); 
        delete obj._fn;
        return val;
    }

测试一下

	const callTest = {
    	name: 'callTest'
    }
    const callObject = {
        name: 'callObject',
        fn: function() {
           console.log(this.name, ...arguments);
        }
    }
    callObject.fn.call(callTest, 1,2,3); // 执行结果:callTest 1,2,3
	callObject.fn.mycall(callTest, 1,2,3); // 执行结果:callTest 1,2,3

执行结果和预期一致,证明这个手动实现的call方法OK了。那我们分析一下这段代码

1、首先这个方法毋庸置疑肯定是添加在Function对象的原型上的。

2、传入两个参数,第一个是要将this改变的目标对象,第二个是真正的要执行的方法的参数,也就是上面的例子中的callObject.fn的参数。

3、obj._fn = this; 我们给目标对象添加了一个_fn_属性,并将mycall调用者赋值给_fn,注意这里的调用者,其实就是callObject.fn函数。

4、let val = obj._fn(...arg); 这里是callObject.fn真正执行的地方,可以看出此时是通过obj去调用了callObject的fn方法,所以当前方法的this就指向了obj对象。

5、return val; 最后将执行结果返回。

注意:delete obj._fn;这里需要值最终执行完callObject的fn方法之后删除,以免造成脏数据。

手动实现apply方法

代码实现

  /**
   *  手动实现apply方法
   * @param {*} obj 
   * @param {*} arr 
  */
  Function.prototype.myApply = function(obj, arr = []) {
    let args = [];
    let val;
    for(let i = 0; i < arr.length; i ++) {
        args.push('arr[' + i + ']');
    }
    obj._fn = this;
    val = eval('obj._fn('+ args + ')');
    delete obj._fn;
    return val;
  }

测试一下

	const applyTest = {
    	name: 'applyTest'
	}
	const applyObject = {
        name: 'applyObject',
        fn: function(sex, age) {
            console.log(`name:${this.name} 性别:${sex} 年龄${age}`);
        }
    }
    let params = ['男', '10']
    applyObject.fn.apply(applyTest, params)// name:applyTest 性别:男 年龄:10
    applyObject.fn.myApply(applyTest, params);// name:applyTest 性别:男 年龄:10

执行结果和预期一致,证明这个手动实现的apply方法OK了。那我们分析一下这段代码

1、同样实现apply方法也是需要添加在Function对象的原型上。

2、let args = [];用来保存最终要执行的方法的参数,因为我们知道apply方法的第二个参数是个数组,所以这里需要用一个arr[]来保存。

3、let val; 这个变量没什么可说的,用来保存最终的执行结果

4、for 循环处理参数列表。

5、obj._fn = this; 我们给目标对象添加了一个_fn属性,并将myApply调用者赋值给_fn,注意这里的调用者,其实就是applyObject.fn函数。

6、val = eval('obj._fn('+ args + ')'); 这里是applyObject.fn真正执行的地方,可以看出此时是通过obj去调用了applyObject的fn方法,所以当前方法的this就指向了obj对象。

7、return val; 最后将执行结果返回。

注意:delete obj._fn;这里需要值最终执行完applyObject的fn方法之后删除,以免造成脏数据。

手动实现bind方法

代码实现

  /**
  * 手动实现bind方法
  * @param {*} obj 
  * @param {*} arg1 
  */
  Function.prototype.myBind = function(obj, arg1) {
    return (...arg2) => {
        let args = arg1.concat(arg2);
        let val;
        obj._fn = this;
        val = obj._fn(...args);
        delete obj._fn;
        return val;
    }
  }

测试一下

  const bindTest = {
    name: 'bindTest'
  }
  const bindObject = {
      name: 'bindObject',
      fn: function() {
          console.log(`name:${this.name}`, ...arguments) 
      }
  }
  const bindFun = bindObject.fn.bind(applyTest, '男', '10');
  const myBindFun = bindObject.fn.myBind(applyTest, '男', '10');
  bindFun('汉族'); // name:applyTest 男,10,汉族
  myBindFun('汉族');// name:applyTest 男,10,汉族

执行结果和预期一致,证明这个手动实现的bind方法OK了。那我们分析一下这段代码。

1、同样实现bind方法也是需要添加在Function对象的原型上。

2、我们可以看出bind方法最终是返回了一个方法,符合bind方法的作用。

3、let args = arg1.concat(arg2);这里是为了拼接参数。

4、obj._fn = this;我们给目标对象添加了一个_fn属性,并将myBind调用者赋值给_fn,注意这里的调用者,其实就是bindObject.fn函数。

5、 val = obj.fn(...args);这里是bindObject.fn真正执行的地方,可以看出此时是通过obj去调用了bindObject的fn方法,所以当前方法的this就指向了obj对象。

6、最终返回了一个函数,这一符合我们的预期,bind方法改变this指针,且返回一个函数。

注意:delete obj._fn;这里需要值最终执行完applyObject的fn方法之后删除,以免造成脏数据。

手动实现new方法

  • 1、新生成了一个对象
  • 2、新对象隐式原型链接到函数原型
  • 3、调用函数绑定this
  • 4、返回新对象
    function myNew(FN, ...args) {
        var obj = {};
        obj.__proto__ = FN.prototype;
        const result = FN.apply(obj, args);
        return typeof result === 'object' ? result : obj;
    } 

测试一下

    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    const person = myNew(Person, '张三', 20);
    console.log(person.name); // 占三
    console.log(person.age); // 20
    console.log(person instanceof Person);  // true

总结

至此我们也就手动实现了call、apply、bind、new方法。如果有不同的实现方法,可以下发留言,感谢支持!!!