面试的时候call和apply是经常被问到的点,它们可以用来改变函数的this指向,那么是怎么实现的呢?
首先我们定义一个需要用到的实例代码
const name = 45456;
const obj = {
name: 111,
fn: function (param1, param2) {
console.log(this.name, param1, param2);
},
};
我们调用自带的call方法
obj.fn.call(null, 2, null);//obj.fn.call(window, 2, null);
在浏览器输出的this都会是window,也就是this.name是45456
手写call方法:
我们自己写一个模拟call方法,直接上代码(需要注意的点我都写在注释里面了)
Function.prototype.mycall = function (fnThis) {
//方法要添加的Function的原型链上面
const newThis = Object(fnThis || window); //要改变的this,一定要是个Object类型,否则类似字符串类型的会报错
const argParams = [];
newThis._callFn = this; //这里的this指的是函数本身
for (let i = 1; i < arguments.length; i++) {
// arguments本身是个类数组,所以定义一个数组去存放
argParams.push(arguments[i]);
}
newThis._callFn(...argParams); // 也可以写成 eval("newThis.fn(" + argParams + ")"); 不过传入null会默认转成undefined
delete newThis._callFn; //把刚刚新增的函数删掉
};
调用一下,this指向没有问题
console.log("自己封装的call:");
obj.fn.mycall(null, null, 2);
把this指向自己,name是111,也没错
console.log("自己封装的call:");
obj.fn.mycall(obj, null, 2);
那么call和apply唯一的不同点就是:call可以多个参数传入,而apply传入的第二个参数必须是数组
先调用自带的apply函数
console.log("自带的apply:");
obj.fn.apply(null, [null, 11]);
手写apply方法:
那么贴上自己封装的apply函数,其本质上是跟call没啥差别,只是多了判断
Function.prototype.myapply = function (fnThis) {
//方法要添加的Function的原型链上面
const newThis = Object(fnThis || window); //要改变的this,一定要是个Object类型
const params = arguments[1];
const arrParams = [];
newThis._applyFn = this; //这里的this指的是函数本身
// 判断一下第二个参数是否是数组
if (Array.isArray(params)) {
for (let i = 0; i < params.length; i++) {
arrParams.push(params[i]);
}
}
newThis._applyFn(...arrParams);
delete newThis._applyFn; //把刚刚新增的函数删掉
};
那么细心的朋友可能会发现,自带的apply如果参数传的不是一个数组可能会抛出错误,例如这样:
obj.fn.apply(null, 11);
那么为了解决这个问题,我们在原来的基础上加多一个判断
Function.prototype.myapply = function (fnThis) {
//方法要添加的Function的原型链上面
const newThis = Object(fnThis || window); //要改变的this,一定要是个Object类型
const params = arguments[1];
const arrParams = [];
newThis._applyFn = this; //这里的this指的是函数本身
// 判断一下第二个参数是否是引用类型
if (typeof params !== "object") {
throw new TypeError("CreateListFromArrayLike called on non-object");
}
// 判断一下第二个参数是否是数组
if (Array.isArray(params)) {
for (let i = 0; i < params.length; i++) {
arrParams.push(params[i]);
}
}
newThis._applyFn(...arrParams);
delete newThis._applyFn; //把刚刚新增的函数删掉
};
console.log("自己封装的apply:");
obj.fn.myapply("hello", 11);
console.log("自己封装的apply:");
obj.fn.myapply(window, [11, { name: 11 }]);
this指向window,输出也是对的
这下就完美了!