本文主要讲解ECMAScript7规范中的instanceof操作符。
“有名”的Symbols指的是内置的符号,它们定义在Symbol对象上。ECMAScript7中使用了@@name的形式引用这些内置的符号,比如下面会提到的@@hasInstance,其实就是Symbol.hasInstance。
O instanceof C在内部会调用InstanceofOperator(O, C)抽象操作,该抽象操作的步骤如下:
如果instOfHandler的值不是undefined,那么:
OrdinaryHasInstance(C, O)抽象操作的步骤如下:
如果C有内部插槽[[BoundTargetFunction]],那么:
重复执行下述步骤:
SameValue抽象操作参见JavaScript中的==,===和Object.js()中的Object.is(),Object.is()使用的就是这个抽象操作的结果。
由上述步骤2可知,如果C是一个bind函数,那么会重新在C绑定的目标函数上执行InstanceofOperator(O, BC)操作。
由上述步骤6可知,会重复地获取对象O的原型对象,然后比较该原型对象和C的prototype属性是否相等,直到相等返回true,或者O变为null,也就是遍历完整个原型链,返回false。
由上面的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)的步骤如下:
所以,你可以看到在默认情况下,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.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
instanceof运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上
instanceof 的作用是判断实例对象是否为构造函数的实例,实际上判断的是实例对象的__proto__属性与构造函数的prototype属性是否指向同一引用;constructor 的作用是返回实例的构造函数,即返回创建此对象的函数的引用
typeof操作符返回一个字符串,表示未经计算的操作数的类型。 可能返回值有:undefined、object、boolean、number、string、symbol、function、object
instanceof 用于判断某个对象是否是另一个对象(构造方法)的实例。instanceof会查找原型链,直到null如果还不是后面这个对象的实例的话就返回false,否则就返回true
Symbol 是ES6中引入的一种原始数据类型,表示独一无二的值。BigInt(大整数)是 ES2020 引入的一种新的数据类型,用来解决 JavaScript中数字只能到 53 个二进制位(JavaScript 所有数字都保存成 64 位浮点数,大于这个范围的整数
JS 是种弱类型语言,对变量的类型没有限制。例如,如果我们使用字符串类型创建了一个变量,后面又可以为同一变量分配一个数字:
typeof 能判断的类型有: number,boolean,string,undefined,object,function。但是不能判断 null,array,object和函数实例(new + 函数),都是返回object;instanceof 能够对不同的对象实例进行判断,如null,array等,返回值为布尔值
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!