博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
instanceof 操作符实现原理解析
阅读量:7046 次
发布时间:2019-06-28

本文共 4320 字,大约阅读时间需要 14 分钟。

本文会介绍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) // trueconsole.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) // falseconsole.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) // trueF.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) // trueObject.setPrototypeOf(a, Function.prototype);console.log(a instanceof Array) // falseconsole.log(a instanceof Function) // true

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

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

跨 frame 或 window 的情况

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

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

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

转载于:https://www.cnblogs.com/zhaoran/p/7284749.html

你可能感兴趣的文章
JeeSite环境搭建及运行和打包(master20161117)
查看>>
Flink -- Failover
查看>>
QT笔记之解决QT5.2.0和VS2012中文乱码 以及在Qt Creator中文报错
查看>>
Hibernate 执行原始SQL语句
查看>>
在 SQL Server 2005 中配置数据库邮件
查看>>
Android 多个Fragment嵌套导致的三大BUG
查看>>
Eclipse下的Maven
查看>>
Java中使用poi导入、导出Excel
查看>>
quartus ii工程文件的分析
查看>>
Nginx %00空字节执行php漏洞
查看>>
数据包的转发流程
查看>>
深入浅出KnockoutJS
查看>>
程序员应该具备哪些必备技能
查看>>
工厂模式
查看>>
临时表空间的恢复
查看>>
android KeyEvent for dot "."
查看>>
第九篇:使用 AdaBoost 元算法提高分类器性能
查看>>
你的身份信息已失效,请重新输入密码登录
查看>>
jQuery------$.each()遍历类方法
查看>>
Serviceability
查看>>