先记住这个答案
执行 new 时,先创建一个新对象并链接原型,然后以它为 this 调用构造函数。若构造函数返回一个对象(非 null 的函数、数组、正则、对象字面量等),则 new 的最终结果是这个返回对象,this 对象被丢弃。若返回原始值或没有 return,则 new 结果仍是新建的 this 对象。null 是原始值,因此也被忽略。
- 返回对象则覆盖 new 创建的实例
- 返回原始值(含 null)则被忽略
- class 构造器也能返回对象覆盖 this
new 执行流程中的返回值判定
new 操作符内部先创建一个普通对象,将该对象的 [[Prototype]] 指向构造函数的 prototype 属性(若 prototype 是对象)。然后以该新对象为 this 调用构造函数。执行完毕后,JavaScript 检查构造函数的返回值类型:若返回一个对象(typeof 为 'object' 且不为 null,或 typeof 为 'function'),则整个 new 表达式的结果就是这个返回值;若返回原始类型(number、string、boolean、null、undefined、symbol、bigint),则丢弃返回值,返回之前创建的 this 对象。
这个规则适用于普通函数和 ES6 class 的 constructor。class 构造器若显式返回对象,也会覆盖 this,虽然 class 必须用 new 调用。值得注意的是,返回一个函数或数组也属于对象,因此同样会覆盖。若返回 null,因为 typeof null 是 'object' 但规范明确将 null 视为原始值,所以 new 的结果仍是 this。
function MakeObject(flag) {
this.name = 'this对象';
if (flag) {
return { name: '返回对象' };
}
return '原始值';
}
const a = new MakeObject(true);
const b = new MakeObject(false);
console.log(a.name); // 返回对象
console.log(b.name); // this对象
class C {
constructor() {
this.x = 1;
return { y: 2 }; // 覆盖 this
}
}
const c = new C();
console.log(c.x); // undefined
console.log(c.y); // 2查看输出与解释
返回对象
this对象
undefined
2MakeObject 返回对象时 new 得到该对象;返回字符串时 new 得到 this。class 构造器返回对象同样覆盖 this,导致 this.x 不存在。
单例模式:用返回对象强制复用实例
假设我们需要一个配置管理器,整个应用只允许一个实例。可以用构造函数返回已有的实例:在函数内部用一个私有变量保存实例,每次调用构造时若已存在则返回该实例,否则创建新对象并赋值给私有变量。这样即使有人多次使用 new ConfigManager(),得到的也是同一个对象,因为构造函数显式返回了那个已存在的对象。
这种模式要求构造函数能区分是否已有实例,但注意它改变了 new 的常规语义:每次 new 产生的并不是新建的对象。缺点是一旦有人修改了返回对象上的属性,会影响所有引用者。更好的做法是用 Symbol 或静态属性保存实例,但这里的关键是利用“返回对象即覆盖”的特性。处理时还需防范实例为 undefined 或 null 的情况,因为 null 会被忽略。
let instance = null;
function ConfigManager(options) {
if (instance !== null) {
return instance;
}
instance = this;
this.options = options;
}
const c1 = new ConfigManager({ debug: true });
const c2 = new ConfigManager({ debug: false });
console.log(c1 === c2); // true
console.log(c1.options.debug); // true
console.log(c2.options.debug); // true查看输出与解释
true
true
true第二次 new 时 instance 已存在,构造函数返回 instance,使 c2 与 c1 相同。该代码可在浏览器或 Node 中运行,无副作用。
边界:返回的对象不能是 null 或原始值的包装对象?
一个常见误区是认为返回包装对象如 new Number(3) 也能覆盖,其实可以,因为包装对象是对象。但返回 null 时 typeof 是 'object',却仍按原始值处理,所以 new 结果仍是 this。另一个边界是如果构造函数的 prototype 属性不是对象(例如被改为基本类型),new 创建的新对象的原型会变为 Object.prototype,但返回值规则不变。
对 class 而言,class 构造器不能返回原始值,但可以返回对象,这同样会覆盖 this。此外,若构造函数返回的对象与 this 无关,则后续指向 this 的操作(如给 this 添加属性)都不会生效,因为 this 已被丢弃。实际判断时建议用 typeof 检查返回值,但必须排除 null;更精确的是用 Object(value) === value 判断是否为对象。
容易答错的地方
- 认为返回 null 会被当成对象覆盖
- 虽然 typeof null 是 'object',但 ECMAScript 规范明确将 null 归为原始值,因此构造函数 return null 时,new 仍返回新建的 this,而不是 null。判断时应使用严格对象检查:value !== null && (typeof value === 'object' || typeof value === 'function')。
- 以为 class 构造器不能返回对象
- ES6 class 的构造器在 new 时同样遵循返回值规则:若返回对象,则 new 的结果是该对象,this 会被丢弃。许多开发者误以为 class 构造器必须返回 undefined 或 this,实际上返回对象是合法的,只是很少使用。示例:class A { constructor() { return {}; } } 得到空对象。
面试官还会怎么问?
箭头函数可以用作构造函数吗?
不能,箭头函数没有 [[Construct]] 内部方法,使用 new 会抛出 TypeError。同样,对象方法简写(method() {})也不可构造,因为其没有 constructor 行为。只有普通函数和 class 才具备构造能力。
new.target 与返回值判断有什么关系?
new.target 可以检测函数是否被 new 调用,但它与返回值无关。即使构造函数返回对象,new.target 仍指向该构造函数。若要避免误用 new,可结合 new.target 和返回值规则设计更安全的工厂函数。
如果返回一个函数,new 的结果是什么?
因为函数也是对象,返回函数时 new 表达式的结果就是该函数对象,而不是 this。例如 function F() { return function(){}; },new F() 得到一个函数,且该函数的 prototype 不会自动设置。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。