手写实现call,apply和bind方法
call,apply和bind方法均是改变this指向的硬绑定方法,要想手写实现此三方法,都要用到一个知识点,即对象调用函数时,this会指向这个对象(谁调用this就指向谁)!
先设定一个测试函数及测试对象:
function func(num1, num2) {console.log("this:", this)return num1 + num2
}const testObj = {test: '测试用的对象'
}
手写实现call方法,
自己写的,所以把方法名定义为myCall。实现func的this指向testObj,输出num1 + num2的值。
测试用例:
const res = func.myCall(testObj, 1, 2);
console.log("res:", res);
// 输出this是{ test: '测试用的对象' },
//num1 + num2 计算结果 3
第一步:要实现任何函数都能使用这个myCall方法,则该方法需要定义在原型上面,因此:
Function.prototype.myCall = function(thisArg, ...args) {
}
// thisArg -- 待绑定this的参数对象。
// args -- 待传入的参数,
// 因为call传入参数是挨个传入,所以用rest参数方法,...args
第二步: 设置this,并调用原函数
Function.prototype.myCall = function (thisArg, ...args) {const key = Symbol('key');thisArg[key] = this; // 步骤aconst res = thisArg[key](...args); // 步骤bdelete thisArg[key] // 步骤creturn res
}
步骤a解释:
给传入的thisArg对象添加一个过渡性的属性,此处用Symbol,因为Symbol具有唯一性,不会与thisArg里的属性发生重名覆写的情况。步骤a的this是原函数(本例中是func)。因为执行func.myCall()时,根据谁调用this就指向谁的原则,myCall()里的this即指向func。因此步骤a最终是往thisArg中添加了原函数func。
步骤b解释:
执行thisArg中刚刚添加的函数func。依旧是谁调用this指向谁的原则,func的this就指向的thisArg。
步骤c解释:删除掉无意义的过渡性属性。
所以上述测试用例中thisArg – testObj,num1 – 1, num2 – 2,结果打印如下:
手写实现apply方法
apply方法和call方法区别在于传入参数是个数组。其余实现原理相同。所以实现代码:
Function.prototype.myApply = function (thisArg, args) {const key = Symbol('key');thisArg[key] = this; const res = thisArg[key](...args); delete thisArg[key] return res
}const res = func.myApply(testObj, [1, 2]);
console.log("res:", res);
// 输出this是{ test: '测试用的对象' },
//num1 + num2 计算结果 3
手写实现bind方法
bind方法和call方法不同点在于bind返回的是个函数。所以实现代码如下
Function.prototype.myBind = function (thisArg, ...args) {return (...args2) => { // 允许调用的时候继续传入参数const key = Symbol('key');thisArg[key] = this; // 箭头函数中this是定义时的this,也就是myBind的thisconst res = thisArg[key](...args, ...args2)delete thisArg[key]return res}
}const res = func.myBind(testObj, 1);
console.log("res:", res(2));
结果:
简而言之,bind可以理解为返回一个函数,返回的函数中使用call改变了this指向。