function myCall(fn,context = window){
context.fn = fn;//将函数fn挂载到对象context上
const args = [...arguments].slice(2);//myCall中的前两个参数(fn,context)不是我们需要的
const res = context.fn(...args);
delete context.fn;//用完删除,避免对传入对象的属性造成污染
return res;
}
这样我们就实现了 call
函数该有的功能,原生的 call
函数是写到 Function.prototype
上的方法,我们也尝试在函数的原型上实现一个 myCall
函数,只需稍加改造即可,代码实现如下:
Function.prototype.myCall = function(context = window){
context.fn = this;
const args = [...arguments].slice(1);//少了一个参数
const res = context.fn(...args);
delete context.fn;
return res;
}
处理边缘情况
上文在函数原型上实现的 myCall
函数,还有优化的空间,有一些边缘的情况,可能会导致报错,比如把要指向的对象指向一个原始值,代码如下:
fn.myCall(0); // Uncaught TypeError: context.fn is not a function
参考一下原生call函数是怎么解决的:
var userName = "xxx";
const person = {
userName: "zhangsan",
};
function fn(type) {
console.log(type, "->", this.userName);
}
fn.call(0, "number");
fn.call(1n, "bigint");
fn.call(false, "boolean");
fn.call("123", "string");
fn.call(undefined, "undefined");
fn.call(null, "null");
const a = Symbol("a");
fn.call(a, "symbol");
fn.call([], "引用类型");
undefined
和 null
指向了 window
,原始类型和引用类型都是 undefined
。
其实是因为,原始类型指向对应的包装类型,引用类型就指向这个引用类型,之所以输出值都是 undefined
,是因为这些对象上都没有 userName
属性。
改造一下我们的 myCall
函数,实现原始类型的兼容,代码如下:
Function.prototype.myCall = function (context = window) {
if (context === null || context === undefined) {
context = window; // undefined 和 null 指向 window
} else {
context = Object(context); // 原始类型就包装一下
}
context.fn = this;
const args = [...arguments].slice(1);
const res = context.fn(...args);
delete context.fn;
return res;
};
还有另外一种边缘情况,假设对象上本来就有一个 fn
属性,执行下面的调用,对象上的 fn
属性会被删除,代码如下:
const person = {
userName: "zhangsan",
fn: 123,
};
function fn() {
console.log(this.userName);
}
fn.myCall(person);
console.log(person.fn); // 输出 undefined,本来应该输出 123
因为对象上本来的 fn
属性和 myCall
函数内部临时定义的 fn
属性重名了,可以用 Symbol
来防止对象属性名冲突问题,继续改造 myCall
函数,代码实现如下:
Function.prototype.myCall = function (context = window) {
if (context === null || context === undefined) {
context = window;
} else {
context = Object(context);
}
const fn = Symbol("fn"); // 用 symbol 处理一下
context[fn] = this;
const args = [...arguments].slice(1);
const res = context[fn](...args);
delete context[fn];
return res;
};