实现 new 操作符

基础知识

在 JavaScript 中,new 不是一个函数,而是一个操作符。它用于创建一个用户自定义的对象类型的实例或具有构造函数的内置对象的实例。当我们执行 let instance = new Constructor(arg1, arg2) 时,new 操作符在背后自动完成了一系列复杂的步骤。

要手动实现一个模拟 new 功能的函数,我们必须首先理解 new 到底做了什么。这个过程大致可以分为四个核心步骤,这也是我们实现的基础。

  1. 构造函数 (Constructor): 任何一个普通的 JavaScript 函数,只要被 new 调用,它就充当了“构造函数”的角色。

  2. 实例 (Instance): 通过 new 创建出来的对象,就是该构造函数的一个“实例”。

  3. this 上下文: 在构造函数内部,this 关键字会指向正在被创建的新实例对象。

  4. 原型链接: new 操作符会自动将新创建的实例对象的原型,链接到构造函数的 prototype 对象上。

核心思路

我们的目标是创建一个函数,例如 myNew(Constructor, ...args),它能模拟原生 new 操作符的行为。这个模拟过程严格遵循 new 的四个内部步骤:

  1. 创建一个新的空对象:这个对象将成为最终返回的实例。

  2. 链接到原型:将这个新创建的空对象的原型,指向构造函数的 prototype 属性。这一步确立了实例与构造函数之间的原型链关系。

  3. 绑定 this 并执行构造函数:将构造函数的 this 指向这个新对象,并执行构造函数的代码(同时传入参数)。这样,构造函数内部对 this 的所有赋值操作,实际上都是在对这个新对象进行赋值。

  4. 返回新对象:在执行完构造函数后,需要判断其返回值。

    • 如果构造函数没有显式 return,或者 return 了一个原始值(如 string, number, boolean, null, undefined),那么就自动返回我们第一步创建的新对象。
    • 如果构造函数显式 return 了一个对象(包括 function, array),那么 new 操作的结果就是这个被返回的对象,而不是第一步创建的新对象。

代码实现

下面是 myNew 的一个完整实现。

/**
 * 自定义 new 操作符
 * @param {Function} Constructor - 构造函数
 * @param {...any} args - 传递给构造函数的参数
 * @returns {Object} - 创建的实例对象
 */
function myNew(Constructor, ...args) {
  // 1. 校验构造函数是否为函数类型
  if (typeof Constructor !== 'function') {
    throw new TypeError(
      'myNew: The first argument must be a constructor function.'
    );
  }

  // 2. 创建一个新对象,并将其原型链接到构造函数的 prototype
  // Object.create() 完美地完成了这两步
  const obj = Object.create(Constructor.prototype);

  // 3. 将构造函数的 this 指向新创建的对象 obj,并执行构造函数
  // 使用 apply 方法,将 args 数组作为参数传入
  const result = Constructor.apply(obj, args);

  // 4. 判断构造函数的返回值
  // 如果返回值是一个对象或函数,则返回该返回值
  if (
    result !== null &&
    (typeof result === 'object' || typeof result === 'function')
  ) {
    return result;
  }

  // 否则,返回新创建的对象 obj
  return obj;
}

代码解析

  1. 函数签名与校验myNew 函数接收的第一个参数必须是 Constructor 构造函数,后续的参数使用 rest 参数语法 ...args 收集成一个数组,方便后续传递。开始时,我们校验 Constructor 必须是一个函数,否则无法进行后续操作。
  2. 创建与链接(Object.create)Object.create(proto) 方法会创建一个新对象,同时将这个新对象的原型设置为 proto。我们使用 Object.create(Constructor.prototype),这一行代码极其优雅地完成了“创建新对象”和“链接到原型”这两个核心步骤。obj 在此时已经是一个空对象,但它的原型链已经正确设置好了。
  3. 执行构造函数(apply):为了将构造函数内部的 this 指向我们新创建的 obj,我们使用 apply 方法。Constructor.apply(obj, args) 的意思是:“请立即执行 Constructor 函数,在执行时,函数内部的 this 给我绑定为 obj 对象,同时把 args 数组里的所有元素作为参数传进去。”执行后,obj 就被赋予了构造函数中定义的属性(例如 this.name = 'xxx')。我们将构造函数的执行结果保存在 result 变量中。
  4. 判断并返回结果:这是 new 操作符一个非常关键的特性。
    • 我们检查 result 是否是一个对象(注意 typeof null 也是 'object',所以要排除 null)或者一个函数。
    • 如果是,根据 new 的规则,我们必须返回这个由用户显式返回的 result 对象。
    • 如果不是(即 resultundefined, null, 或原始值),我们就忽略它,并返回我们最初创建和修改的 obj 对象。这确保了 new 表达式总能返回一个对象。
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 ""