深入理解 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
相关推荐
Juchecar9 分钟前
npm、pnpm、yarn 是什么?该用哪个?怎么用?如何迁移?
前端·node.js
CYRUS_STUDIO12 分钟前
Miniconda 全攻略:优雅管理你的 Python 环境
前端·后端·python
学不动学不明白13 分钟前
ECharts 为visualMap视觉映射添加自适应外边框
前端
怪可爱的地球人17 分钟前
ts的高级类型
前端
支撑前端荣耀18 分钟前
优雅的Git提交:用Husky为你的项目加上提交约束
前端·javascript
支撑前端荣耀19 分钟前
前端CI/CD深度实践:从代码提交到自动化部署的专业化流水线
前端
林太白39 分钟前
npm多组件发布Vue3+TS版本,快来像Antd一样搭建属于你的UI库吧
前端·javascript·node.js
Juchecar1 小时前
如何避免Node.js项目node_modules重复占用空间
前端
百罹鸟1 小时前
nestjs 从零开始 一步一步实践
前端·node.js·nestjs