深海鱼的博客 深海鱼的博客
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

深海鱼

尽人事,知天命,知行合一。
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • new与instanceof的实现
    • new操作符
      • 1. 规范
      • 2. 实现
    • instanceof操作符 {#instanceof}
      • 1. 使用
      • 2. 规范
      • 3. 实现
  • call、apply和bind实现
  • Promise的原理与实现
  • 手写系列
深海鱼
2024-06-19
目录

new与instanceof的实现

# new与instanceof的实现

# new操作符

# 1. 规范

new操作符的规范是:ECMAScript 2025 Language Specification#new-operator (opens new window)

EvaluateNew ( constructExpr, arguments ):

  1. 令ref为constructExpr的Evaluation结果
  2. 令constructor为GetValue(ref)的结果
  3. 如果arguments是空的,则令argList为一个空的List
  4. 否则令argList为arguments的ArgumentListEvaluation结果
  5. 如果IsConstructor(constructor)执行结果是false,则抛出一个TypeError异常(是一个对象且有[[Construct]]内部方法)
  6. 返回Construct(constructor, argList)的结果(执行内部的[[Construct]]方法)

# 2. 实现

new操作符的规范是比较简单的,通常实现的思路如下:

  1. 创建一个空的简单的javascript对象newInstance
  2. 如果构造函数的prototype属性是一个对象,则将newInstance的[[Prototype]]指向该对象,否则将newInstance的[[Prototype]]指向Object.prototype
  3. 使用给定的参数执行构造函数,并将newInstance作为this绑定上下文(换句话说,在构造函数中的所有this都指向newInstance)
  4. 如果构造函数返回了非原始值,则该返回值成为整个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 // 返回指定对象或者实例
}

1
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
1
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
1
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 ):

  1. 如果target不是一个对象,则抛出一个TypeError异常
  2. 令instOfHandler为GetMethod(target, @@hasInstance)的结果(即部署的的[Symbol.hasInstance]方法,GetMethod方法见GetMethod规范 (opens new window),会返回函数或者undefined)
  3. 如果instOfHandler不是undefined,则返回ToBoolean(Call(instOfHandler, target, [V]))(即调用[Symbol.hasInstance]方法转为布尔值的结果)
  4. 如果IsCallable(target)为false,则抛出一个TypeError异常
  5. 返回OrdinaryHasInstance(target, V)的结果
    1. 如果IsCallable(target)是false,returnfalse
    2. 如果target有一个[[BoundTargetFunction]]内部槽,则让BC等于target.[[BoundTargetFunction]],然后返回InstanceofOperator(V, BC)的结果
  6. 如果V不是一个对象,则返回false
  7. 令P为Get(target, "prototype")的结果(即target对象的prototype属性)
  8. 如果P不是对象,则抛出一个TypeError异常
  9. 不断重复令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__
  }
}
1
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]
1
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__
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

通过这样改造后,以下发现两条测试用例均通过了:

console.log(isInstance(board, Desk))
console.log(isInstance(hello, HelloBind))
1
2

从规范中我们还看到了原生的instanceof还应该对绑定函数(使用Function.prototype.bind方法产生的函数)做专门的处理。但是我们改造后的代码并没有对此种情况做专门的处理,那为什么绑定函数的测试也能测试通过呢?我们把HelloBind和hello打印出来:

new-bind

从中,我们至少可以得出以下几个结论:

  • 绑定函数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__
  }
}
1
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
最近更新: 2024/06/19, 17:54
call、apply和bind实现

call、apply和bind实现→

最近更新
01
Axios源码解读
07-29
02
基于React16的Webpack升级与构建速度优化
07-09
03
Vue-Router源码解读
07-09
更多文章>
Theme by Vdoing | Copyright © 2024-2024 深海鱼 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式