这是JS 原生方法原理探究系列的第三篇文章。本文会介绍如何模拟实现 new 操作符。关于 new 的具体用法,MDN 已经描述得很清楚了,这里我们只做简单的介绍,具体的重点在于如何模拟实现。
下面展示的所有规范都是 ES5 版本的,与现在最新的规范有些区别
首先看一下根据规范的描述, new 操作符做了什么事:

全是英文,不过没关系,我简单翻译一下:
我在使用 new 操作符的时候,后面跟着的构造函数可能带参数,也可能不带参数,如果不带参数的话,比如说 new Fn(),那么这里这个 Fn 就是一个 NewExpression;如果带参数,比如说 new Fn(name,age),那么这里的 Fn 就是一个 MemberExpression。
这两种情况下使用 new 操作符所进行的操作有点点不同,这里拿带参数的情况说明一下:
Fn 这个 MemberExpression 求值,其结果是指向实际函数对象的一个引用,我们把这个引用作为 refGetValue(ref) 进行求值,得到实际的函数对象,把这个对象作为 constructorArguments 也就是传进来的参数求值,得到一个参数列表,作为 argListconstructor 不是对象,则抛出类型错误constructor 没有实现内部的 [[Constructor]] 方法,也抛出类型错误constructor 的 [[Constructor]]方法,并将 argList 传入作为参数,返回调用结果从这些描述可以看出,更多的实现细节放在函数的 [[Constructor]] 方法里。那么这个方法具体是做什么用的呢?
[[Constructor]] 的规范在 JS 中,函数有两种调用方式,一种是正常调用,这将调用函数的内部方法 [[Call]],还有一种是通过 new 调用,此时的函数作为一个构造函数,这将调用函数的另一个内部方法 [[Consturct]]。所以,要实现 new 操作的话,我们得先搞懂 [[Construct]] 内部方法做了什么事。
这里继续看规范是怎么说的:

简单翻译一下:
当通过可能为空的参数列表调用函数 F 的内部方法 [[Construct]] 的时候,会执行如下步骤:
obj 作为一个新创建的原生对象obj 设置所有内部方法obj 的内部属性 [[Class]] 设置为 Objectprototype 调用函数 F 的内部方法 [[Get]],获取函数的原型对象,作为 protoproto 是对象,则将 obj 的内部属性 [[Prototype]] 设置为 protoproto 不是对象,则将 obj 的内部属性 [[Prototype]] 设置为标准内建的 Object 的原型对象F 的内部方法 Call, obj 作为调用时的 this 值,此前传给 [[Construct]] 的参数列表作为调用时的参数。将调用后得到的结果作为 resultresult 是对象,则将其返回obj可以说,规范已经讲得很清楚了,简单地说,在 new 一个构造函数的时候,具体会做下面的事情:
__proto__ 等于构造函数的 prototype__proto__ 等于 Object 的 prototypeES3 版本的实现如下:
function myNew(Fn){
if(typeof Fn != 'function'){
throw new TypeError(Fn + 'is not a constructor')
}
myNew.target = Fn
var instance = {}
// 检测构造函数原型是不是对象
instance.__proto__ = Fn.prototype instanceof Object ? Fn.prototype : Object.prototype
const returnValue = Fn.apply(instance,Array.prototype.slice.call(arguments,1))
if(typeof returnValue === 'object' && returnValue !== null || typeof returnValue === 'function'){
return returnValue
} else {
return instance
}
}ES6 版本的实现如下:
function myNew(Fn,...args){
if(typeof Fn != 'function'){
throw new TypeError(Fn + 'is not a constructor')
}
myNew.target = Fn
const instance = {}
// 检测构造函数原型是不是对象
instance.__proto__ = Fn.prototype instanceof Object ? Fn.prototype : Object.prototype
const returnValue = Fn.call(instance,...args)
return returnValue instanceof Object ? returnValue : instance
}注意几个要点:
new.target 会指向函数自身,这个“指向”的操作在代码里就是通过 myNew.target = Fn 体现的const instance = Object.create(Fn.prototype) 创建实例呢?根据规范,我们在实现 new 的时候,需要检测构造函数的原型是不是对象,如果不是对象,比如说是 null,那么实例的 __proto__ 会指向 Object 的原型,而这里如果使用了 Object.create,则会导致实例的 __proto__ 仍然指向 null。网上很多 new 的模拟实现直接使用了 Object.create,或者根本没有对构造函数的原型进行类型检查,这是不够严谨的instanceof,我们也可以改用 typeof Fn.prototype === 'Object' && Fn.prototype !== null 进行判断