JS new操作原理及手写函数模拟实现示例
本文深入解析JavaScript中new操作符的执行原理,包括创建对象、绑定原型、绑定this及返回结果四个步骤。通过v1至v5五个版本的手写实现,逐步完善对参数类型校验、Object.setPrototypeOf替代__proto__、ES6剩余参数及new.target检测的支持,提供完整的模拟代码。
引言
在 ES6 引入之前,即处于 ES5 阶段时,JavaScript 语言中类的表现形式主要依赖于构造函数。
那么,JavaScript 中的构造函数具体具有什么特征?
- 构造函数本质上仍是一个普通函数,从外观形式上看,它与常规函数并无二致(唯一的惯例区别是构造函数名称的首字母通常大写);
- 然而,一旦一个普通函数被 操作符调用,该函数即被视为构造函数;
原理
当一个函数被 new 操作符调用时,引擎会执行以下一系列操作:
- 在内存中实例化一个全新的对象;
- 将该新对象内部的
[[Prototype]]属性设置为构造函数的prototype属性; - 将构造函数内部的
this绑定为该新对象(即确保this指向这个新对象); - 执行构造函数体内的代码逻辑(从而为新对象添加属性);
- 若构造函数显式返回一个非空对象,则返回该对象;否则,默认返回刚才创建的新对象;
手写函数模拟 new
基于上述原理,接下来我们将尝试手动编写一个函数,以模拟 new 操作符的核心功能。
v1 基本实现
我们首先利用 ES5 的基础语法来构建这一实现:
function useNewOperator() {
var constructor = arguments[0]
var args = [].slice.call(arguments, 1)
// 1. 在内存中创建一个新对象
var obj = {}
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
obj.__proto__ = constructor.prototype
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
var res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
// return undefined
// return null
// return {}
// return 123
// return ''
// return String(123)
// return new String(123)
// return { name: 'wy' }
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj
v2 考虑参数类型
上述基本实现虽然可以运行,但存在缺陷,即未对传入的第一个参数是否为函数类型进行校验。若传入非函数,在执行 constructor.apply(obj, args) 这行代码调用 constructor() 时将引发错误。因此,我们需要增加判断逻辑:若第一个参数不是函数,则直接抛出异常:
function useNewOperator() {
var constructor = arguments[0]
+
+ if (typeof constructor !== 'function') {
+ throw new TypeError('the first argument to useNewOperator function must be a function')
+ }
+
var args = [].slice.call(arguments, 1)
// 1. 在内存中创建一个新对象
var obj = {}
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
obj.__proto__ = constructor.prototype
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
var res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const obj = {}
const p2 = useNewOperator(obj, 'zhj', 20) // Uncaught TypeError: the first argument to useNewOperator function must be a function
console.log(p2);
p2.sayName()
v3 Object.prototype.__proto__ 的替代方案
在前面的实现中,我们将新对象内部的 [[Prototype]] 属性赋值为构造函数的 prototype 属性时,是通过修改 obj 上的 __proto__ 属性完成的(即使用了 Object.prototype.__proto__)。尽管这种方式可行,但官方并不推荐直接使用 Object.prototype.__proto__,更建议使用 Object.getPrototypeOf/Reflect.getPrototypeOf 或 Object.setPrototypeOf/Reflect.setPrototypeOf(参考链接:developer.mozilla.org/zh-CN/docs/…)。因此,我们进行如下修改:
function useNewOperator() {
var constructor = arguments[0]
if (typeof constructor !== 'function') {
throw new TypeError('the first argument to useNewOperator function must be a function')
}
var args = [].slice.call(arguments, 1)
// 1. 在内存中创建一个新对象
var obj = {}
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
+ Object.setPrototypeOf(obj, constructor.prototype)
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
var res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj
或者,我们还可以利用 Object.create() 方法直接指定原型来创建新对象:
function useNewOperator() {
var constructor = arguments[0]
if (typeof constructor !== 'function') {
throw new TypeError('the first argument to useNewOperator function must be a function')
}
var args = [].slice.call(arguments, 1)
// 1. 在内存中创建一个新对象
- var obj = {}
-
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
+ var obj = Object.create(constructor.prototype)
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
var res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj
v4 使用 ES6 语法实现
接下来,我们采用 ES6 语法(包括剩余参数(rest parameters)和 const 声明)来重新实现:
function useNewOperator(constructor, ...args) {
if (typeof constructor !== 'function') {
throw new TypeError('the first argument to useNewOperator function must be a function')
}
// 1. 在内存中创建一个新对象
const obj = {}
// 2. 这个新对象内部的 [[Prototype]] 指针被赋值为构造函数(constructor)的 prototype 属性
Object.setPrototypeOf(obj, constructor.prototype)
// 或者使用 Object.create() 直接指定原型创建新对象
// const obj = Object.create(constructor.prototype)
// 3. 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)
// 4. 执行构造函数内部的代码(给新对象添加属性)
const res = constructor.apply(obj, args)
// 5. 如果构造函数返回非空对象,则返回该对象;否则,返回刚才创建的新对象
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
// 测试
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name);
}
const p1 = new Person('zhj', 20)
console.log(p1); // Person {name: 'zhj', age: 20}
p1.sayName() // zhj
const p2 = useNewOperator(Person, 'zhj', 20)
console.log(p2); // Person {name: 'zhj', age: 20}
p2.sayName() // zhj
v5 考虑 ES6 的 new.target 检测
最后,还需考虑一个细节,即 ES6 新增的 new.target 属性。当通过 new 操作符调用构造方法或函数时,new.target 会返回一个指向该构造方法或函数的引用(参考链接:developer.mozilla.org/en-US/docs/…)。因此,我们可以利用 new.target 来检测函数或构造方法是否是通过 new 操作符调用的。为此,我们需要在自己实现的 useNewOperator 函数中添加相应的检测代码:
无注释版本
function useNewOperator(constructor, ...args) {
if (typeof constructor !== 'function') {
throw new TypeError('the first argument to useNewOperator function must be a function')
}
useNewOperator.target = constructor
const obj = {}
Object.setPrototypeOf(obj, constructor.prototype)
// const obj = Object.create(constructor.prototype)
const res = constructor.apply(obj, args)
if (res != null && (typeof res === 'object' || typeof res === 'function')) {
return res
}
return obj
}
以上,即为关于 new 操作背后的原理,以及手写函数模拟实现 new 操作过程的全部内容。更多关于JS new操作手写函数的资料请关注本站其它相关文章!
JavaScript异步发送HTTP请求时怎么优雅地处理错误
2026-11-05 17:10:44
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
