new操作符在JS中做了什么:构造器执行的四个步骤
深入解析JavaScript中new操作符的底层运作机制,详细拆解创建对象、链接原型、绑定this与处理返回值的执行步骤与代码实现。
new 关键字调用一个构造函数时,语言运行时并不仅仅是执行该函数内部的代码,而是按严格次序完成四项底层工作:① 创建一个全新的普通对象;② 将该新对象的原型链关联到构造函数的原型对象;③ 改变执行上下文,将构造函数内部的 this 绑定到该新对象并传入实参执行;④ 判定构造函数的返回值类型以裁定最终产物。理解这四个步骤,是搞懂 JavaScript 面向对象、原型链继承以及上下文绑定机制的关键基石。
一、为什么需要 new:构造器模式的核心职责
JavaScript 本质上是基于原型的动态语言。在引入 ES6 的 class 语法糖之前,开发者完全依托普通函数扮演“构造器”(Constructor)的角色。即使在现代前端开发中,class 底层的实例生成方式仍然遵循传统构造函数的调用规范。
如果直接以普通函数方式调用构造器(例如 Person("张三", 25)),函数内部的 this 在非严格模式下会逃逸绑定至全局对象(浏览器环境为 window,Node.js 环境为 global),在严格模式下则为 undefined,导致不仅无法生成独立实例,反而会污染全局作用域或直接抛出运行时异常。
new 操作符的作用,就是为普通函数包裹一套“生命周期装配流水线”,使得构造函数专注于为新生成的对象赋予状态与属性,而将对象的申请、继承纽带连接以及返回值托底收拢由运行时引擎自动化完成。
二、核心解析:构造器执行的四个详细步骤
根据 ECMAScript 官方规范中关于内置对象内部方法 [[Construct]] 的描述,new Func(...args) 的执行过程严格划分为以下四个阶段:
步骤 1:在堆内存中创建一个全新的空对象
引擎首先向内存管理器申请一块全新的对象存储空间。此时新创建的对象是一个纯净的空对象(在概念层面相当于 {},在规范层面对应 OrdinaryCreateFromConstructor)。
这个空对象目前尚未被赋予任何自有业务属性,但它拥有独立合法的内存地址。后续步骤中所有对属性的装载,都将依托这一块内存空间展开。
步骤 2:将新对象的内部原型链接到构造函数的 prototype
生成空对象后,引擎将该对象的内部隐藏属性 [[Prototype]](浏览器及Node环境中可通过 __proto__ 访问,现代代码推荐使用 Object.getPrototypeOf())指向构造函数的 prototype 属性对象。
这一步正是 JavaScript 实例继承共享属性与方法的根本来源。自此,凡是挂载在 Constructor.prototype 上的公共方法,该新对象均可通过原型链回溯机制顺畅访问。
步骤 3:将构造函数的 this 绑定到新对象并执行代码
完成原型链搭建后,引擎正式调用构造函数本身。此时关键之处在于改变函数的执行上下文:构造函数体内的 this 被明确强制指向步骤 1 创建出的新对象。
同时,调用 new 时传入的所有实参(arguments)也会一并传递给构造函数。在此阶段,函数体中的赋值语句(如 this.name = name; this.age = age;)被逐行执行,新对象逐步获得其特有的实例属性与状态。
步骤 4:根据函数返回值决定最终输出
构造函数执行完毕后,引擎需要决定 new 表达式最终返回给调用方的具体内容。这里包含一条十分重要却常被忽视的分支逻辑:
- 若构造函数显式返回了一个对象类型(Object、Array、Function、Date 等引用类型):引擎将放弃在步骤 1 创建的新对象,转而直接返回构造函数显式返回的那个对象。
- 若构造函数没有显式返回值(默认返回
undefined)或返回的是原始值(Primitive values,如数字、字符串、布尔值、Symbol、null 等):引擎将直接忽略该返回值,如实返回步骤 1 中创建并经过步骤 3 初始化完毕的那个新实例对象。
typeof null === "object",但在构造函数返回值裁定时,规范将 null 判定为原始值类型。因此,如果构造函数中写了 return null;,new 操作符依然会返回步骤 1 创建的新实例,而绝不会返回 null!
三、代码实现:用 JavaScript 手写实现 myNew
为了彻底吃透这四个步骤,我们可以脱离原生 new,完全用原生 JavaScript 编写一个 myNew 模拟函数,把上述四个动作按顺序具象化:
/**
* 模拟实现 JavaScript 的 new 操作符
* @param {Function} Constructor - 构造函数
* @param {...any} args - 传递给构造函数的参数
* @returns {Object} 最终的实例化对象
*/
function myNew(Constructor, ...args) {
// 基础防御:确保传入的 Constructor 是合法的函数
if (typeof Constructor !== 'function') {
throw new TypeError('Constructor must be a function');
}
// 【步骤 1 & 步骤 2】:创建新对象并将原型链关联到 Constructor.prototype
// Object.create() 会直接以传入的对象为原型创建一个全新的空对象
// 若 Constructor.prototype 不是对象(例如为 null),则回退到 Object.prototype
const prototype = typeof Constructor.prototype === 'object' && Constructor.prototype !== null
? Constructor.prototype
: Object.prototype;
const instance = Object.create(prototype);
// 【步骤 3】:将构造函数的 this 绑定到新创建的 instance 对象上,并传入参数执行
const result = Constructor.apply(instance, args);
// 【步骤 4】:判定返回值。如果返回值是对象或函数类型(排除 null),则返回它;否则返回 instance
const isObject = typeof result === 'object' && result !== null;
const isFunction = typeof result === 'function';
return (isObject || isFunction) ? result : instance;
}
测试验证与行为对比
我们可以编写对应的测试用例,分别检验普通属性赋值、原型方法继承以及返回值覆盖等场景:
// 1. 定义测试构造函数
function User(name, age) {
this.name = name;
this.age = age;
}
// 在原型上挂载共享方法
User.prototype.sayHi = function() {
return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
};
// 使用自定义 myNew 创建实例
const u1 = myNew(User, '李四', 28);
console.log(u1.name); // 示例输出: "李四"
console.log(u1.age); // 示例输出: 28
console.log(u1.sayHi()); // 示例输出: "你好,我是 李四,今年 28 岁。"
console.log(u1 instanceof User); // 示例输出: true
console.log(u1.constructor === User); // 示例输出: true
// 2. 验证步骤四:返回原始值的情况
function PrimitiveReturn(val) {
this.val = val;
return "我是字符串原始值";
}
const pObj = myNew(PrimitiveReturn, 42);
console.log(pObj.val); // 示例输出: 42 (字符串被忽略,成功拿到实例)
// 3. 验证步骤四:返回自定义对象的情况
function ObjectReturn() {
this.status = 'ignored';
return { custom: 'override' };
}
const oObj = myNew(ObjectReturn);
console.log(oObj); // 示例输出: { custom: 'override' } (原实例被覆盖)
四、适用边界与限制:哪些函数不能被 new?
并非所有函数都能配合 new 操作符调用。在 ECMAScript 规范中,一个函数能否被 new 调用的根本判定标准是:该函数内部是否实现了 [[Construct]] 内部方法。
| 函数类型 | 是否拥有 [[Construct]] | 能否使用 new | 运行时表现与原因说明 |
|---|---|---|---|
| 普通函数 / 函数声明 | 具备 | 可以 | 拥有 prototype 属性与 [[Construct]] 插槽,默认作为构造器支持 |
| ES6 Class 构造器 | 具备 | 必须使用 new | 只能通过 new 调用,直接调用会抛出 TypeError: Class constructor cannot be invoked without 'new' |
| 箭头函数 (Arrow Function) | 不具备 | 不能 | 抛出 TypeError: ... is not a constructor。无 prototype 属性,this 词法绑定外层 |
| 对象方法简写 (Method Shorthand) | 不具备 | 不能 | 例如 const o = { foo() {} },简写方法不挂载 prototype,禁止 new |
| Symbol / BigInt 函数 | 内部被禁用 | 不能 | 作为基本包装函数受到语言保护,抛出 TypeError: Symbol is not a constructor |
new.target 检测当前函数是否通过 new 关键字被调用。若以常规方式调用,new.target 为 undefined;若以 new 实例化,它指向该构造函数本身。很多库利用此特性自动修正调用方式(例如 if (!new.target) return new Person(...args);)。
五、全景归纳与总结
综上所述,new 操作符的核心价值在于规范化了实例创建、继承关联、属性赋予和产物兜底这四个阶段。
无论是在手写模拟实现面试题中,还是在日常排查诸如原型方法丢失、构造函数内部属性覆盖或 this 丢失等实际 Bug 时,只要紧扣“创对象 → 链原型 → 绑 this → 判返回”这四条清晰的主线,即可快速定位并解决绝大多数与对象实例化相关的疑难杂症。
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
相关标签:








