关于javascript:ECMAScript7规范中的instanceof操作符

31次阅读

共计 5004 个字符,预计需要花费 13 分钟才能阅读完成。

本文次要解说 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 A
a.__proto__ === A.prototype // true
a.__proto__.__proto__ === Object.prototype // true
a.__proto__.__proto__.__proto__ === null // true

a instanceof A // true
a 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.prototype

a.__proto__ === A.prototype // true
a.__proto__.__proto__ === B.prototype // true
a.__proto__.__proto__.__proto__ === Object.prototype // true
a.__proto__.__proto__.__proto__.__proto__ === null // true

a 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 // true

var b = new B
b instanceof B // true
b instanceof A // true

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

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

function A () {}
var a = new A
a instanceof A // true

A[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] = fun
A[Symbol.hasInstance] === fun // false
A[Symbol.hasInstance] === Function.prototype[Symbol.hasInstance] // true
A[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 A
a instanceof A // true

Object.defineProperty(A, Symbol.hasInstance, {value: function () {return false}
})
a instanceof A // false

总结

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

正文完
 0