如何手写 new 操作符?
简化版
new Ctor(...args) 会创建对象,把其原型关联到构造器的 prototype,以构造方式执行函数,并根据构造器返回值决定最终结果:显式返回对象或函数就使用该值,返回原始值则忽略。常见 Object.create + apply 只能近似普通函数构造,类、内建构造器和 new.target 应使用 Reflect.construct。
详细版
普通函数的教学近似版:
function educationalNew(Ctor, ...args) {
if (typeof Ctor !== 'function') throw new TypeError('Ctor must be callable')
const prototype = Ctor.prototype
const instance = Object.create(
prototype !== null && (typeof prototype === 'object' || typeof prototype === 'function')
? prototype
: Object.prototype
)
const returned = Reflect.apply(Ctor, instance, args)
const isObject = returned !== null &&
(typeof returned === 'object' || typeof returned === 'function')
return isObject ? returned : instance
}
它能回答核心步骤,但不是完整替代:apply 调用时 new.target 是 undefined,class 构造器禁止普通调用,Map 等内建构造器也依赖真正的构造内部机制。准确的通用包装应写 Reflect.construct(Ctor, args)。
完整版教学
一、new 的四步模型要加边界
面试常说四步:创建对象、连接原型、绑定 this 执行、处理返回值。更精确地说:
| 阶段 | 关键规则 |
|---|---|
| 检查 | 目标必须可构造,不只是任意值 |
| 分配 | 创建新的普通对象或由构造器定义的实例 |
| 原型 | Ctor.prototype 是对象时使用它,否则回退到当前 realm 的 Object.prototype |
| 构造 | 以构造语义调用,函数内部可见 new.target |
| 返回 | 对象/函数返回值覆盖默认实例,原始值被忽略 |
Object.create(Ctor.prototype)加Ctor.apply(instance, args)只是在模拟可普通调用的传统函数。它执行的是普通调用而非构造调用,不能声称完整实现new。
二、普通函数近似版怎么写
function educationalNew(Ctor, ...args) {
if (typeof Ctor !== 'function') throw new TypeError('Ctor must be callable')
const candidate = Ctor.prototype
const prototype = candidate !== null &&
(typeof candidate === 'object' || typeof candidate === 'function')
? candidate
: Object.prototype
const instance = Object.create(prototype)
const returned = Reflect.apply(Ctor, instance, args)
const returnedObject = returned !== null &&
(typeof returned === 'object' || typeof returned === 'function')
return returnedObject ? returned : instance
}
使用 Object.create 比创建 {} 后再调用 Object.setPrototypeOf 更直接,也避免创建完成后改变原型带来的优化问题。
但这里只检查了“可调用”,没有办法用普通 JavaScript 的 typeof 可靠判断 [[Construct]];箭头函数可调用却不可构造,绑定函数可能可构造却没有自有 prototype。
三、构造器返回值规则
function A() {
this.value = 1
return 2
}
function B() {
this.value = 1
return { value: 2 }
}
function C() {
return function named() {}
}
new A().value // 1,原始值 2 被忽略
new B().value // 2,返回对象覆盖默认实例
new C() // 返回 named 函数
null 也属于原始值语义,会被忽略;函数是对象类别的一部分,会覆盖默认实例。只判断 typeof returned === 'object' 会漏掉函数,还要排除 null。
四、new.target 揭示近似版差异
function Probe() {
this.target = new.target
}
new Probe().target === Probe // true
educationalNew(Probe).target // undefined
Reflect.apply 是普通调用,所以 new.target 为 undefined。构造函数可用它禁止无 new 调用、实现抽象基类,或根据实际派生构造器调整行为,因此差异不是纯理论问题。
类构造器必须通过构造语义调用:
class User {}
educationalNew(User) // TypeError:class 不能由 apply 普通调用
五、通用答案是 Reflect.construct
允许使用标准反射 API 时,准确实现非常短:
function myNew(Ctor, ...args) {
return Reflect.construct(Ctor, args)
}
它会检查目标是否可构造,正确设置 new.target,处理 class、绑定构造函数和依赖内部槽的内建构造器,也会执行规范的返回值规则。
Reflect.construct 还能把“执行哪个构造器”和“使用谁的 prototype”分开:
function Parent(name) {
this.name = name
}
function Child() {}
const value = Reflect.construct(Parent, ['Ada'], Child)
Object.getPrototypeOf(value) === Child.prototype // true
第三个参数 newTarget 决定 new.target 和默认实例原型,常用于解释 extends 和 super() 背后的构造转发。
六、prototype 非对象与可构造性
function Weird() {}
Weird.prototype = 1
const value = new Weird()
Object.getPrototypeOf(value) === Object.prototype // true
原生 new 不会把数字 1 当原型,也不会直接让 Object.create(1) 抛出的错误冒充自身行为,而是回退到默认对象原型。
| 函数类别 | 可普通调用 | 可用 new |
|---|---|---|
| 普通函数 | 是 | 通常是 |
| class | 否 | 是 |
| 箭头函数 | 是 | 否 |
| async / generator 函数 | 是 | 否 |
| 绑定函数 | 是 | 取决于目标是否可构造 |
因此 typeof Ctor === 'function' 不是“可构造”的充分条件。真正执行 Reflect.construct 并让它抛出标准 TypeError,比维护一份易漏的函数类型名单可靠。
七、常见误区与追问
- 误区:
new只是把this指向一个空对象。 它使用构造内部机制,还涉及new.target、原型选择和返回值覆盖。 - 误区:构造器只要显式 return 就覆盖实例。 只有对象或函数返回值覆盖,数字、字符串、布尔值、Symbol、BigInt、
null、undefined都忽略。 - 误区:
typeof Ctor === 'function'就一定能new。 箭头、async、generator 函数不可构造,而 class 可构造却不能普通 apply。 - 追问:为什么不用
{}再修改原型?Object.create可在创建时指定原型,更直接,也避免事后改变原型。 - 追问:Ctor.prototype 是原始值怎么办? 原生构造会回退到当前 realm 的默认
Object.prototype。 - 追问:如何支持 class 和内建构造器? 使用
Reflect.construct,不要用apply伪装构造调用。 - 追问:
Reflect.construct第三个参数有什么用? 它指定 newTarget,从而决定函数里的new.target以及默认实例使用的 prototype。
八、加强记忆
- 分配:创建实例,prototype 非对象时回退到默认对象原型。
- 构造:真正的 new 使用构造语义,函数内部能看到
new.target。 - 返回:对象和函数覆盖默认实例,所有原始值都忽略。
- 近似:
Object.create + apply只适用于传统普通函数教学。 - 准确:class、内建对象、绑定构造器应交给
Reflect.construct。 - 判断:可调用不等于可构造,
typeof === 'function'不是充分条件。