实现 new 操作符
基础知识
在 JavaScript 中,new
不是一个函数,而是一个操作符。它用于创建一个用户自定义的对象类型的实例或具有构造函数的内置对象的实例。当我们执行 let instance = new Constructor(arg1, arg2)
时,new
操作符在背后自动完成了一系列复杂的步骤。
要手动实现一个模拟 new
功能的函数,我们必须首先理解 new
到底做了什么。这个过程大致可以分为四个核心步骤,这也是我们实现的基础。
构造函数 (Constructor): 任何一个普通的 JavaScript 函数,只要被
new
调用,它就充当了“构造函数”的角色。实例 (Instance): 通过
new
创建出来的对象,就是该构造函数的一个“实例”。this
上下文: 在构造函数内部,this
关键字会指向正在被创建的新实例对象。原型链接:
new
操作符会自动将新创建的实例对象的原型,链接到构造函数的prototype
对象上。
核心思路
我们的目标是创建一个函数,例如 myNew(Constructor, ...args)
,它能模拟原生 new
操作符的行为。这个模拟过程严格遵循 new
的四个内部步骤:
创建一个新的空对象:这个对象将成为最终返回的实例。
链接到原型:将这个新创建的空对象的原型,指向构造函数的
prototype
属性。这一步确立了实例与构造函数之间的原型链关系。绑定
this
并执行构造函数:将构造函数的this
指向这个新对象,并执行构造函数的代码(同时传入参数)。这样,构造函数内部对this
的所有赋值操作,实际上都是在对这个新对象进行赋值。返回新对象:在执行完构造函数后,需要判断其返回值。
- 如果构造函数没有显式
return
,或者return
了一个原始值(如string
,number
,boolean
,null
,undefined
),那么就自动返回我们第一步创建的新对象。 - 如果构造函数显式
return
了一个对象(包括function
,array
),那么new
操作的结果就是这个被返回的对象,而不是第一步创建的新对象。
- 如果构造函数没有显式
代码实现
下面是 myNew
的一个完整实现。
/**
* 自定义 new 操作符
* @param {Function} Constructor - 构造函数
* @param {...any} args - 传递给构造函数的参数
* @returns {Object} - 创建的实例对象
*/
function myNew(Constructor, ...args) {
// 1. 校验构造函数是否为函数类型
if (typeof Constructor !== 'function') {
throw new TypeError(
'myNew: The first argument must be a constructor function.'
);
}
// 2. 创建一个新对象,并将其原型链接到构造函数的 prototype
// Object.create() 完美地完成了这两步
const obj = Object.create(Constructor.prototype);
// 3. 将构造函数的 this 指向新创建的对象 obj,并执行构造函数
// 使用 apply 方法,将 args 数组作为参数传入
const result = Constructor.apply(obj, args);
// 4. 判断构造函数的返回值
// 如果返回值是一个对象或函数,则返回该返回值
if (
result !== null &&
(typeof result === 'object' || typeof result === 'function')
) {
return result;
}
// 否则,返回新创建的对象 obj
return obj;
}
代码解析
- 函数签名与校验:
myNew
函数接收的第一个参数必须是Constructor
构造函数,后续的参数使用 rest 参数语法...args
收集成一个数组,方便后续传递。开始时,我们校验Constructor
必须是一个函数,否则无法进行后续操作。 - 创建与链接(Object.create):
Object.create(proto)
方法会创建一个新对象,同时将这个新对象的原型设置为proto
。我们使用Object.create(Constructor.prototype)
,这一行代码极其优雅地完成了“创建新对象”和“链接到原型”这两个核心步骤。obj
在此时已经是一个空对象,但它的原型链已经正确设置好了。 - 执行构造函数(apply):为了将构造函数内部的
this
指向我们新创建的obj
,我们使用apply
方法。Constructor.apply(obj, args)
的意思是:“请立即执行Constructor
函数,在执行时,函数内部的this
给我绑定为obj
对象,同时把args
数组里的所有元素作为参数传进去。”执行后,obj
就被赋予了构造函数中定义的属性(例如this.name = 'xxx'
)。我们将构造函数的执行结果保存在result
变量中。 - 判断并返回结果:这是
new
操作符一个非常关键的特性。- 我们检查
result
是否是一个对象(注意typeof null
也是'object'
,所以要排除null
)或者一个函数。 - 如果是,根据
new
的规则,我们必须返回这个由用户显式返回的result
对象。 - 如果不是(即
result
是undefined
,null
, 或原始值),我们就忽略它,并返回我们最初创建和修改的obj
对象。这确保了new
表达式总能返回一个对象。
- 我们检查