本文会介绍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. [模板] Manacher(马拉车)算法

    用途 求回文子串 做法 先考虑回文子串以某字符为中心的情况,即长度为奇数 推着做,记rad[i]为以i位置为中心的最大半径(包含中点) 考虑怎么求rad[i].找之前的一个右端点最靠右的位置p,设它的 ...

  2. CodeForces - 589A(二分+贪心)

    题目链接:http://codeforces.com/problemset/problem/589/F 题目大意:一位美食家进入宴会厅,厨师为客人提供了n道菜.美食家知道时间表:每个菜肴都将供应. 对 ...

  3. Nlog 简单的快速攻略

    废话不多说直接进入正题. 1.在项目中加入Nlog的应用 安装后会出现两个文件 2.我们打开Nlog.config配置文件设置日志记录 <?xml version="1.0" ...

  4. Python模块之time、random、os、sys、序列化、re

    Time模块 和时间有关系的我们就要用到时间模块.在使用模块之前,应该首先导入这个模块. #常用方法 1.time.sleep(secs) (线程)推迟指定的时间运行.单位为秒. 2.time.tim ...

  5. numpy的使用方法

    一.numpy快速入门 1.什么是numpy: numpy是python的一个矩阵类型,提供了大量矩阵处理的函数,非正式来说,就是一个使运算更容易,执行更迅速的库,因为它的内部运算是通过c语言而不是p ...

  6. 第十九节,使用RNN实现一个退位减法器

    退位减法具有RNN的特性,即输入的两个数相减时,一旦发生退位运算,需要将中间状态保存起来,当高位的数传入时将退位标志一并传入参与计算. 我们在做减法运算时候,把减数和被减数转换为二进制然后进行运算.我 ...

  7. Spring3 (事务管理)

    简介: 1.事务管理.2.整合Junit.3.整和Web 1       事务管理 1.1   回顾事务 l  事务:一组业务操作ABCD,要么全部成功,要么全部不成功. l  特性:ACID 原子性 ...

  8. WORD2010如何把全角字母和数字批量转换成半角

    个人觉得全角字符看起来相当别扭,如果文档中存在大量全角形式的字母和数字,要如何把它们全部转化成半角的呢?   全角和半角   全角是指一个字符占用两个标准字符位置的状态.汉字字符和规定了全角的英文字符 ...

  9. (二叉树 BFS) leetcode103. Binary Tree Zigzag Level Order Traversal

    Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to ...

  10. java将long数据转为int类型的方法

    二.调用intValue()方法 [java] long ll = 300000; int ii= new Long(ll).intValue(); 三.先把long转换成字符串String,然后在转 ...