call
是 JavaScript 中非常核心的函数方法之一。它能改变函数的执行上下文(也就是 this 的指向),在日常开发和面试中都极其常见。本文将带你一步步实现一个Function.prototype.call
的自定义版本,真正理解它的底层原理。
✨ 一、call 方法做了什么?
在 MDN 中,Function.prototype.call()
是这样定义的:
call()
方法使用一个指定的this
值和若干个指定的参数(参数的列表)来调用一个函数。
通俗来讲,它的作用就是:
显式指定函数的
this
指向;并立即执行该函数;
参数可以依次传入。
📌 二、原生 call 的底层逻辑拆解
来看一段模拟的执行逻辑:
var obj = {x: 100,fn() {console.log(this.x);}
};
obj.fn(); // 输出 100
上面 fn()
的 this 指向 obj
,因为是以 obj.fn()
的形式调用。
而 call
就是模拟了类似的过程——把函数挂载到目标对象上执行,从而实现 this 指向绑定。
🧠 三、call 方法内部做了哪些事?
判断传入的
context
(目标对象)是否为值类型(如字符串、数字),如果是,就转为对象(如'abc'
→new String('abc')
);将函数临时设为
context
的属性;通过
context.fn()
执行函数,此时this
指向 context;删除临时挂载的函数属性,防止污染;
返回函数的执行结果。
🔧 四、手动实现 call 方法(完整版)
Function.prototype.myCall = function (context = window, ...args) {if (typeof context !== 'object' && typeof context !== 'function') {context = new Object(context); // 将值类型转换为对象}const fnKey = Symbol(); // 创建唯一属性名,避免覆盖已有属性context[fnKey] = this; // this 是当前函数,即被调用的函数const result = context[fnKey](...args); // 执行函数,绑定 this 并传参delete context[fnKey]; // 清理属性,防止污染return result; // 返回函数执行结果
};
🧪 五、测试用例验证
function f(a, b) {console.log(a + b); // 输出 3console.log(this.name); // 输出 1
}const obj = { name: 1 };
f.myCall(obj, 1, 2); // 等价于 f.call(obj, 1, 2)
输出结果:
3
1
💬 六、值类型绑定说明
function print() {console.log(this);
}print.myCall('abc'); // this → String 对象:new String('abc')
值类型(如字符串、数字)在传入时会被自动装箱为对象类型。
🚧 七、边界情况优化建议
如果
context
是null
或undefined
,原生call
会将this
指向全局对象(非严格模式),或undefined
(严格模式);也可以通过判断加以处理:
context = context ?? window;
🎯 八、面试高频拓展:call vs apply vs bind
方法名 | 作用 | 参数传入方式 | 是否立即执行 |
---|---|---|---|
call | 改变 this 并执行函数 | 参数列表 | ✅ 是 |
apply | 改变 this 并执行函数 | 参数数组 | ✅ 是 |
bind | 改变 this,返回新函数 | 参数列表 | ❌ 否(需手动执行) |
📎 九、小结
手动实现 call
的过程并不复杂,但却是理解 JavaScript 中函数和 this 机制的关键一步。建议掌握以下重点:
this 的绑定原理(对象调用 vs 普通调用)
值类型的自动装箱
如何通过函数挂载 + Symbol 解决属性污染
面试中可以延伸讲解
apply
和bind
的区别
🎁 十、完整源码
Function.prototype.myCall = function (context = window, ...args) {if (typeof context !== 'object' && typeof context !== 'function') {context = new Object(context);}const fnKey = Symbol();context[fnKey] = this;const result = context[fnKey](...args);delete context[fnKey];return result;
};
如果你觉得这篇文章有帮助,欢迎点赞 👍 收藏 ⭐ 评论 💬 交流~