本文次要解说ECMAScript7标准中的instanceof操作符。

准备常识

有名的Symbols

“有名”的Symbols指的是内置的符号,它们定义在Symbol对象上。ECMAScript7中应用了@@name的模式援用这些内置的符号,比方上面会提到的@@hasInstance,其实就是Symbol.hasInstance

InstanceofOperator(O, C)

O instanceof C在外部会调用InstanceofOperator(O, C)形象操作,该形象操作的步骤如下:

  1. 如果C的数据类型不是对象,抛出一个类型谬误的异样;
  2. instOfHandler等于GetMethod(C, @@hasInstance),大略语义就是获取对象C@@hasInstance属性的值;
  3. 如果instOfHandler的值不是undefined,那么:

    1. 返回ToBoolean(? Call(instOfHandler, C, « O »))的后果,大略语义就是执行instOfHandler(O),而后把调用后果强制转化为布尔类型返回。
  4. 如果C不能被调用,抛出一个类型谬误的异样;
  5. 返回OrdinaryHasInstance(C, O)的后果。

OrdinaryHasInstance(C, O)

OrdinaryHasInstance(C, O)形象操作的步骤如下:

  1. 如果C不能被调用,返回false
  2. 如果C有外部插槽[[BoundTargetFunction]],那么:

    1. BC等于C的外部插槽[[BoundTargetFunction]]的值;
    2. 返回InstanceofOperator(O, BC)的后果;
  3. 如果O的类型不是对象,返回false
  4. P等于Get(C, "prototype"),大略语义是获取C.prototype的值;
  5. 如果P的数据类型不是对象,抛出一个类型谬误的异样;
  6. 反复执行下述步骤:

    1. O等于O.[[GetPrototypeOf]]()的后果,大略语义就是获取O的原型对象;
    2. 如果O等于null,返回false
    3. 如果SameValue(P, O)的后果是true,返回true

SameValue形象操作参见JavaScript中的==,===和Object.js()中的Object.is()Object.is()应用的就是这个形象操作的后果。

由上述步骤2可知,如果C是一个bind函数,那么会从新在C绑定的指标函数上执行InstanceofOperator(O, BC)操作。

由上述步骤6可知,会反复地获取对象O的原型对象,而后比拟该原型对象和Cprototype属性是否相等,直到相等返回true,或者O变为null,也就是遍历残缺个原型链,返回false

Function.prototype[@@hasInstance] (V)

由下面的InstanceofOperator(O, C)形象操作的步骤23能够晓得,如果C下面定义或继承了@@ hasInstance属性的话,会调用该属性的值,而不会走到步骤45。步骤45的目标是为了兼容没有实现@@hasInstance办法的浏览器。如果一个函数没有定义或继承@@hasInstance属性,那么就会应用默认的instanceof的语义,也就是OrdinaryHasInstance(C, O)形象操作形容的步骤。

ECMAScript7标准中,在Functionprototype属性上定义了@@hasInstance属性。Function.prototype[@@hasInstance](V)的步骤如下:

  1. F等于this值;
  2. 返回OrdinaryHasInstance(F, V)的后果。

所以,你能够看到在默认状况下,instanceof的语义是一样的,都是返回OrdinaryHasInstance(F, V)的后果。为什么说默认状况下?因为你能够笼罩Function.prototype[@@hasInstance]办法,去自定义instanceof的行为。

例子

function A () {}function B () {}var a = new Aa.__proto__ === A.prototype // truea.__proto__.__proto__ === Object.prototype // truea.__proto__.__proto__.__proto__ === null // truea instanceof A // truea instanceof B // false

OrdinaryHasInstance(C, O)的第6步可知:

  • 对于a instanceof APA.prototype,在第一次循环的时候,a的原型对象a._proto__A.prototype,也就是步骤中的OA.prototype,所以返回了true
  • 对于a instanceof BPB.prototype,在第一次循环的时候,a的原型对象a._proto__A.prototype,不等于P;执行第二次循环,此时Oa.__proto__.__proto__,也就是Object.prototype,不等于P;执行第三次循环,此时Oa.__proto__.__proto__.__proto__,也就是null,也就是原型链都遍历完了,所以返回了false

接着下面的例子:

A.prototype.__proto__ = B.prototypea.__proto__ === A.prototype // truea.__proto__.__proto__ === B.prototype // truea.__proto__.__proto__.__proto__ === Object.prototype // truea.__proto__.__proto__.__proto__.__proto__ === null // truea instanceof B // true

在下面的例子中,咱们把B.prototype设置成了a的原型链中的一环,这样a instanceof BOrdinaryHasInstance(C, O)的第6步的第2次循环的时候,返回了true

OrdinaryHasInstance(C, O)的第2步,咱们晓得bind函数的行为和一般函数的行为是不一样的:

function A () {}var B = A.bind()B.prototype === undefined // truevar b = new Bb instanceof B // trueb instanceof A // true

由下面的例子可知,B.prototypeundefined。所以,instanceof作用于bind函数的返回后果其实是作用于绑定的指标函数的返回值,和bind函数基本上没有什么关系。

InstanceofOperator(O, C)步骤2和步骤3可知,咱们能够通过@@hasInstance属性来自定义instanceof的行为:

function A () {}var a = new Aa instanceof A // trueA[Symbol.hasInstance] = function () { return false }a instanceof A // ?

chrome浏览器测试了一下,发现还是输入true。而后看了一下ECMAScript6的文档,ECMAScript6文档外面还没有规定能够通过@@hasInstance扭转instanceof的行为,所以应该是目前chrome浏览器还没有实现ECMAScript7中的instanceof操作符的行为。

直到有一天看了MDN上Symbol.hasInstance的兼容性局部,发现chrome51版本就开始反对Symbol.hasInstance了:

class MyArray {    static [Symbol.hasInstance](instance) {    return Array.isArray(instance)  }}console.log([] instanceof MyArray) // true

那么为什么我那样写不行呢?直到我发现:

function A () {}var fun = function () {return false}A[Symbol.hasInstance] = funA[Symbol.hasInstance] === fun // falseA[Symbol.hasInstance] === Function.prototype[Symbol.hasInstance] // trueA[Symbol.hasInstance] === A.__proto__[Symbol.hasInstance] // true

由下面的代码可知,A[Symbol.hasInstance]并没有赋值胜利,而且始终等于Function.prototype[Symbol.hasInstance],也就是始终等于A的原型上的Symbol.hasInstance办法。那是不是因为原型上的同名办法?

Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance)// Object {writable: false, enumerable: false, configurable: false, value: function}

由下面的代码可知,Function.prototype上的Symbol.hasInstance的属性描述符的writablefalse,也就是这个属性是只读的,所以在A下面增加Symbol.hasInstance属性失败了。然而为啥没有失败的提醒呢?

'use strict'function A () {}var fun = function () {return false}A[Symbol.hasInstance] = fun// Uncaught TypeError: Cannot assign to read only property 'Symbol(Symbol.hasInstance)' of function 'function A() {}'

谬误提醒进去了,所以当前还是尽量应用严格模式。非严格模式下有些操作会静默失败,也就是即便操作失败了也不会有任何提醒,导致开发人员认为操作胜利了。

var a = {}a[Symbol.hasInstance] = function () {return true}new Number(3) instanceof a // true

因为能够通过自定义Symbol.hasInstance办法来笼罩默认行为,所以用instanceof操作符判断数据类型并不一定是牢靠的。

还有一个问题:为什么下面MDN文档的例子能够胜利,我最后的例子就不行呢,目标不都是写一个构造函数,而后在构造函数上增加一个属性吗?
集体剖析的后果是:尽管大家都说Class是写构造函数的一个语法糖,然而其实还是和应用function的形式有差异的,就比方下面的例子。应用Class的时候,会间接在构造函数上增加一个动态属性,不会先查看原型链上是否存在同名属性。而应用function的形式的时候,给构造函数增加一个静态方法,相当于给对象赋值,赋值操作会先查看原型链上是否存在同名属性,所以就会有赋值失败的危险。所以,就给构造函数增加Symbol.hasInstance属性来说,Class能做到,应用Function的形式就做不到。

更新于2018/11/20
下面总结到

所以,就给构造函数增加Symbol.hasInstance属性来说,Class能做到,应用Function的形式就做不到。

然而,起初发现给对象增加属性的办法不只是赋值这一种形式,还有一个Object.defineProperty办法:

function A () {}var a = new Aa instanceof A // trueObject.defineProperty(A, Symbol.hasInstance, {    value: function () { return false }})a instanceof A // false

总结

本文次要解说ECMAScript7标准中的instanceof操作符,心愿大家能有所播种。如果本文有什么谬误或者不谨严的中央,欢送在评论区留言。