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