按照规范模拟实现 Instanceof

1,949 阅读3分钟

在 JS 中判断一个变量的类型常常会用 typeof 运算符,但是对于引用类型存储值就会出现问题,所以引入了 instanceof 来解决问题。instanceof 用于检测构造函数的 prototype 属性是否出现在某个实例的原型链上。

用法:

object instanceof constructor

举例:

let simpleStr = 'This is a simple string';
simpleStr instanceof String; // false, 检查原型链会找到 undefined
let myString  = new String();
myString  instanceof String; // true
myString  instanceof Object; // true

需要注意的是,如果表达式返回 true,并不意味着该表达式会永远返回 true,因为 constructor.prototype 属性的值可能会改变改变之后的值很有可能不存在于 object 的原型链上面。另外一种情况就是 object 的原型链情况改变也会造成表达式结果的改变。

// 情况1: 改变构造函数的 prototype 属性值
function Cat() {}
function Dog() {}
const cat = new Cat()
console.log(cat instanceof Cat) // true
Cat.prototype = new Dog()
console.log(cat instanceof Cat) // false
// 情况2: 改变实例的原型链情况
const dog = new Dog()
console.log(dog instanceof Dog) // true
dog.__proto__ = new String()
console.log(dog instanceof Dog) // false

语言规范的定义

看几个例子:

console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Number instanceof Number); // false
console.log(String instanceof String); // false

function Foo(){} 
console.log(Function instanceof Object); // true
console.log(Foo instanceof Function); // true
console.log(Foo instanceof Foo); // false

为什么 Object 和 Function instanceof 自己都返回 true,但是其他类却不等于 true 呢?接下来我们就从语言规范中是如何定义这个运算符的理解一下 instanceof。

11.8.6 The instanceof operator The production RelationalExpression: RelationalExpression instanceof ShiftExpression is evaluated as follows:

  1. Evaluate RelationalExpression.

  2. Call GetValue(Result(1)). // 调用 GetValue 方法得到 Result(1) 的值,设为 Result(2)

  3. Evaluate ShiftExpression.

  4. Call GetValue(Result(3)). // 同理,这里设为 Result(4)

  5. If Result(4) is not an object, throw a TypeError exception. // 如果 Result(4) 不是 object,抛出异常

  6. If Result(4) does not have a [[HasInstance]] method, throw a TypeError exception. // 如果 Result(4) 没有 [[HasInstance]] 方法,抛出异常。规范中的所有 [[...]] 方法或者属性都是内部的,在 JavaScript 中不能直接使用。并且规范中说明,只有 Function 对象实现了 [[HasInstance]] 方法。所以这里可以简单的理解为:如果 Result(4) 不是 Function 对象,抛出异常.

  7. Call the [[HasInstance]] method of Result(4) with parameter Result(2). // 相当于这样调用:Result(4).[[HasInstance]](Result(2))

  8. Return Result(7).

相关的 HasInstance 方法定义:

15.3.5.3 [[HasInstance]] (V) Assume F is a Function object. // 这里 F 就是上面的 Result(4),V 是 Result(2) When the [[HasInstance]] method of F is called with value V, the following steps are taken:

1. If V is not an object, return false. // 如果 V 不是 object,直接返回 false
2. Call the [[Get]] method of F with property name "prototype". // 用 [[Get]] 方法取 F 的 prototype 属性
3. Let O be Result(2).//O = F.\[\[Get]]("prototype") 
4. If O is not an object, throw a TypeError exception. 
5. Let V be the value of the [[Prototype]] property of V.//V = V.[[Prototype]] 
6. If V is null, return false. 
7. If O and V refer to the same object or if they refer to objects joined to each other (section 13.1.2), return true. // 这里是关键,如果 O 和 V 引用的是同一个对象,则返回 true;否则,到 Step 8 返回 Step 5 继续循环
8. Go to step 5.

上面的规范定义比较晦涩,看起来比较复杂,涉及到很多概念,但是把这段翻译成 JS 代码却很简单,如下:

function instance_of(L, R) { // L 表示左表达式,R 表示右表达式
  const O = R.prototype;
  L = L.__proto__;

  while(true) {
    if (L === null) { // Object.prototype 为 null
      return false
    }

    if (O === L) { // 当 O 严格等于 L 时,返回 true 
      return true
    }

    L = L.__proto__
  }
}

涉及到原型链的,可以参考之前写的文章,【JS基础】原型和原型链

这里我们就可以跟据上面的代码来分析一下为什么 Object 和 Function instanceof 自己都返回 true,但是其他类却不等于 true。

首先 Object 和 String 这些本身都属于函数,他们的构造函数都是 Function,所以 Object instanceof Function 返回 true,Function.prototype 的构造函数是 Object,所以 Function.prototype instanceof Object 返回 true,所以 Object 和 String instanceof Object 和 Function 的时候都会返回 true。

Object instanceof Object; // true
Object.__proto__ === Function.prototype // true
Function.prototype.__proto__ === Object.prototype // true

String instanceof String; // false
String.__proto__ === Function.prototype // true
Function.prototype.__proto__ === Object.prototype // true
Object.prototype.__proto__ === null // 根据上面代码,如果 L 为 null 时,返回 false