1. new 背后到底发生了什么?(规范语义视角)
当执行 new Constructor(...args) 时,大致等价于以下步骤:
- 创建一个全新的普通对象 ,并把它的内部
[[Prototype]]指向Constructor.prototype。 - 用新对象作为
this调用Constructor(...args),且设置new.target为Constructor。 - 如果构造函数显式返回一个对象(object/function),则返回该对象;否则返回步骤 1 创建的新对象。
2. 构造函数与严格模式:遗漏 new 的后果
在非严格模式 下,遗漏 new 会把 this 绑定到全局对象 (浏览器为 window),导致"把属性挂到全局"的隐蔽 Bug;在严格模式 下,this 为 undefined,对其赋值会直接抛错。
js
'use strict'; // 开启严格模式,避免 this 指向全局
function Person(name) { // 声明一个构造函数(可被 new 调用)
this.name = name; // 期望把 name 放到"新对象实例"的 this 上
}
const a = new Person('Ankit'); // 正确:使用 new,创建实例
console.log(a.name); // 输出 "Ankit"
// const b = Person('Ankit'); // 错误示例:未使用 new,在严格模式下会抛 TypeError
3. 强制要求使用 new:new.target 与回退模式
你可以在构造函数内通过 new.target 检查是否由 new 调用;若未使用 new,可自动补救 或显式抛错。
js
function User(name) { // 声明构造函数
if (!new.target) return new User(name); // 若未用 new,则递归调用补上 new
this.name = name; // 继续正常初始化
}
const u1 = new User('Ada'); // 正确用法
const u2 = User('Grace'); // 漏写 new 也能得到实例(自动补救)
console.log(u1 instanceof User, u2 instanceof User); // 两者都为 true
若你担心继承场景(子类
extends)或想要显式约束,也可改为抛错:if (!new.target) throw new Error('Must use new');
4. 类(class)必须配合 new 使用
类声明天生不可作为普通函数调用,否则会直接抛错。
js
class Car { // 声明一个类
constructor(brand) { // 构造器,new 时被调用
this.brand = brand; // 在实例上记录品牌
} // 构造器结束
} // 类定义结束
const c1 = new Car('Tesla'); // 正确:使用 new
console.log(c1.brand); // 输出 "Tesla"
// const c2 = Car('BYD'); // 错误:类不能不带 new 调用,会抛 TypeError
5. 构造函数的返回值规则(再次严谨化)
return 对象/函数→ 覆盖默认返回的新实例。return 基本类型(string/number/boolean/symbol/bigint/undefined/null)→ 被忽略,仍返回新实例。
js
function Test() { // 声明构造函数
this.value = 10; // 给新实例设置属性
return { msg: 'I override!' }; // 显式返回对象,覆盖默认实例
}
const t = new Test(); // 使用 new
console.log(t); // 输出 { msg: 'I override!' }
6. 哪些可以 new,哪些不行?(内置对象全景与差异)
- 既可
new,也可直接调用 (两者结果等价或相近):Object、Array、Function、RegExp、Date(但Date()与new Date()不等价 ,见下)、Error、Map/Set/WeakMap/WeakSet(注意:这些通常仍需new才能有实例)。 - 包装对象的陷阱 :
new Number(5)、new String('x')、new Boolean(false)返回对象而非基本类型,可能导致布尔判断异常。 - 彻底不能
new:Symbol、BigInt(它们是函数/语法,但不可作为构造器使用)。
js
// 1) Date 的差异
console.log(typeof Date()); // "string":直接调用返回"当前时间"的字符串
console.log(Date() instanceof Date); // false:仅是字符串而非 Date 实例
console.log(new Date() instanceof Date); // true:使用 new 才得到 Date 实例
// 2) 包装对象陷阱
const x = new Boolean(false); // 这是一个 Boolean 对象
if (x) console.log('会执行'); // 会执行:对象在条件判断中为真
const y = Boolean(false); // 这是基本类型 false
if (y) console.log('不会执行'); // 不会执行:基本类型 false 为假
// 3) 不能 new 的类型
// new Symbol('id'); // TypeError:Symbol 不能作为构造器
// new BigInt(10); // TypeError:BigInt 不能作为构造器
7. 谁"能被 new"?------可构造(constructible)与箭头函数限制
- 并不是所有函数都能被
new:箭头函数 、类的原型方法 、某些内建函数 都不可构造。 - 能不能被
new,取决于其是否具有 [[Construct]] 内部方法。
js
const Arrow = () => {}; // 声明箭头函数
// new Arrow(); // TypeError:箭头函数不可被构造
class A { // 声明类 A
m() {} // 这是原型方法,不是构造器
}
// new A.prototype.m(); // TypeError:方法不可被构造
8. instanceof 与原型链、Symbol.hasInstance
a instanceof Ctor 的判断逻辑:沿着 a 的 [[Prototype]] 链向上找,是否能找到 Ctor.prototype。你还可以通过静态的 Symbol.hasInstance 自定义 instanceof 的行为。
js
function Ctor() {} // 声明构造函数
const obj = new Ctor(); // 基于 Ctor.prototype 构造实例
console.log(Object.getPrototypeOf(obj) === Ctor.prototype); // true:原型指向匹配
console.log(obj instanceof Ctor); // true:instanceof 也为真
class Range { // 自定义类
static [Symbol.hasInstance](x) { // 自定义 instanceof 判断逻辑
return typeof x === 'number' && x >= 0 && x <= 100; // 规定:0~100 的数字算"实例"
} // 自定义结束
} // 类结束
console.log(50 instanceof Range); // true:满足自定义规则
console.log(200 instanceof Range); // false:不在范围内
9. new 与 bind/call/apply 的交互:this 绑定被忽略
当你对构造函数做了 bind,再用 new 调用时,this 会指向新实例 ,而不是 被绑定的对象;但通过 bind 预置的参数仍然生效 。call/apply 也无法改变 new 的 this。
js
function Foo(x, y) { // 声明构造函数
this.sum = x + y; // 在实例上记录两个参数的和
}
const obj = {}; // 一个无关对象
const Bound = Foo.bind(obj, 1); // 绑定 this 为 obj,并预置第一个参数为 1
const ins = new Bound(2); // 使用 new:this 指向新实例,而不是 obj
console.log(ins.sum); // 3:预置参数 1 与传入参数 2 生效
console.log(ins instanceof Foo); // true:原型链正常
console.log(obj.sum); // undefined:obj 并未被写入
10. Object.create vs new:何时选择哪个?
new Ctor():会执行构造函数 逻辑,并把[[Prototype]]设为Ctor.prototype。Object.create(proto):只 创建一个以proto为原型的对象,不执行任何构造逻辑 。适合你只想要某个原型,而不需要副作用或初始化过程的场景。
js
function Person(name) { // 声明构造函数
this.name = name; // 初始化副作用:写入实例属性
}
const a = new Person('Ada'); // 使用 new:会执行构造逻辑
const b = Object.create(Person.prototype); // 只建立原型链,不执行构造逻辑
console.log(a.name); // "Ada":a 有 name
console.log(b.name); // undefined:b 没有被初始化
11. Reflect.construct:构造的高级玩法(改变 new.target)
Reflect.construct(Target, args, NewTarget) 允许你像 new 一样构造 Target,但把 new.target 设为 NewTarget,这在继承与"混入"工厂里很有用。
js
function Base() { // 基类构造
this.baseNewTarget = new.target; // 记录调用时的 new.target
}
function Mid() {} // 中间类(仅示意)
const obj = Reflect.construct(Base, [], Mid); // 用 Mid 作为 new.target 构造 Base
console.log(obj.baseNewTarget === Mid); // true:new.target 被定制为 Mid
console.log(Object.getPrototypeOf(obj) === Base.prototype); // true:原型仍指向 Base.prototype
12. 常见陷阱与最佳实践清单
- ✅ 在构造函数/类中始终使用
new;构造函数内使用new.target防止误用。 - ✅ 避免
new Boolean/Number/String带来的真假判断混乱;更倾向 使用无new的转换函数(Boolean/Number/String)。 - ✅ 需要无副作用、仅定制原型时,考虑
Object.create。 - ✅ 自定义
instanceof行为时谨慎使用Symbol.hasInstance,保持语义清晰。 - ✅ 和
bind/call/apply混用时,理解new对this绑定具有更高优先级。 - ⚠️ 不能 对
Symbol/BigInt使用new。 - ⚠️ 箭头函数、原型方法不可构造,不要 对它们用
new。 - ⚠️
Date()与new Date()行为不同:前者给你字符串,后者给你Date实例。
13. 参考示例总览(逐行中文注释)
13.1 构造函数 + 严格模式 + 自动补救
js
'use strict'; // 开启严格模式,避免 this 误指向全局
function Person(name) { // 声明构造函数 Person
if (!new.target) return new Person(name); // 若未用 new 调用,自动补上 new
this.name = name; // 在实例上记录 name
} // 构造函数结束
const p1 = new Person('Lin'); // 标准用法:使用 new
const p2 = Person('He'); // 误用:未使用 new,但被自动补救
console.log(p1.name, p2.name); // 输出 "Lin He"
13.2 类构造与错误示范
js
class Car { // 声明类 Car
constructor(brand) { // 定义构造器
this.brand = brand; // 初始化实例字段 brand
} // 构造器结束
} // 类定义结束
const c = new Car('NIO'); // 正确:必须使用 new
console.log(c.brand); // 输出 "NIO"
// Car('XPeng'); // 错误:类不允许不带 new 调用,会抛 TypeError
13.3 返回对象覆盖实例
js
function Overrider() { // 声明构造函数
this.ok = true; // 默认给实例打个标记
return { replaced: true }; // 返回对象,覆盖默认实例
} // 构造函数结束
const o = new Overrider(); // 使用 new
console.log(o); // 输出 { replaced: true },说明覆盖成功
13.4 内置对象差异与包装类型陷阱
js
console.log(typeof Date()); // "string":直接调用返回日期字符串
console.log(new Date() instanceof Date); // true:使用 new 才得到 Date 实例
const nb = new Boolean(false); // Boolean 包装对象,始终为真(对象即真值)
if (nb) console.log('会执行'); // 会执行:对象在条件判断中为真
const pb = Boolean(false); // 基本类型 false
if (pb) console.log('不会执行'); // 不会执行:基本类型 false 为假
13.5 箭头函数与方法不可构造
js
const F = () => {}; // 箭头函数
// new F(); // TypeError:箭头函数没有 [[Construct]]
class T { // 声明类 T
m() {} // 原型方法 m
} // 类结束
// new T.prototype.m(); // TypeError:方法也不可构造
13.6 bind 与 new 的优先级
js
function Add(x, y) { // 声明构造函数
this.sum = x + y; // 累加并保存到实例
} // 构造函数结束
const other = {}; // 准备一个不相关对象
const BAdd = Add.bind(other, 10); // 绑定 this(会被 new 覆盖),并预置 x=10
const ins = new BAdd(5); // 使用 new:this 指向新实例,而非 other
console.log(ins.sum); // 15:预置参数 10 + 传入参数 5
console.log(other.sum); // undefined:other 未被污染
13.7 Object.create 仅设原型,不执行构造
js
function Profile(name) { // 声明构造函数
this.name = name; // 赋值到实例属性
} // 构造函数结束
const p = Object.create(Profile.prototype); // 基于原型创建对象,但不调用构造函数
console.log(Object.getPrototypeOf(p) === Profile.prototype); // true:原型无误
console.log(p.name); // undefined:未运行构造逻辑,因此无 name
13.8 Reflect.construct 自定义 new.target
js
function Base() { // 声明构造函数
this.nt = new.target; // 记录构造时的 new.target
} // 构造函数结束
function N() {} // 声明另一个构造函数,用作 new.target
const r = Reflect.construct(Base, [], N); // 以 N 作为 new.target 构造 Base
console.log(r.nt === N); // true:new.target 已被定制
console.log(r instanceof Base); // true:仍然是 Base 的实例
14. 一句话速查表(Cheat Sheet)
- 用构造函数/类创建实例 → 务必用
new。 - 在构造函数里用
new.target防漏用;必要时抛错 或自动补救。 - 需要仅设原型且无副作用 → 用
Object.create(proto)。 - 避免
new Boolean/Number/String带来的真假与类型混乱。 - 箭头函数、方法不可构造;
Symbol/BigInt不能new。 Date()(字符串)≠new Date()(实例)。- 想要更灵活的构造流程(控制
new.target)→Reflect.construct。