Skip to content
new 运算符的执行过程
概述
new 运算符用于以构造函数为模板生成实例对象。规范通过内部方法 [[Construct]] 定义其行为,整个过程可以拆分成四个固定步骤。同时,元属性 new.target 提供了区分调用方式的能力。
基本概念
[[Construct]] 内部方法
所有函数对象都具备 [[Call]] 内部方法,但只有一部分同时具备 [[Construct]] 内部方法。new 运算符会触发目标函数的 [[Construct]],而不是普通的 [[Call]]。箭头函数、Symbol、BigInt 以及 class 内部定义的方法(包括静态方法)均缺少 [[Construct]],因此无法作为构造函数使用。
new.target 元属性
new.target 只能在函数体内部使用,用于判断当前函数是通过 new 调用还是普通调用。通过 new 调用时,new.target 指向被调用的构造函数本身;普通调用时值为 undefined。
引擎层面的实现方式:每次函数调用都会创建一个新的执行上下文,其中包含一个 [[NewTarget]] 字段。如果执行路径走的是 [[Construct]],该字段会被设为当前构造函数;如果走的是 [[Call]],则保持为 undefined。
在 V8 中,两条调用链的大致结构如下(简化模型,仅反映高层调用路径,不等同于精确实现):
text
new Foo()
→ Builtins::kConstruct
→ 创建 ExecutionContext,设置 new_target = Foo
→ 调用 Foo.[[Construct]]
Foo()
→ Builtins::kCall
→ 创建 ExecutionContext,new_target = undefined
→ 调用 Foo.[[Call]]Reflect.construct 可以显式覆盖 new.target 的值:
js
function Foo() { console.log(new.target); }
function Bar() {}
new Foo(); // [Function: Foo]
Reflect.construct(Foo, [], Bar); // [Function: Bar]这一特性在需要正确继承内置类型(如 Array、Error)但又不想直接使用 class extends 时尤为重要——继承内置类型往往需要 Reflect.construct 配合 new.target 来完成实例创建。
工作原理
执行步骤
ECMAScript 规范中,new Constructor(...args) 的求值过程可以归纳为以下四步:
- 创建一个新的普通对象
obj。 - 将
obj的内部[[Prototype]]设为Constructor.prototype。如果该属性不是对象,则降级为Object.prototype。 - 以
obj作为this值执行Constructor.[[Call]](obj, ...args),得到返回值result。 - 若
result的类型为 Object(包括函数)则返回result;否则返回obj。
返回值规则
第 4 步决定了最终从 new 表达式得到什么:只有构造函数显式返回引用类型(对象或函数)时才会覆盖掉默认创建的新对象;如果返回的是原始值,则会被忽略,仍然返回新对象。
js
function A() { this.x = 1; return 2; }
function B() { this.x = 1; return { x: 2 }; }
console.log(new A().x); // 1
console.log(new B().x); // 2new A() 中的 return 2 是原始值,因此无效,最终返回的新建对象上 x 属性为 1。而 new B() 返回了一个 { x: 2 },所以表达式最终指向的是该对象,其 x 属性也就变成了 2。
设计上必须忽略原始值,是为了保持面向对象语义的一致性。如果允许 return 123 生效,那么 new Foo() 就可能变成一个纯粹的 number,而不是 Foo 的实例,instanceof 检查以及后续在原型链上的方法调用都会直接崩溃。引擎选择只对非对象返回值置若罔闻,确保 new 表达式的结果始终是一个对象。
注意点
构造函数的 prototype 不是对象
原生 new 在设置原型时对非对象值进行了降级处理:如果 Constructor.prototype 被赋为 null 或原始值,新对象的 [[Prototype]] 会被设为 Object.prototype,而不会抛出错误。
js
function Foo() { this.x = 1; }
Foo.prototype = 42;
const obj = new Foo();
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true不能作为构造函数的函数
下列函数或对象由于缺少 [[Construct]] 内部方法,无法与 new 一起使用:
- 箭头函数:没有
[[Construct]],也没有prototype属性。 Symbol()/BigInt():规范明确禁止将其作为构造函数使用。- Proxy(未定义
construct陷阱时):如果被代理的目标本身不可构造,那么new Proxy(target, {})会抛出TypeError。 - class 的方法(包括
static方法):类方法不具备[[Construct]]。
可以通过 Reflect.construct 来试探一个函数是否可构造:
js
function isConstructable(fn) {
try {
// 这里将 fn 作为 new.target 传入,
// 如果 fn 不可构造,Reflect.construct 会抛出 TypeError
Reflect.construct(Object, [], fn);
return true;
} catch {
return false;
}
}手写实现
根据上述的四步模型,可以用以下方式模拟 new 的行为:
js
function myNew(constructor, ...args) {
// 步骤 1+2:创建对象并设置原型
const obj = Object.create(constructor.prototype);
// 步骤 3:执行构造函数
const result = constructor.apply(obj, args);
// 步骤 4:处理返回值
return (typeof result === 'object' && result !== null) || typeof result === 'function'
? result
: obj;
}两个需要额外留意的细节:
- 返回值判断中必须包含
typeof result === 'function'。因为函数在 ECMAScript 中属于对象,如果构造函数显式返回了一个函数,new也必须返回该函数,而不是默认创建的那个普通对象。 - 当
constructor.prototype不是对象时,Object.create(constructor.prototype)会直接抛出TypeError,而原生new并不会因此报错。如果需要更贴近原生行为,可以预先检查prototype的类型,对非对象值降级使用Object.prototype:
js
function safeMyNew(constructor, ...args) {
const proto = (typeof constructor.prototype === 'object' && constructor.prototype !== null)
? constructor.prototype
: Object.prototype;
const obj = Object.create(proto);
const result = constructor.apply(obj, args);
return (typeof result === 'object' && result !== null) || typeof result === 'function'
? result
: obj;
}原始类型的自动装箱
new 可以显式创建包装对象,但在没有 new 的时候,引擎在访问原始值属性时也会触发临时装箱:
js
const a = 1;
a.__proto__; // 不报错,返回 Number.prototype背后的过程是:
- 属性访问运算符
.要求操作数是对象。 - 引擎将
a临时装箱为等值的new Number(1)。 - 读取
new Number(1).__proto__,得到Number.prototype。 - 该次访问结束后,临时包装对象即被回收。
undefined 和 null 没有对应的包装类型,因此访问它们的属性会直接抛出 TypeError。
用 new 显式创建的包装对象与字面量原始值处在不同的“类型”层面:
js
const a = 1; // typeof: 'number'
const b = new Number(1); // typeof: 'object'
console.log(a === 1); // true
console.log(b === 1); // false —— b 是对象
console.log(+a === +b); // true —— 通过隐式转换比较数值参考链接
- ECMAScript 规范:
[[Construct]]——13.2.2 [[Construct]] ( argumentsList, newTarget ) - ECMAScript 规范:
new.target——13.3 Meta Properties - V8 源码:
Builtins::kConstruct入口(builtins-construct.cc) - V8 源码:
Reflect.construct实现(builtins-reflect.cc)
