这是JS 原生方法原理探究系列的第三篇文章。本文会介绍如何模拟实现 new
操作符。关于 new
的具体用法,MDN 已经描述得很清楚了,这里我们只做简单的介绍,具体的重点在于如何模拟实现。
下面展示的所有规范都是 ES5 版本的,与现在最新的规范有些区别
首先看一下根据规范的描述, new
操作符做了什么事:
全是英文,不过没关系,我简单翻译一下:
我在使用 new
操作符的时候,后面跟着的构造函数可能带参数,也可能不带参数,如果不带参数的话,比如说 new Fn()
,那么这里这个 Fn
就是一个 NewExpression
;如果带参数,比如说 new Fn(name,age)
,那么这里的 Fn
就是一个 MemberExpression
。
这两种情况下使用 new
操作符所进行的操作有点点不同,这里拿带参数的情况说明一下:
Fn
这个 MemberExpression
求值,其结果是指向实际函数对象的一个引用,我们把这个引用作为 ref
GetValue(ref)
进行求值,得到实际的函数对象,把这个对象作为 constructor
Arguments
也就是传进来的参数求值,得到一个参数列表,作为 argList
constructor
不是对象,则抛出类型错误constructor
没有实现内部的 [[Constructor]]
方法,也抛出类型错误constructor
的 [[Constructor]]
方法,并将 argList
传入作为参数,返回调用结果从这些描述可以看出,更多的实现细节放在函数的 [[Constructor]]
方法里。那么这个方法具体是做什么用的呢?
[[Constructor]]
的规范在 JS 中,函数有两种调用方式,一种是正常调用,这将调用函数的内部方法 [[Call]]
,还有一种是通过 new 调用,此时的函数作为一个构造函数,这将调用函数的另一个内部方法 [[Consturct]]
。所以,要实现 new
操作的话,我们得先搞懂 [[Construct]]
内部方法做了什么事。
这里继续看规范是怎么说的:
简单翻译一下:
当通过可能为空的参数列表调用函数 F
的内部方法 [[Construct]]
的时候,会执行如下步骤:
obj
作为一个新创建的原生对象obj
设置所有内部方法obj
的内部属性 [[Class]]
设置为 Object
prototype
调用函数 F
的内部方法 [[Get]]
,获取函数的原型对象,作为 proto
proto
是对象,则将 obj
的内部属性 [[Prototype]]
设置为 proto
proto
不是对象,则将 obj
的内部属性 [[Prototype]]
设置为标准内建的 Object
的原型对象F
的内部方法 Call
, obj
作为调用时的 this 值,此前传给 [[Construct]]
的参数列表作为调用时的参数。将调用后得到的结果作为 result
result
是对象,则将其返回obj
可以说,规范已经讲得很清楚了,简单地说,在 new 一个构造函数的时候,具体会做下面的事情:
__proto__
等于构造函数的 prototype
__proto__
等于 Object
的 prototype
ES3 版本的实现如下:
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
进行判断扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有