实现 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表达式总能返回一个对象。
- 我们检查