JavaScript new关键字实例化对象的执行流程

作者:函数茶馆

时间:2026-10-30

浏览:301

深入解析JavaScript中new关键字实例化对象的完整执行流程,包含四个核心步骤、原型链绑定机制、构造函数返回值判定分支与手写模拟实现。

文章目录
在 JavaScript 中,new 关键字用于通过构造函数创建并初始化一个新的对象实例。当我们在一个函数前面加上 new 调用时,JavaScript 引擎并非直接执行普通的函数调用,而是在底层严格按序执行了四个步骤:在内存中创建全新的普通空对象、将该对象的原型指向构造函数的 prototype、将构造函数内部的 this 绑定到该新对象并执行构造逻辑,最后根据构造函数的返回值类型决定返回新对象还是显式返回的对象。

一、new 关键字的四个核心执行阶段

为了准确理解 new 的运行机制,我们可以先通过一个最经典的构造函数示例作为上下文切入点:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHi = function() {
  console.log(`你好,我是 ${this.name}`);
};

const user = new Person('张三', 24);

在上述代码中,new Person('张三', 24) 执行的一瞬间,引擎在幕后协同完成了内存分配、指针重定向与属性填充等一系列工作。下图完整展现了该过程中的四个核心阶段:

JavaScript new关键字实例化对象的执行流程
图 1:JavaScript new 关键字实例化对象的四个执行阶段时序推导

二、深入解析每个执行环节

1. 第一阶段:在内存中创建全新的空对象

当执行 new Person(...) 时,JavaScript 引擎首先会向系统申请一块新的堆内存空间,生成一个纯粹的空对象。这个对象在被创建之初,内部没有任何属于自身的自有属性(Own Properties)。它就像一张白纸,作为后续所有操作的目标载体。

2. 第二阶段:链接原型链(建立继承纽带)

一个普通的空对象并不能直接使用构造函数原型上的方法。因此,第二步的关键是将新对象的内部私有属性 [[Prototype]](在浏览器等宿主环境中通常可以通过 __proto__ 访问)指向构造函数的 prototype 属性所引用的对象。

这一步是 JavaScript 实现基于原型继承的核心。正因为完成了这一链接,新创建的 user 实例即使自身没有声明 sayHi 方法,也可以顺着原型链在 Person.prototype 上成功查找到该方法。

JavaScript new关键字实例化对象的执行流程
图 2:new 实例化过程中构造函数、原型对象与新建实例的内存引用关系

3. 第三阶段:将 this 绑定到新对象并执行函数代码

在常规调用函数时(例如 Person('张三', 24)),非严格模式下函数内部的 this 默认指向全局对象 window(Node.js 环境下为 global),严格模式下则是 undefined。

但在 new 调用的上下文中,引擎将构造函数内部的执行上下文 this 明确重定向并绑定到了第一步新创建的对象上。随后,传入调用时的实参开始执行构造函数内部的代码。诸如 this.name = name 这类赋值操作,就会直接将属性挂载到新创建的对象实例上,完成了实例自身数据的初始化。

4. 第四阶段:判定返回值类型并最终交付

这是 new 关键字在执行流程中最容易让开发者产生困惑的地方。构造函数本质上仍然是一个函数,因此函数体内可能包含 return 语句。对于构造函数的返回值,JavaScript 引擎有非常明确的判定分流规则:

  • 如果构造函数没有显式返回,或者返回的是一个原始类型值(如数字、字符串、布尔值、null、undefined 或 Symbol):引擎会直接无视该返回值,默认返回第一步中创建并初始化好的新对象实例。
  • 如果构造函数显式返回了一个引用类型对象(如普通对象、数组、函数、Date 等):引擎将尊重该返回,丢弃第一步创建的新对象(随后会被垃圾回收),最终 new 表达式的结果将是这个显式返回的对象。
JavaScript new关键字实例化对象的执行流程
图 3:构造函数返回值判定分支决策树

三、手写模拟实现 new 关键字

检验是否真正吃透一个机制的最好方式,就是通过代码亲手模拟它的实现逻辑。我们编写一个名为 myNew 的工具函数,将上述四个步骤逐行还原:

/**
 * 模拟实现 new 关键字的功能
 * @param {Function} constructor 构造函数
 * @param {...any} args 传递给构造函数的参数
 * @returns {Object} 实例化的对象
 */
function myNew(constructor, ...args) {
  // 1. 验证传入的目标是否为函数
  if (typeof constructor !== 'function') {
    throw new TypeError('constructor must be a function');
  }

  // 2. 步骤一与步骤二:以 constructor.prototype 为原型创建一个全新对象
  // Object.create 在底层完成了创建新对象并将 __proto__ 指向参数对象的工作
  const newObj = Object.create(constructor.prototype);

  // 3. 步骤三:将 constructor 内部的 this 绑定到 newObj 并执行构造代码
  const result = constructor.apply(newObj, args);

  // 4. 步骤四:检查返回值类型
  // 判断 result 是否为非 null 的对象或函数(引用类型)
  const isObject = result !== null && (typeof result === 'object' || typeof result === 'function');

  // 如果构造函数显式返回了对象,则返回该对象,否则返回新创建的实例
  return isObject ? result : newObj;
}
代码验证与运行测试:
// 测试案例 1:常规使用
function Student(name, score) {
  this.name = name;
  this.score = score;
}
Student.prototype.getGrade = function() {
  return this.score >= 60 ? '及格' : '待提升';
};

const s1 = myNew(Student, '小李', 88);
console.log(s1.name); // 输出: 小李
console.log(s1.getGrade()); // 输出: 及格
console.log(s1 instanceof Student); // 输出: true

// 测试案例 2:构造函数返回对象
function Special() {
  this.title = 'original';
  return { custom: 'hooked' };
}
const s2 = myNew(Special);
console.log(s2); // 输出: { custom: 'hooked' }

测试结果表明,myNew 的执行效果、原型链结构以及对于特殊返回值的处理,与原生 new 运算符的表现完全一致。

四、实例化机制中的关键细节与容易混淆的边界

1. 为什么箭头函数不能使用 new 实例化?

ECMAScript 规范中,并不是所有函数都能被用作构造函数。函数对象内部有两个关键的内部方法:[[Call]] 和 [[Construct]]。

  • 普通函数与使用 class 声明的类拥有 [[Construct]] 方法,因此支持 new 操作。
  • 而箭头函数(Arrow Function)在设计上是为了保证简洁与词法级 this 绑定。它既没有 [[Construct]] 内部方法,也没有 prototype 原型属性。若对箭头函数执行 new,JavaScript 引擎会直接抛出 TypeError: ... is not a constructor 错误。

2. new.target 元属性

ES6 引入了 new.target 元属性。如果一个函数是通过 new 调用的,其内部的 new.target 将直接指向该构造函数本身;如果是被普通方式调用(如直接调用或通过 call 调用),new.target 则为 undefined。

function SafeUser(name) {
  if (!new.target) {
    // 读者忘记写 new 时,自动重定向为 new 调用
    return new SafeUser(name);
  }
  this.name = name;
}

const u1 = SafeUser('李四'); // 即使不写 new,也能正确返回实例
console.log(u1 instanceof SafeUser); // true
JavaScript new关键字实例化对象的执行流程
图 4:new 关键字底层原理与关键边界规约汇总

五、知识梳理与对比总结

为了让读者在实际开发或代码排错时能迅速查阅定位,下表整理了直接调用函数与通过 new 关键字调用的关键差异对比:

对比维度 直接调用:Person('张三') 使用 new 实例化:new Person('张三')
内部执行机制 触发 [[Call]] 内部方法 触发 [[Construct]] 内部方法
this 的指向 非严格模式下为 window/global,严格模式下为 undefined 严格指向新创建的实例对象
原型链关联 不创建新对象,不进行原型链绑定 自动建立 obj.__proto__ = Constructor.prototype
返回值规则 完全取决于是否有 return,无则返回 undefined 默认返回新实例;若显式返回对象类型则覆盖返回
new.target 值为 undefined 指向被调用的构造函数自身

掌握 new 关键字的执行流程,不仅有助于深入理解 JavaScript 原型继承与上下文绑定机制,也为后续掌握 ES6 类(class)语法糖的本质运作打下了坚实的技术底层支撑。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章