深入理解 JavaScript 中的 new:工作原理、边界与最佳实践

1. new 背后到底发生了什么?(规范语义视角)

当执行 new Constructor(...args) 时,大致等价于以下步骤:

  1. 创建一个全新的普通对象 ,并把它的内部 [[Prototype]] 指向 Constructor.prototype
  2. 新对象作为 this 调用 Constructor(...args),且设置 new.targetConstructor
  3. 如果构造函数显式返回一个对象(object/function),则返回该对象;否则返回步骤 1 创建的新对象。

2. 构造函数与严格模式:遗漏 new 的后果

非严格模式 下,遗漏 new 会把 this 绑定到全局对象 (浏览器为 window),导致"把属性挂到全局"的隐蔽 Bug;在严格模式 下,thisundefined,对其赋值会直接抛错

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. 强制要求使用 newnew.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,也可直接调用 (两者结果等价或相近):ObjectArrayFunctionRegExpDate(但 Date()new Date() 不等价 ,见下)、ErrorMap/Set/WeakMap/WeakSet(注意:这些通常仍需 new 才能有实例)。
  • 包装对象的陷阱new Number(5)new String('x')new Boolean(false) 返回对象而非基本类型,可能导致布尔判断异常。
  • 彻底不能 newSymbolBigInt(它们是函数/语法,但不可作为构造器使用)。
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. newbind/call/apply 的交互:this 绑定被忽略

当你对构造函数做了 bind,再用 new 调用时,this 会指向新实例 ,而不是 被绑定的对象;但通过 bind 预置的参数仍然生效call/apply 也无法改变 newthis

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 混用时,理解 newthis 绑定具有更高优先级。
  • ⚠️ 不能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 bindnew 的优先级

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
相关推荐
weixin_382395232 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__2 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.2 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~3 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华4 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子6 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅6 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni7 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学8 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端