阅读 338

instanceof使用中可能漏掉的一点细节

前言

在面向对象语言中,一般都有关键字 instanceof 来检测对象类型,更准确点来说是检测对象是哪个类型的实例。那么在 JS 中这个关键字又有什么不同之处呢?此文仅是一篇对 ES 标准中 instanceof 关键字的解读,并记录了在此过程中的对 JS 中对象系统的一点小感悟。

标准规定该关键字需要做什么

先举例 a instanceof b, 下文中的步骤解释中关键字左侧的值用a表示, 右侧的值用b表示。

在标准中 instanceof 的行为被抽象到 InstanceofOperator 操作中,下面列出比较关键的两个抽象操作,并根据自己的理解对整理了下步骤:

  • InstanceofOperator ( a, b);
  • OrdinaryHasInstance ( b, a );

InstanceofOperator ( a, b)

  1. 检测b是否是对象,否TypeError
  2. 获取b是否定义了Symbol.hasInstance
  3. 是则b[Symbol.hasInstance](a),返回true或false
  4. 否则判断b是否是函数,否则TypeError
  5. 是则返回OrdinaryHasInstance(b, a)的运行结果

OrdinaryHasInstance ( b, a )

  1. 判断b是否是函数,否则返回false(这一步在这一场景中应该不会被调用)
  2. 是则判断b是否是被bind包装过的函数,是则获取到bind包装的函数bc,并调用InstanceofOperator( a, bc)
  3. 是则检测a是否是对象,否则返回false
  4. 是则获取b的prototype属性bp
  5. 如果bp不是对象则返回TypeError
  6. 获取a的原型并赋值给a
  7. 检测a是否为null,是则返回false
  8. 否则判断a和bp是否相等,是则返回true
  9. 否则重复步骤11~14直至返回true或false

几处细节的推敲

在查看标准解释是,有几处理解的不是很明白,故使用几个demo对自己的理解做了验证

b应该是什么值

先看b不是对象是什么情况

从上面的结果可以看到报错了,而且错误提示很明显的提示右侧的值不是对象,这是第1步检测报的错,那么如果是对象,而不是函数对象又会是什么情况呢?

嗯,没错,对象也报错了,这次的错误是右侧的值不可调用,即不是函数,这应是第4步检测报的错了。根据上面的步骤试试给对象设置Symbol.hasInstance属性

这一次没有报错,检测正常进行了,且返回了在函数中定义的结果,根据上面的步骤和代码验证,可以得出右侧的值可以是设置了Symbol.hasInstance属性的对象。且检测的结果会被右侧值定义的Symbol.hasInsyance函数拦截成函数返回的结果。

右侧是函数的情况就是比觉正常的返回了,需要明确的一点是函数是不能直接设置Symbol.hasInstance属性的,具体原因,感兴趣的可以继续查阅资料。到这里可以得出结论,b必须是设置了Symbol的对象或者函数,否则会报错。

被 dind 包装过的函数依然会返回原函数原型检测结果

这一步存疑的原因是,没有直接理解标准中的描述,只是有所猜测,不能确定,故验证之。

var a = function() {
   this.name = 'a';
}; 
var b = {testName: 'b'};
a.prototype = b;

var c = new a();
console.log('c instanceof a', c instanceof a);   // true

var h = {name: 'h'};
var s = a.bind(h);
console.log('c instanceof s', c instanceof s);   // true
复制代码

代码传送门

如上代码示例,从其运行结果可知,a.prototype 是等于 c 的原型的,s 是 a 被 bind 对象 h 后返回的函数,从验证结果来看,函数经过 bind 的一层包装,依然不会影响其原型检测的绑定。

一点思考

在推敲上面细节时突然联想到这种检测实际是很合理的,类比 Java 中的检测类,这里对象的原型不就可以类比成 Java 里面的类么。 某个对象的原型是什么决定它具备了原型对象的特性的,而类不也是这样么,该对象属于哪个类,决定了该对象具备了哪些特性。不同的是,在JS中的检测是往继承链上多退了一步的,Java 中是直接检测对象是否是该类的实例,而JS中是检测的是对象的原型是否等于函数的prototype属性,这应该就是继承原理不同产生的区别吧。

之前从未认真的思考过,JS 中的整个对象系统是什么样的,虽然现在也还是没有理解太多,但是至少现在在我的认知里 JS 的 对象系统是简单而完善的,并没有因为简单而缺少了面向对象基本特性。虽然它灵活的允许你随便更改一个对象的原型,也就是类,但是这依然不妨碍它成为一门可以面像对象编程的语言。

结论

instanceof 关键字检测的本质是检测左侧对象的原型链上是否存在和右侧函数的prototype属性相等的对象,如果存在则返回true,如果不存在则返回false。

关注下面的标签,发现更多相似文章
评论