手写实现 apply 方法

理解并手动实现 JavaScript 中的 apply 方法,掌握函数上下文绑定的原理

问题

apply 是 JavaScript 函数原型上的一个方法,用于调用函数并指定函数内部的 this 指向,同时以数组形式传递参数。我们需要手动实现一个 myApply 方法,模拟原生 apply 的行为。

要解决的问题:

  1. 改变函数执行时的 this 指向
  2. 接收数组形式的参数并传递给函数
  3. 返回函数执行结果
  4. 处理边界情况(null/undefined 上下文、非数组参数等)

解答

/**
 * 手写实现 apply 方法
 * @param {Object} context - 要绑定的上下文对象
 * @param {Array} args - 参数数组
 * @returns {*} 函数执行结果
 */
Function.prototype.myApply = function(context, args) {
  // 1. 处理 context,如果为 null 或 undefined,则指向全局对象
  // 浏览器环境为 window,Node.js 环境为 global
  context = context || (typeof window !== 'undefined' ? window : global);
  
  // 2. 将 context 转换为对象类型(处理原始值的情况)
  context = Object(context);
  
  // 3. 创建唯一的属性名,避免覆盖原有属性
  const fnSymbol = Symbol('fn');
  
  // 4. 将当前函数作为 context 的方法
  context[fnSymbol] = this;
  
  // 5. 处理参数:确保 args 是数组
  args = args || [];
  
  // 6. 执行函数并获取结果
  const result = context[fnSymbol](...args);
  
  // 7. 删除临时添加的方法
  delete context[fnSymbol];
  
  // 8. 返回执行结果
  return result;
};

使用示例

// 示例 1:基本使用
function greet(greeting, punctuation) {
  return `${greeting}, 我是 ${this.name}${punctuation}`;
}

const person = { name: '张三' };

console.log(greet.myApply(person, ['你好', '!']));
// 输出: "你好, 我是 张三!"

// 示例 2:数学计算
function sum(a, b, c) {
  return a + b + c;
}

console.log(sum.myApply(null, [1, 2, 3]));
// 输出: 6

// 示例 3:获取数组最大值
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.myApply(null, numbers);
console.log(max);
// 输出: 7

// 示例 4:对象方法调用
const calculator = {
  base: 10,
  add: function(a, b) {
    return this.base + a + b;
  }
};

const obj = { base: 100 };
console.log(calculator.add.myApply(obj, [5, 15]));
// 输出: 120

// 示例 5:构造函数场景
function Person(name, age) {
  this.name = name;
  this.age = age;
}

const obj2 = {};
Person.myApply(obj2, ['李四', 25]);
console.log(obj2);
// 输出: { name: '李四', age: 25 }

关键点

  • Symbol 的使用:使用 Symbol 创建唯一属性名,避免污染原对象或覆盖已有属性

  • 上下文处理:当 contextnullundefined 时,需要指向全局对象;同时使用 Object() 包装原始值类型

  • 临时方法模式:通过将函数临时挂载到目标对象上,利用对象方法调用时 this 自动绑定的特性来改变 this 指向

  • 参数展开:使用扩展运算符 ...args 将数组参数展开传递给函数,这是 ES6 的语法,等同于 apply 的参数传递方式

  • 清理操作:执行完毕后必须删除临时添加的方法,保持对象的纯净性

  • 返回值处理:确保返回函数的执行结果,保持与原生 apply 行为一致

  • 与 call 的区别apply 接收数组参数,而 call 接收参数列表,这是两者的区别