本文会介绍ES6规范中 instanceof 操作符的实现,以及自定义 instanceof 操作符行为的几个方法。

文中涉及的规范相关的代码皆为伪代码,为了便于理解,其中可能会省略一些参数判断逻辑或者使用ES语法来代替规范内置的方法,如果发现纰漏,欢迎随时指出。

instanceof 操作符的实现

InstanceofOperator(O, C)

O instanceof C 会被编译为方法调用 -- InstanceofOperator(O, C),其实现如下:

InstanceofOperator(O, C){

    if(typeof C !== 'object'){
throw TypeError;
}   let instOfHandler = C[Symbol.hasInstance]; if(typeof instOfHandler !== 'undefined'){
return !!instOfHandler.call(C, O);
}   if(typeof C !== 'function'){
throw TypeError;
} return OrdinaryHasInstance(C, O);
}

该方法首先判断了 C[Symbol.hasInstance] 方法是否存在,如果存在,就调用;如果不存在,就调用 OrdinaryHasInstance(C, O) 方法。

Function.prototype[Symbol.hasInstance](V)

对于 ES 内置构造器如 Function(), Array() ,其本身是没有 [Symbol.hasInstance] 属性的,都继承自 Function.prototype,这个方法是预定义的,不可修改:

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

其实现如下:

Function.prototype[Symbol.hasInstance](V){

    let F = this;

    return OrdinaryHasInstance(F, V);
}

比较简单明了,直接调用 OrdinaryHasInstance(F, V) 方法。

OrdinaryHasInstance(C, O)

上述两个方法都最终调用到了 OrdinaryHasInstance(C, O) ,其实现如下:

OrdinaryHasInstance(C, O){

    if(typeof C !== 'function'){
return false;
} if(typeof O !== 'object'){
return false;
} let P = C.prototype; while(true){ let O = Object.getPrototypeOf(O); if(O === null){
return false;
} if(SameValue(P, O)){
return true;
}
}
}

这个方法是判断 C.prototype 是否在 O 的原型链上。

知道了 instanceof 操作符的实现原理,可以发现有3个地方可以自定义操作符行为。

自定义 instanceof 操作符行为的几个方法

  • InstanceofOperator(O, C) 方法中的 let instOfHandler = C[Symbol.hasInstance]

这是对操作符右侧变量做修改

普通的对象,默认是没有 [Symbol.hasInstance] 属性的,也继承不到内置的 Function.prototype[Symbol.hasInstance]() 方法:

let o = {};
let a = new Array(); console.log(a instanceof Array) // true
console.log(a instanceof o) // Uncaught TypeError: Right-hand side of 'instanceof' is not callable

如果要避免报错,可以让 o 继承系统内置方法:

Reflect.setPrototypeOf(o, Function.prototype);

console.log(a instanceof o) // false

也可以直接给其添加 [Symbol.hasInstance] 属性:

Reflect.defineProperty(o, Symbol.hasInstance, {
value(instance){
return Array.isArray(instance);
}
}); console.log(a instanceof o) // true

一种更常规的自定义方法是:

class C {
static [Symbol.hasInstance](instance){ return false;
}
} let c = new C(); console.log(c instanceof C) // false

注意,这里定义的是静态方法,是直接挂在 C 上的方法,而不是实例方法:

Reflect.getOwnPropertyDescriptor(C, Symbol.hasInstance);
=>
Object {writable: true, enumerable: false, configurable: true, value: function}

使用传统的模拟构造函数法:

function F(){}

Reflect.defineProperty(F, Symbol.hasInstance, {
value(instance){
      return false;
}
}); let f = new F(); console.log(f instanceof F) // false

内置构造器也是可以添加 Symbol.hasInstance 方法的:

Reflect.defineProperty(Array, Symbol.hasInstance, {
value(instance){ return typeof instance === 'function';}
})
console.log(Array[Symbol.hasInstance]) // function value(instance){ return typeof instance === 'function';}
console.log([] instanceof Array) // false
console.log(function(){} instanceof Array) // true

注意,如果不使用 defineProperty 方法,而是用 [] 的方法来设置属性的话,是不生效的:

Array[Symbol.hasInstance] = function(){ return typeof instance === 'function';}
console.log(Array[Symbol.hasInstance]) // function [Symbol.hasInstance]() { [native code] }
  • OrdinaryHasInstance(C, O) 方法中的 let P = C.prototype;

也是对操作符右侧变量做修改

function F(){}

F.prototype = {};

let f = new F();

console.log(f instanceof F) // true

F.prototype = {};

console.log(f instanceof F) // false

在实例化之后,再重新设置构造函数的 prototype 属性,会导致修改之前创建的实例做 instanceof 操作时不再符合预期。

  • OrdinaryHasInstance(C, O) 方法中的 let O = Object.getPrototypeOf(O)

这是对操作符左侧变量做修改:

var a = new Array();

console.log(a instanceof Array) // true

Object.setPrototypeOf(a, Function.prototype);

console.log(a instanceof Array) // false
console.log(a instanceof Function) // true

对 a 的原型链上的任何环节做修改,都可以改变 instanceof 操作符的行为。

以上是从纯语法的方面来考虑 instanceof 操作符的行为,当涉及到浏览器环境中时,还会有一些需要特别注意的地方。

跨 frame 或 window 的情况

同一个页面中不同的 frame 之间,以及主页面与 frame 之间,有着不同的上下文执行环境,和不同的内置对象。当 instanceof 操作符涉及到多个 frame 时,就会出现一些非预期的情况:

[] instanceof window.frames[0].Array // false

因为 [] 是由主页面中的 Array 生成的,跟 frame 中的 Array 并无关联。当页面中有多个 frame 之间的数据交换时,要特别注意这一点。

instanceof 操作符实现原理解析的更多相关文章

  1. Android进阶:七、Retrofit2.0原理解析之最简流程【下】

    紧接上文Android进阶:七.Retrofit2.0原理解析之最简流程[上] 一.请求参数整理 我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢? 这 ...

  2. ThreadLocal系列(三)-TransmittableThreadLocal的使用及原理解析

    ThreadLocal系列(三)-TransmittableThreadLocal的使用及原理解析 上一篇:ThreadLocal系列(二)-InheritableThreadLocal的使用及原理解 ...

  3. [置顶] 滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理

    上周末,滴滴与360都开源了各自的插件化框架,VirtualAPK与RePlugin,作为一个插件化方面的狂热研究者,在周末就迫不及待的下载了Virtualapk框架来进行研究,本篇博客带来的是Vir ...

  4. Spring IOC设计原理解析:本文乃学习整理参考而来

    Spring IOC设计原理解析:本文乃学习整理参考而来 一. 什么是Ioc/DI? 二. Spring IOC体系结构 (1) BeanFactory (2) BeanDefinition 三. I ...

  5. ButterKnife 原理解析

    一.使用方法 1.添加依赖. implementation 'com.jakewharton:butterknife:8.8.1' annotationProcessor 'com.jakewhart ...

  6. android黑科技系列——微信抢红包插件原理解析和开发实现

    一.前言 自从几年前微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...

  7. Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析

    Spring Security 解析(六) -- 基于JWT的单点登陆(SSO)开发及原理解析   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把 ...

  8. Android中微信抢红包插件原理解析和开发实现

    一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...

  9. View Animation 运行原理解析

    Android 平台目前提供了两大类动画,在 Android 3.0 之前,一大类是 View Animation,包括 Tween animation(补间动画),Frame animation(帧 ...

随机推荐

  1. hdu 2328 Corporate Identity(kmp)

    Problem Description Beside other services, ACM helps companies to clearly state their “corporate ide ...

  2. GNOME下让QT应用使用adwaita主题统一外观

    效果展示 使用前 使用后 步骤 Arch Linux下使用AUR安装 sudo yaourt adwaita-qt4 adwaita-qt5 sudo pacman -S qtconfig-qt4 q ...

  3. A1131. Subway Map (30)

    In the big cities, the subway systems always look so complex to the visitors. To give you some sense ...

  4. 【洛谷P2585】三色二叉树

    题目大意:给定一个二叉树,可以染红绿黄三种颜色,要求父节点和子节点的颜色不同,且如果一个节点有两个子节点,那么两个子节点之间的颜色也不同.求最多和最少有多少个节点会被染成绿色. 题解:加深了对二叉树的 ...

  5. 修改 iis 的端口号: 80 与 443

    来自:https://support.microsoft.com/en-us/help/149605/how-to-change-the-tcp-port-for-iis-services Micro ...

  6. django基于中间件的IP访问频率控制

    一.中间件的代码 注意:成功时返回的是None,那样才会走视图层,返回httpresponse就直接出去了 import time from django.utils.deprecation impo ...

  7. Linux下学习摄像头使用

    刚接触Linux硬件驱动有关的项目,配置摄像头经历的一些操作 (这篇文章是刚接触Linux下V4L时作为记录记下的,感觉只有几个命令还有参考作用) 一 确定摄像头种类 确定是否符合UVC标准协议,一般 ...

  8. 生产环境Linux常用命令【随时更新】

    1. 查询文件中的关键字并高亮显示[查询当前目录关键字为elasticsearch的日志文件] find ./ -name "my-elasticsearch.log" | xar ...

  9. Springboot+WebSocket+Kafka(写着玩的)

    闹着玩的来源:前台发送消息,后台接受处理发给kafka,kafka消费者接到消息传给前台显示.联想到websocket. 最终效果如图: 页面解释: 不填写内容的话,表单值默认为Topic.Greet ...

  10. JS中的toString方法

    JS中的所有对象都具有toString方法,它把一个变量隐式转换为字符串 Number类型的对象的toString()方法比较特殊,有默认模式和基模式两种 默认模式: 无论我们用什么表示法声明数字变量 ...