5.实现 call

发布于:2025-08-04 ⋅ 阅读:(12) ⋅ 点赞:(0)

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 方法内部做了哪些事?

  1. 判断传入的 context(目标对象)是否为值类型(如字符串、数字),如果是,就转为对象(如 'abc'new String('abc'));

  2. 将函数临时设为 context 的属性;

  3. 通过 context.fn() 执行函数,此时 this 指向 context;

  4. 删除临时挂载的函数属性,防止污染;

  5. 返回函数的执行结果。


🔧 四、手动实现 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);       // 输出 3
  console.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')

值类型(如字符串、数字)在传入时会被自动装箱为对象类型。


🚧 七、边界情况优化建议

  • 如果 contextnullundefined,原生 call 会将 this 指向全局对象(非严格模式),或 undefined(严格模式);

  • 也可以通过判断加以处理:

context = context ?? window;

🎯 八、面试高频拓展:call vs apply vs bind

方法名 作用 参数传入方式 是否立即执行
call 改变 this 并执行函数 参数列表 ✅ 是
apply 改变 this 并执行函数 参数数组 ✅ 是
bind 改变 this,返回新函数 参数列表 ❌ 否(需手动执行)

📎 九、小结

手动实现 call 的过程并不复杂,但却是理解 JavaScript 中函数和 this 机制的关键一步。建议掌握以下重点:

  • this 的绑定原理(对象调用 vs 普通调用)

  • 值类型的自动装箱

  • 如何通过函数挂载 + Symbol 解决属性污染

  • 面试中可以延伸讲解 applybind 的区别


🎁 十、完整源码

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;
};

如果你觉得这篇文章有帮助,欢迎点赞 👍 收藏 ⭐ 评论 💬 交流~


网站公告

今日签到

点亮在社区的每一天
去签到