← 返回题目列表

如何手写 new 操作符?

高频 中等 第 8 / 27 题 更新于 2026/07/28
手写代码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.targetundefined,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.targetundefined。构造函数可用它禁止无 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 和默认实例原型,常用于解释 extendssuper() 背后的构造转发。

六、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、nullundefined 都忽略。
  • 误区: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。

八、加强记忆

  1. 分配:创建实例,prototype 非对象时回退到默认对象原型。
  2. 构造:真正的 new 使用构造语义,函数内部能看到 new.target
  3. 返回:对象和函数覆盖默认实例,所有原始值都忽略。
  4. 近似Object.create + apply 只适用于传统普通函数教学。
  5. 准确:class、内建对象、绑定构造器应交给 Reflect.construct
  6. 判断:可调用不等于可构造,typeof === 'function' 不是充分条件。