实现 apply 方法

基础知识

Function.prototype.apply() 是 JavaScript 中用于调用函数的内置方法。它与 call() 方法非常相似,核心作用是允许我们指定函数在执行时的 this 上下文以及其参数。理解并手动实现一个 apply 方法,有助于深化对 JavaScript this 指向和函数调用机制的理解。

在实现我们自己的 apply 方法之前,需要先掌握以下几个 JavaScript 核心概念:

  1. this 关键字: this 的值在 JavaScript 中由函数的调用方式决定。我们实现 apply 的核心目的,就是去手动改变一个函数执行时的 this 指向。

  2. 函数上下文 (Function Context): 每个函数的调用都有一个与之关联的上下文,也就是 this 的值。当我们将函数作为对象的方法调用时(如 obj.myFunc()),this 就指向这个对象(obj)。我们的 myApply 方法就是要打破这种默认行为,临时将任何我们指定的对象设置为函数的上下文。

  3. arguments 对象: 这是一个在函数体内可用的类数组对象,包含了函数被调用时传入的所有参数。虽然现在更推荐使用 rest 参数 (...args),但了解 arguments 对象对于理解一些经典实现仍然有帮助。

核心思路

我们的目标是在 Function.prototype 上创建一个 myApply 方法,其功能要与原生的 apply 方法保持一致。核心思路如下:

  1. myApply 方法需要挂载到 Function.prototype 上,以便所有函数实例都能访问。

  2. 该方法接收两个参数:context (要绑定的 this 对象) 和一个可选的 args 数组 (要传递给函数的参数列表)。

  3. 核心技巧:要改变函数的 this 指向,最直接的方法就是将该函数作为 context 对象的一个临时方法来调用。

    • 例如,要让 fn 函数在 obj 的上下文中执行,我们可以先将 fn 赋值给 obj.tempFn,然后执行 obj.tempFn()。在 tempFn 的执行过程中,其内部的 this 就自然指向了 obj
  4. 执行完毕后,为了不污染原始的 context 对象,必须将我们添加的临时方法(obj.tempFn)删除。

  5. 需要处理一些边界情况,例如:

    • 传入的 contextnullundefined 时,this 应指向全局对象(在浏览器中是 window,在非严格模式下)。
    • 用户可能不传递第二个参数(参数数组)。

关键点

根据上述思路,在具体实现时需要关注以下几点:

  • 获取调用函数: 在 myApply 内部,this 就指向调用 myApply 的那个函数本身。我们需要将其保存下来。
  • 处理 context: 将传入的 context 参数转换为对象类型,如果它是 nullundefined,则替换为全局对象。
  • 唯一属性名: 在向 context 添加临时方法时,属性名必须是唯一的,以避免与 context 对象上已有的属性冲突。可以使用 Symbol 来创建一个独一无二的属性名,这是一种更现代和健壮的做法。如果需要兼容旧版环境,也可以使用一个随机生成的字符串。
  • 参数传递: apply 的第二个参数是一个数组。在调用临时方法时,需要将这个数组展开作为参数传递进去。可以使用 ES6 的展开语法 (...)。
  • 清理和返回: 调用完临时方法后,必须使用 delete 操作符清理掉在 context 上添加的临时属性,并返回临时方法的执行结果。

代码实现

下面是 myApply 的一个完整实现,采用了现代的 ES6 语法。

/**
 * 自定义 apply 方法
 *
 * @param {Object} context - 新函数执行时绑定的 this 对象。
 * @param {Array} argsArray - 一个数组或类数组对象,其中的数组元素将作为单独的参数传给 func 函数。
 * @returns {*} - 返回调用函数的执行结果。
 */
Function.prototype.myApply = function (context, argsArray) {
  // 1. 获取调用 myApply 的函数本身
  const fn = this;

  // 2. 处理 context,如果为 null/undefined 则指向全局对象 window
  // 使用 Object() 包装可以确保 context 是一个对象
  context =
    context === null || context === undefined ? window : Object(context);

  // 3. 使用 Symbol 创建一个唯一的 key,防止与 context 上的原有属性冲突
  const uniqueKey = Symbol('fn');

  // 4. 将函数 fn 作为 context 的一个临时方法
  context[uniqueKey] = fn;

  let result;

  // 5. 执行这个临时方法,并传递参数
  // 检查 argsArray 是否存在且为数组
  if (Array.isArray(argsArray)) {
    // 使用展开语法将数组参数展开
    result = context[uniqueKey](...argsArray);
  } else {
    // 如果没有传递参数数组或格式不正确,则不带参数执行
    result = context[uniqueKey]();
  }

  // 6. 从 context 上删除这个临时方法,避免污染
  delete context[uniqueKey];

  // 7. 返回函数的执行结果
  return result;
};

代码解析

  1. const fn = this;:在 myApply 的执行环境中,this 指向调用它的函数。例如,在 someFunc.myApply(...) 调用中,fn 被赋值为 someFunc
  2. context = ...:这行代码做了两件事:
    • 判断 context 是否为 nullundefined,若是则将其设置为全局对象 window
    • 使用 Object(context) 将原始值(如 1, 'str', true)包装成对应的对象,确保后续可添加属性。
  3. const uniqueKey = Symbol('fn');Symbol 能创建全局唯一的原始值,用它作为属性名可确保不会意外覆盖 context 对象上已存在的同名属性(如 context.fn)。
  4. context[uniqueKey] = fn;:实现的核心,将要执行的函数 fn 挂载到 context 对象上。
  5. result = contextuniqueKey;
    • 通过 context[uniqueKey] 调用函数时,根据 JavaScript 的 this 绑定规则,被调用函数内部的 this 自动指向 context 对象。
    • 使用 ... 展开语法将 argsArray 数组中的每个元素作为独立参数传递给函数,同时处理了 argsArray 不存在的情况。
  6. delete context[uniqueKey];:获取函数执行结果后,删除临时属性,保持 context 对象纯净,不产生副作用。
  7. return result;apply 方法返回被调用函数的返回值,因此也需将结果返回。
Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""