instanceof 操作符实现原理解析
本文会介绍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 操作符实现原理解析的更多相关文章
- Android进阶:七、Retrofit2.0原理解析之最简流程【下】
紧接上文Android进阶:七.Retrofit2.0原理解析之最简流程[上] 一.请求参数整理 我们定义的接口已经被实现,但是我们还是不知道我们注解的请求方式,参数类型等是如何发起网络请求的呢? 这 ...
- ThreadLocal系列(三)-TransmittableThreadLocal的使用及原理解析
ThreadLocal系列(三)-TransmittableThreadLocal的使用及原理解析 上一篇:ThreadLocal系列(二)-InheritableThreadLocal的使用及原理解 ...
- [置顶]
滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理
上周末,滴滴与360都开源了各自的插件化框架,VirtualAPK与RePlugin,作为一个插件化方面的狂热研究者,在周末就迫不及待的下载了Virtualapk框架来进行研究,本篇博客带来的是Vir ...
- Spring IOC设计原理解析:本文乃学习整理参考而来
Spring IOC设计原理解析:本文乃学习整理参考而来 一. 什么是Ioc/DI? 二. Spring IOC体系结构 (1) BeanFactory (2) BeanDefinition 三. I ...
- ButterKnife 原理解析
一.使用方法 1.添加依赖. implementation 'com.jakewharton:butterknife:8.8.1' annotationProcessor 'com.jakewhart ...
- android黑科技系列——微信抢红包插件原理解析和开发实现
一.前言 自从几年前微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...
- Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析
Spring Security 解析(六) -- 基于JWT的单点登陆(SSO)开发及原理解析 在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把 ...
- Android中微信抢红包插件原理解析和开发实现
一.前言 自从去年中微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...
- View Animation 运行原理解析
Android 平台目前提供了两大类动画,在 Android 3.0 之前,一大类是 View Animation,包括 Tween animation(补间动画),Frame animation(帧 ...
随机推荐
- css元素溢出
当子元素的尺寸超过父元素的尺寸时,需要设置父元素显示溢出的子元素的方式,设置的方法是通过overflow属性来设置. overflow的设置项: 1.visible 默认值.内容不会被修剪,会呈现在元 ...
- STL的相关知识
STL简介: STL(Standard Template Library,标准模版库)以模板类和模版函数的形式为程序员提供了各种数据结构和算法的实现,程序员通过利用STL,可以在代码空间.执行时间和编 ...
- p1184高手之在一起
P1184 题目背景 高手是可以复活的,这点我们大家都知道. 题目描述 高手列出了一个详尽的日程表,这次他要追求的则是一个心灵纯洁的小萝莉.他和她都是要上课的,但是也会有时间空闲,于是高手决定无时无刻 ...
- apache StringUtils 工具类
// org.apache.commons.lang3.StringUtils // 1.IsEmpty/IsBlank - checks if a String contains text 检查是否 ...
- WIndows下将文件夹映射为磁盘
subst 盘符 文件夹路径 [/d] 映射 将e:\work映射为z:盘,使用subst z: e:\work 取消映射 取消z盘映射,使用subst z: /d 参考资料:http://mp.we ...
- A1013. Battle Over Cities
It is vitally important to have all the cities connected by highways in a war. If a city is occupied ...
- 收集JavaScript中常用的方法函数
本文中,收集了一些比较常用的Javascript函数,希望对学习JS的朋友们有所帮助. 1. 字符串长度截取 function cutstr(str, len) { var temp, icount ...
- Mysql 远程连接服务器
#授权外部访问 GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'root' WITH GRANT OPTION; #清空mysql缓存 ...
- 高DPI下界面错乱的解决方法和原理
来源: http://bbs.csdn.net/topics/370177760 我在win32 + c写的界面中解决办法,就是把字体的字号给固定了,这样做的结果就是,不管dpi是否有改变,界面中控件 ...
- 终于解决了用JAVA写窗口程序在不同的windows界面下的显示保持一致。
好像是两三年前的时候发现这个问题. 由于在windows经典界面与windows xp界面下,窗口的标题栏的高度是不一样的. 所以我们在用Java写GUI程序的时候,会遇到一个问题. 当我把一个JFr ...