参考蓝桥云课
在尝试去实现 call 函数之前,我们再来回顾一下它的用法,call 函数是为了改变 this 的指向,代码如下:
var userName = "xxx";
const person = {
userName: "zhangsan",
};
function fn() {
console.log(this.userName);
}
fn.call(); // 直接调用,this 指向 window,输出 'xxx'
fn.call(person); // 用 call,this 指向 person,输出 'zhangsan'
call 是写到 Function.prototype 上的方法,而本节我们要实现的 myCall 是把函数当作参数传递进去,两者只是调用形式不同,原理都是一样的。
我们尝试来实现一下显式改变 this 指向的功能,调用对象中的函数,this 指向为这个对象,所以我们需要做的操作是:
- 把函数 fn 挂载到要指向的对象 context 上。
- 执行 context.fn,执行完了删除 context 上的 fn 函数,避免对传入对象的属性造成污染。
代码实现如下:
function myCall(fn, context) {
// 把函数 fn 挂载到对象 context 上。
context.fn = fn;
// 执行 context.fn
context.fn();
// 执行完了删除 context 上的 fn 函数,避免对传入对象的属性造成污染。
delete context.fn;
}
测试一下:
var userName = "xxx";
const person = {
userName: "zhangsan",
};
function fn() {
return this.userName;
}
myCall(fn, person); // 输出 'zhangsan'
myCall(fn, window); // 输出 'xxx'
可以看到,仅仅三行代码,我们就实现了 call 函数的核心功能。
不过这里面有一些其他细节需要处理,比如:
- 要处理 context 不传值的情况,传一个默认值 window。
- 处理函数 fn 的参数,执行 fn 函数时把参数携带进去。
- 获取执行函数 fn 产生的返回值,最终返回这个返回值。
最终实现代码如下:
// 要处理 context 不传值的情况,传一个默认值 window。
function myCall(fn, context = window) {
context.fn = fn;
// 处理函数 fn 的参数,执行 fn 函数时把参数携带进去。
const args = [...arguments].slice(2);
// 获取执行函数 fn 产生的返回值。
const res = context.fn(...args);
delete context.fn;
// 最终返回这个返回值
return res;
}
测试一下:
const obj = {
count: 10,
};
function fn(x, y, z) {
console.log(this.count + x + y + z);
}
myCall(fn, obj, 1, 2, 3); // 执行函数 fn,输出 16
这样我们就实现了 call 函数该有的功能,原生的 call 函数是写到 Function.prototype 上的方法,我们也尝试在函数的原型上实现一个 myCall 函数,只需稍加改造即可,代码实现如下:
// 写到函数的原型上,就不需要把要执行的函数当作参数传递进去
Function.prototype.myCall = function (context = window) {
// 这里的 this 就是这个要执行的函数
context.fn = this;
// 参数少了一个,slice(2) 改为 slice(1)
const args = [...arguments].slice(1);
const res = context.fn(...args);
delete context.fn;
return res;
};
测试一下:
const obj = {
count: 10,
};
function fn(x, y, z) {
console.log(this.count + x + y + z);
}
fn.myCall(obj, 1, 2, 3); // 执行函数 fn,输出 16