new与instanceof的实现
# new与instanceof的实现
# new操作符
# 1. 规范
new操作符的规范是:ECMAScript 2025 Language Specification#new-operator (opens new window)
EvaluateNew ( constructExpr, arguments )
:
- 令
ref
为constructExpr
的Evaluation
结果 - 令
constructor
为GetValue(ref)
的结果 - 如果
arguments
是空的,则令argList
为一个空的List
- 否则令
argList
为arguments
的ArgumentListEvaluation
结果 - 如果
IsConstructor(constructor)
执行结果是false
,则抛出一个TypeError
异常(是一个对象且有[[Construct]]
内部方法) - 返回
Construct(constructor, argList)
的结果(执行内部的[[Construct]]
方法)
# 2. 实现
new
操作符的规范是比较简单的,通常实现的思路如下:
- 创建一个空的简单的javascript对象
newInstance
- 如果构造函数的
prototype
属性是一个对象,则将newInstance
的[[Prototype]]
指向该对象,否则将newInstance
的[[Prototype]]
指向Object.prototype
- 使用给定的参数执行构造函数,并将
newInstance
作为this
绑定上下文(换句话说,在构造函数中的所有this
都指向newInstance
) - 如果构造函数返回了非原始值,则该返回值成为整个
new
表达式的结果。否则,返回newInstance
function create(constructor, ...args) {
if (typeof constructor !== 'function') {
throw new TypeError('constructor must be a function')
}
const prototype = constructor.prototype
const newInstance = {}
// 如果构造函数的prototype属性是一个对象,则将newInstance的[[Prototype]]指向该对象
if(prototype instanceof Object) {
Object.setPrototypeOf(newInstance, prototype)
}
const res = constructor.apply(newInstance, args)
return res instanceof Object ? res : newInstance // 返回指定对象或者实例
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# instanceof操作符 {#instanceof}
# 1. 使用
instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。
const Animal = function() {}
const Dog = function() {}
const Cat = function() {}
Dog.prototype = new Animal()
Cat.prototype = new Animal()
const dog = new Dog()
console.log(dog instanceof Animal) // true
console.log(dog instanceof Dog) // true
console.log(dog instanceof Cat) // false
console.log(dog instanceof Object) // true
2
3
4
5
6
7
8
9
10
11
12
虽然instanceof
判断为true
的条件是prototype
属性出现在某个实例对象的原型链上,但是instanceof
运算符的行为是可以改变的。如下:
class Desk {
static [Symbol.hasInstance](instance) {
return instance.legs >= 4 // 4条腿以上的模板就当作是桌子
}
}
class Board {
constructor(legs = 0) {
this.legs = legs
}
}
const board1 = new Board()
const board2 = new Board(4)
console.log(board1 instanceof Desk) // false
console.log(board2 instanceof Desk) // true
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
可以看到,通过部署[Symbol.hasInstance]
方法,我们可以让instanceof
判断更灵活。[Symbol.hasInstance]
的规范和使用查看下面文档:
- ECMAScript 2025 Language Specification#instanceof-operator (opens new window)
- MDN#instanceof (opens new window)
# 2. 规范
instanceof
操作符的规范:ECMAScript 2025 Language Specification#instanceof-operator (opens new window)
InstanceofOperator ( V, target )
:
- 如果
target
不是一个对象,则抛出一个TypeError
异常 - 令
instOfHandler
为GetMethod(target, @@hasInstance)
的结果(即部署的的[Symbol.hasInstance]
方法,GetMethod
方法见GetMethod规范
(opens new window),会返回函数或者undefined
) - 如果
instOfHandler
不是undefined
,则返回ToBoolean(Call(instOfHandler, target, [V]))
(即调用[Symbol.hasInstance]
方法转为布尔值的结果) - 如果
IsCallable(target)
为false
,则抛出一个TypeError
异常 - 返回
OrdinaryHasInstance(target, V)
的结果- 如果
IsCallable(target)
是false
,returnfalse
- 如果
target
有一个[[BoundTargetFunction]]
内部槽,则让BC
等于target.[[BoundTargetFunction]]
,然后返回InstanceofOperator(V, BC)
的结果
- 如果
- 如果
V
不是一个对象,则返回false
- 令
P
为Get(target, "prototype")
的结果(即target
对象的prototype
属性) - 如果
P
不是对象,则抛出一个TypeError
异常 - 不断重复令
V
为V.[[GetPrototypeOf]]
(即V = V.__proto__
),如果V
为null
,则返回false
,如果V === P
,则返回true
[[BoundTargetFunction]]
[[BoundTargetFunction]]
是调用Function.prototype.bind
方法时部署的内部属性(实现的内部名称为[[TargetFunction]]
,同时还会部署了[[BoundThis]]
和[[BoundArgs]]
属性),其值指向了目标函数。
# 3. 实现
在提及instanceof
如何实现时,我们大部分情况下看到的实现方案如下:
function isInstance(o, p) {
const prototype = p.prototype
let proto = o.__proto__
while (true) {
if (proto === null) return false
if (proto === prototype) return true
proto = proto.__proto__
}
}
2
3
4
5
6
7
8
9
也即大部分实现的是判断prototype
属性是否出现在某个实例对象的原型链上。没有实现部署了[Symbol.hasInstance]
方法的情况,以上实现在下列的测试用例中高亮部分无法测试通过:
class Desk {
static [Symbol.hasInstance](instance) {
return instance.legs >= 4 // 4条腿以上的模板就当作是桌子
}
}
class Board {
constructor(legs = 0) {
this.legs = legs
}
}
function Hello() {}
const HelloBind = Hello.bind(null)
const board = new Board(4)
const hello = new HelloBind()
console.log(board instanceof Desk) // true
console.log(board instanceof Board) // true
console.log(isInstance(board, Desk)) // false 测试不通过 // [!code highlight]
console.log(isInstance(board, Board)) // true
console.log(hello instanceof Hello) // true
console.log(hello instanceof HelloBind) // true
console.log(isInstance(hello, Hello)) // true
console.log(isInstance(hello, HelloBind)) // false 测试不通过 // [!code highlight]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
所以我们修改代码实现如下:
function isInstance(o, p) {
// 是否部署了[Symbol.hasInstance]方法
if(typeof p[Symbol.hasInstance] === 'function') {
return p[Symbol.hasInstance](o)
}
const prototype = p.prototype
let proto = o.__proto__
while (true) {
if (proto === null) return false
if (proto === prototype) return true
proto = proto.__proto__
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
通过这样改造后,以下发现两条测试用例均通过了:
console.log(isInstance(board, Desk))
console.log(isInstance(hello, HelloBind))
2
从规范中我们还看到了原生的instanceof
还应该对绑定函数(使用Function.prototype.bind
方法产生的函数)做专门的处理。但是我们改造后的代码并没有对此种情况做专门的处理,那为什么绑定函数的测试也能测试通过呢?我们把HelloBind
和hello
打印出来:
从中,我们至少可以得出以下几个结论:
- 绑定函数
HelloBind
的原型上有[[TargetFunction]]
,指向目标函数Hello
- 对绑定函数
HelloBind
进行new
调用,实际调用的是HelloBind
的[[TargetFunction]]
属性Hello
,而不是HelloBind
本身,所以hello instanceof Hello === true
- 绑定函数
HelloBind
的原型上部署了[Symbol.hasInstance]
方法,因此调用hello instanceof HelloBind
时,会执行该方法 - 猜测:基于以上3点,猜测
[Symbol.hasInstance]
方法的实现直接或间接的引用了[[TargetFunction]]
属性,从而令hello instanceof HelloBind
的值为true
不确定的结论
本人尝试读了V8对Function.prototype.bind
的实现,似乎绑定函数内部定义的[Symbol.hasInstance]
方法直接返回了OrdinaryHasInstance(target, V)
执行的结果,而我们知道按照规范OrdinaryHasInstance(target, V)
内部会判断是否具有[[BoundTargetFunction]]
(即[[TargetFunction]]
)。以上结论未查询到相关资料,为个人猜测。如果你有明确的结论可以告诉我。
最后,加上异常以及边界相关的逻辑,instanceof
实现完整代码如下:
function isObject(o) {
return o !== null && (typeof o === 'object' || typeof o === 'function')
}
function isInstance(o, p) {
if(!isObject(p)) {
throw new TypeError('Right-hand side of instanceof is not an object')
}
// 是否部署了[Symbol.hasInstance]方法
if(typeof p[Symbol.hasInstance] === 'function') {
return p[Symbol.hasInstance](o)
}
// o不是对象直接返回false
if(!isObject(o) ) return false
const prototype = p.prototype
if(!isObject(prototype)) {
throw new TypeError(`Function has non-object prototype '${prototype}' in instanceof check`)
}
let proto = o.__proto__
while (true) {
if (proto === null) return false
if (proto === prototype) return true
proto = proto.__proto__
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28