面试官真的会问:new 的实现以及无 new 实例化
Category(分类): JavaScript Status: 未知
配图来自原文页面,已下载并转换为本地 WebP;原始授权未核实,发布前请人工确认。
面试中经常会从“构造函数显式 return”追问到 new 的实现,以及如何设计一个不要求调用者书写 new 的工厂 API。本文保留原文的推导和 jQuery 设计思路,并补充现代标准 API 和实现边界。
new 实例化做了什么
对普通构造函数来说,new Constructor(...args) 大致会经历以下步骤:
- 创建一个新对象。
- 如果构造函数的
prototype是对象,把新对象的[[Prototype]]指向它。 - 执行构造函数,把新对象作为
this,并传入参数。 - 如果构造函数返回非原始值,就返回该值;否则返回新对象。
下面的代码可以观察其中的前三步:
function Test() {
console.log(JSON.stringify(this))
console.log(Object.getPrototypeOf(this).constructor === Test)
this.name = 'jack'
this.age = 18
console.log(JSON.stringify(this))
}
const value = new Test()
console.log(value.name) // jack
console.log(value.age) // 18
构造函数的 this 在执行过程中指向新对象;原型连接也在函数体执行前准备好了。__proto__ 可以用于观察,但新代码建议使用 Object.getPrototypeOf。
实现一个简化版 new
下面的版本适用于普通函数构造器:
function myNew(Constructor, ...args) {
if (typeof Constructor !== 'function') {
throw new TypeError('Constructor must be a function')
}
const prototype = Constructor.prototype
const instance = Object.create(
prototype !== null && (typeof prototype === 'object' || typeof prototype === 'function')
? prototype
: Object.prototype
)
const result = Reflect.apply(Constructor, instance, args)
const isObject = result !== null && (typeof result === 'object' || typeof result === 'function')
return isObject ? result : instance
}
验证参数、原型和返回值:
function Test(name, age) {
this.name = name
this.age = age
}
Test.prototype.describe = function () {
return `${this.name}:${this.age}`
}
const value = myNew(Test, '小明', 18)
console.log(value.describe()) // 小明:18
console.log(value instanceof Test) // true
原文中只写 func.call(obj, ...args); return obj,忽略了构造函数返回对象的情况。完整教学实现必须保留对象/函数返回值;但它仍不是原生 new 的完全等价物:class 不能用 Reflect.apply 调用,Proxy、跨 realm 和 new.target 等场景需要 Reflect.construct。
生产代码通常直接使用原生语法,元编程场景可以使用:
function User(name) {
this.name = name
}
const value = Reflect.construct(User, ['Ada'])
console.log(value.name) // Ada
构造函数显式 return
如果构造函数返回对象,这个对象会覆盖 new 创建的实例。对象不仅包括普通对象,也包括数组、日期、函数和 null 原型对象:
function ReturnObject() {
this.name = 'jack'
this.age = 18
return {
content: '我有 freestyle'
}
}
const objectResult = new ReturnObject()
console.log(objectResult) // { content: '我有 freestyle' }
console.log(objectResult.name) // undefined
如果返回原始值,则原始值会被忽略:
function ReturnPrimitive() {
this.name = 'jack'
this.age = 18
return '我有 freestyle'
}
const primitiveResult = new ReturnPrimitive()
console.log(primitiveResult.name) // jack
console.log(primitiveResult instanceof ReturnPrimitive) // true
如果构造函数返回函数,也会采用这个函数,因为函数也是非原始值:
function ReturnFunction() {
return function result() {
return 'function result'
}
}
const fn = new ReturnFunction()
console.log(fn()) // function result
无 new 实例化
“无 new 实例化”通常是 API 设计层面的说法:调用者不需要写 new,但函数内部仍可能使用 new 或其他构造机制。jQuery 就采用过类似的工厂入口:
function Shadow() {
this.name = 'jack'
this.age = 18
}
function myQuery() {
return new Shadow()
}
const object1 = myQuery()
const object2 = new myQuery()
console.log(object1 instanceof Shadow) // true
console.log(object2 instanceof Shadow) // true
console.log(object1.name) // jack
console.log(object2.name) // jack
myQuery() 和 new myQuery() 在这个例子中都得到 Shadow 实例,是因为 myQuery 显式返回了 new Shadow() 的对象。它们的调用语义并不完全相同:普通调用不创建 myQuery 实例,构造调用会先创建临时实例,然后被显式返回对象覆盖。
现代项目中也常见以下几种不要求 new 的方案:
- 工厂函数:直接返回对象或 class 实例。
- 静态
create()方法:把创建过程命名出来。 - 模块函数:返回带有私有闭包状态的 API。
- class:明确要求
new,让错误在调用时尽早暴露。
jQuery 的原型转接思路
jQuery 1.x 的入口大致是下面的结构(为便于学习进行了删减):
const $ = function (selector, context) {
return new $.fn.init(selector, context)
}
$.fn = $.prototype = {
constructor: $,
init(selector, context) {
this.selector = selector
this.context = context
},
text() {
return this.selector
}
}
// 取出 init 函数并挂回 fn,模拟 jQuery.fn.init 的结构。
$.fn.init = function (selector, context) {
this.selector = selector
this.context = context
}
// 关键:让 new $.fn.init() 创建的对象也能访问 $.prototype 上的方法。
$.fn.init.prototype = $.fn
const value = $('<div>123</div>')
console.log(value instanceof $) // true
console.log(value instanceof $.fn.init) // true
console.log(value.text()) // <div>123</div>
原文的核心代码是:
jQuery = function (selector, context) {
return new jQuery.fn.init(selector, context)
}
jQuery.fn = jQuery.prototype = {
// 原型方法和其他成员
}
jQuery.fn.init = function (selector, context) {
// 创建实例的具体逻辑
}
jQuery.fn.init.prototype = jQuery.fn
jQuery.fn 是 jQuery.prototype 的别名,主要为了书写简洁。真正关键的是 init.prototype = jQuery.fn:这样 new init() 产生的对象的原型就是 jQuery.prototype,从而可以使用 jQuery 的原型方法,并同时满足 instanceof jQuery 的判断。

这种设计可以抽象成一个最小模块:
function myModule(params) {
return new myModule.fn.init(params)
}
myModule.fn = myModule.prototype = {
constructor: myModule,
getValue() {
return this.value
}
}
myModule.fn.init = function (params) {
this.value = params
}
myModule.fn.init.prototype = myModule.fn
const moduleValue = myModule('value')
console.log(moduleValue.getValue()) // value
console.log(moduleValue instanceof myModule) // true
instanceof 与 new.target
通过 instanceof 判断“是否使用 new”并不可靠,因为调用者可以把一个真实实例传给普通函数:
function Student(name) {
if (this instanceof Student) {
this.name = name
return
}
throw new Error('必须通过 new 关键字来调用 Student')
}
const student = new Student('若')
// student 已经是 Student 实例,下面的普通调用会绕过这个检查。
const result = Student.call(student, '川')
console.log(result) // undefined
console.log(student.name) // 川
new.target 可以区分普通调用与构造调用:
function Student2(name) {
if (new.target !== undefined) {
this.name = name
return
}
throw new Error('必须通过 new 关键字来调用 Student2')
}
const student2 = new Student2('若')
console.log(student2.name) // 若
try {
Student2.call(student2, '川')
} catch (error) {
console.log(error.message) // 必须通过 new 关键字来调用 Student2
}
new.target 在直接普通调用时是 undefined,在 new 调用中是构造目标。它比 instanceof 更适合检查调用方式,但在 Reflect.construct 显式指定 newTarget 的元编程场景中,仍应按具体设计理解。
现代实践
- 能用工厂函数表达的创建过程,不必为了形式强行使用构造函数。
- class 的构造器不能脱离
new调用;需要“可调用也可构造”时,显式提供工厂入口。 - 读取原型使用
Object.getPrototypeOf,创建继承对象使用Object.create,不要把__proto__赋值当作通用方案。 - 手写
new适合面试和理解规范,生产代码优先使用new或Reflect.construct。

参考资料
作者:程序员白彬
链接:https://juejin.cn/post/6850037282319204360
来源:稀土掘金。著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。