共计 5004 个字符,预计需要花费 13 分钟才能阅读完成。
本文次要解说 ECMAScript7
标准中的 instanceof
操作符。
准备常识
有名的 Symbols
“有名”的 Symbols
指的是内置的符号,它们定义在 Symbol
对象上。ECMAScript7
中应用了 @@name
的模式援用这些内置的符号,比方上面会提到的@@hasInstance
,其实就是Symbol.hasInstance
。
InstanceofOperator(O, C)
O instanceof C
在外部会调用 InstanceofOperator(O, C)
形象操作,该形象操作的步骤如下:
- 如果
C
的数据类型不是对象,抛出一个类型谬误的异样; - 让
instOfHandler
等于GetMethod(C, @@hasInstance)
,大略语义就是获取对象C
的@@hasInstance
属性的值; -
如果
instOfHandler
的值不是undefined
,那么:- 返回
ToBoolean(? Call(instOfHandler, C, « O »))
的后果,大略语义就是执行instOfHandler(O)
,而后把调用后果强制转化为布尔类型返回。
- 返回
- 如果
C
不能被调用,抛出一个类型谬误的异样; - 返回
OrdinaryHasInstance(C, O)
的后果。
OrdinaryHasInstance(C, O)
OrdinaryHasInstance(C, O)
形象操作的步骤如下:
- 如果
C
不能被调用,返回false
; -
如果
C
有外部插槽[[BoundTargetFunction]]
,那么:- 让
BC
等于C
的外部插槽[[BoundTargetFunction]]
的值; - 返回
InstanceofOperator(O, BC)
的后果;
- 让
- 如果
O
的类型不是对象,返回false
; - 让
P
等于Get(C, "prototype")
,大略语义是获取C.prototype
的值; - 如果
P
的数据类型不是对象,抛出一个类型谬误的异样; -
反复执行下述步骤:
- 让
O
等于O.[[GetPrototypeOf]]()
的后果,大略语义就是获取O
的原型对象; - 如果
O
等于null
,返回false
; - 如果
SameValue(P, O)
的后果是true
,返回true
。
- 让
SameValue
形象操作参见 JavaScript 中的 ==,=== 和 Object.js()中的 Object.is()
,Object.is()
应用的就是这个形象操作的后果。
由上述步骤 2
可知,如果 C
是一个 bind
函数,那么会从新在 C
绑定的指标函数上执行 InstanceofOperator(O, BC)
操作。
由上述步骤 6
可知,会反复地获取对象 O
的原型对象,而后比拟该原型对象和 C
的prototype
属性是否相等,直到相等返回 true
,或者O
变为null
,也就是遍历残缺个原型链,返回false
。
Function.prototype[@@hasInstance] (V)
由下面的 InstanceofOperator(O, C)
形象操作的步骤 2
和3
能够晓得,如果 C
下面定义或继承了 @@ hasInstance
属性的话,会调用该属性的值,而不会走到步骤 4
和5
。步骤 4
和5
的目标是为了兼容没有实现 @@hasInstance
办法的浏览器。如果一个函数没有定义或继承 @@hasInstance
属性,那么就会应用默认的 instanceof
的语义,也就是 OrdinaryHasInstance(C, O)
形象操作形容的步骤。
ECMAScript7
标准中,在 Function
的prototype
属性上定义了 @@hasInstance
属性。Function.prototype[@@hasInstance](V)
的步骤如下:
- 让
F
等于this
值; - 返回
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 A
,P
是A.prototype
,在第一次循环的时候,a
的原型对象a._proto__
是A.prototype
,也就是步骤中的O
是A.prototype
,所以返回了true
; - 对于
a instanceof B
,P
是B.prototype
,在第一次循环的时候,a
的原型对象a._proto__
是A.prototype
,不等于P
;执行第二次循环,此时O
是a.__proto__.__proto__
,也就是Object.prototype
,不等于P
;执行第三次循环,此时O
是a.__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 B
在OrdinaryHasInstance(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.prototype
是 undefined
。所以,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 的兼容性局部,发现 chrome
从51
版本就开始反对 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
的属性描述符的 writable
是false
,也就是这个属性是只读的,所以在 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
操作符,心愿大家能有所播种。如果本文有什么谬误或者不谨严的中央,欢送在评论区留言。