js原生方法原理探究:如何实现instanceof?

Chor      2022-02-11     647

关键词:

这是JS 原生方法原理探究系列的第五篇文章。本文会介绍如何实现 instanceof 方法。

typeof 操作符返回一个表示数据类型的字符串,它可以应付常规场景下的数据类型判断。对基本数据类型 undefinedbooleanstringnumberSymbol 和引用数据类型 function 都可以正确判断,但是对 null、数组、对象等则统一返回 "object"。

比如说:

function F1(){}
function F2(){}
const obj1 = new F1()
const obj2 = new F2()
typeof obj1            // ‘object’
typeof obj2           // 'object' 

这里只能看出 obj1obj2 是对象,但不知道具体是哪个构造函数创建的对象。

但使用 instanceof 之后,就一目了然了:

console.log(obj1 instanceof F1)    // true
console.log(obj1 instanceof F2)    // false
console.log(obj2 instanceof F2)    // true

根据 MDN 的描述:

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

instanceof 运算符有两个操作数,左操作数通常是一个实例对象,它的类型可以是对象或者函数,也可以是基本类型(这种情况下不会报错,但总返回 false),右操作数通常是一个可调用的(callable)对象,我们可以直接认为它的类型应该是一个函数。

那么 instanceof 的实现原理是什么呢?从定义中我们可以看到,它的原理和原型链的机制有关,具体地说,它会拿到右操作数的原型对象,然后在左操作数上通过 __proto__ 不断查找实例的原型链,只要右操作数的 prototype 出现在左操作数的原型链上时,就返回 true。如果原型链一直查找到尽头 —— 也就是 null,还没有找到右操作数的原型,就返回 false

所以,在模拟实现中,我们只要不断遍历左操作数的原型链,取得原型链上的原型对象,并与右操作数的原型对象比较即可。

下面是具体的代码实现:

function myInstanceof(instance,constructor){
    if(typeof instance != 'object' && typeof instance != 'function' || instance == null){
        return false
    }
    if(typeof constructor != 'function'){
        throw TypeError('the right-hand-side of instanceof must be a function')
    }
    let proto = constructor.prototype
    let p = instance.__proto__
    while(p != null){
        if(p == proto){
            return true
        }
        p = p.__proto__
    }
}

这里还可以稍微扯一下题外话。原生的 instanceof 并不支持检测基本数据类型,就和上面的实现一样,当发现左操作数是基本数据类型时,会直接返回 false。有没有办法做到让它也能检测基本数据类型呢?实际上是可以的。

根据规范的说法,在调用 instanceof 的时候,实际上会去调用内部的 @@hasInstance 方法,而这个内部方法在 ES6 中通过 [Symbol.hasInstance] 暴露出来,作为右操作数(构造函数)上的静态方法,这意味着我们可以修改这个方法,自定义 instanceof 的返回值。

举个例子,这里要检测 1 instanceof Number,那么我们可以通过 Object.defineProperty改写 Number[Symbol.hasInstance] 方法:

Object.defineProperty(Number,Symbol.hasInstance,{
    value: fucntion(x){
        return typeof(x)==='object'? x instanceof Number : typeof(x) === 'number'
    }                
})

当调用 1 instanceof Number的时候,实际是调用了 Number[Symbol.hasInstance](1),而且它既可以检测基本数据类型"number",也可以检测它的包装对象:

1 instanceof Number                          // true
new Number(1) instanceof Number              // true
Number[Symbol.hasInstance](1)                // true
Number[Symbol.hasInstance](new Number(1))    // true  

如果不希望修改内置类,也可以自己实现一个 MyNumber 类:

class MyNumber{
    static [Symbol.hasInstance](x){
         return typeof(x)==='object'? x instanceof Number : typeof(x) === 'number'
    }
}

效果是一样的:

1 instanceof MyNumber                          // true
new Number(1) instanceof MyNumber              // true
MyNumber[Symbol.hasInstance](1)                // true
MyNumber[Symbol.hasInstance](new Number(1))    // true  

js原生方法原理探究如何实现浅拷贝和深拷贝?

这是JS原生方法原理探究系列的第九篇文章。本文会介绍如何手写实现浅拷贝和深拷贝。实现浅拷贝什么是浅拷贝?对原对象进行浅拷贝,会生成一个和它“一样”的新对象。但是这种拷贝只会拷贝原对象第一层的基本类型属性... 查看详情

js基础-instanceof原理及其实现

参考技术Ainstanceof的实现实际上是调用JS的内部函数[[HasInstance]]来实现的其实现原理是:只要右边变量的prototype在左边变量的原型链上即可。因此instanceof在查找过程中会遍历边变量的原型链,直到找到右边变量的prototype,如果查... 查看详情

理解javascript_07_理解instanceof实现原理

在《Javascript类型检测》一文中讲到了用instanceof来用做检测类型,让我们来回顾一下: 那么instanceof的这种行为到底是如何实现的呢,现在让我们揭开instanceof背后的迷雾。 instanceof原理照惯例,我们先来看一段代码:12345678910... 查看详情

ioscordova原生与js通讯原理

...ller,里面都有一个webview,是ios中专门显示h5页面的view.h5和原生端的交互,主要是通过插件的形式实现,原生这边写一个类继承CDVPlugin,然后实现方法,js那边封装好每个接口的方法,最终通过调用cor 查看详情

基于原生js的jsonp方法的实现

基于原生JS的jsonp方法的实现jsonp,相信大家并不陌生,是在js异步请求中解决跨域的方法之一,原理很简单,有不清楚的同学可以google下,这里就补详细解释了。在Jquery库中,jQuery直接封装有jsonp的方法,很简便,只需在ajax请求... 查看详情

ios底层原理-kvo本质探究(代码片段)

...已知类的子类NSKVONotifying_某类名,并在子类实现setter方法,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeVal 查看详情

探究entityframework如何在多个仓储层实例之间实现工作单元的实现及原理(代码片段)

前言  1、本文的前提条件:EF上下文是线程唯一,EF版本6.1.3。  2、网上已有相关API的详细介绍,本文更多的是作为我自己的个人学习研究记录。疑问  用反编译工具翻开DbContext类可以看到EF本身就是一个实现了工作单元... 查看详情

vmwarenat模式原理探究,实现虚拟机跨网段管理

vmwarenat模式原理探究:理解nat模式,我们能更加了解主机与虚拟机之间如何通信,以及虚拟机如何实现上网。以及便于我们分析虚拟机与主机无法通信和无法上外网的问题。下面通过实战:虚拟网络拓扑,抓包分析。为什么要探... 查看详情

ios底层原理-kvo本质探究(代码片段)

...已知类的子类NSKVONotifying_某类名,并在子类实现setter方法,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:... 查看详情

zuul源码分析-探究原生zuul的工作原理(代码片段)

前提最近在项目中使用了SpringCloud,基于zuul搭建了一个提供加解密、鉴权等功能的网关服务。鉴于之前没怎么使用过Zuul,于是顺便仔细阅读了它的源码。实际上,zuul原来提供的功能是很单一的:通过一个统一的Servlet入口(ZuulServ... 查看详情

js中string()、newstring()探究

...字符串,str3是个对象?什么原因呢?用typeof验证一下用instanceof验证一下可以看出str3确确实实是个String对象了再来看个有意思的事,如果给String加上自定义方法和属性呢?虽然str1、str2不是对象,但可以用String上的方法和属性再... 查看详情

原生js实现一个简单的前端路由(原理)

说一下前端路由实现的简要原理,以hash形式(也可以使用HistoryAPI来处理)为例,当url的hash发生变化时,触发hashchange注册的回调,回调中去进行不同的操作,进行不同的内容的展示。直接看代码或许更直观。1functionRouter(){2this.ro... 查看详情

vue.js学习item12–内部响应式原理探究

深入响应式原理大部分的基础内容我们已经讲到了,现在讲点底层内容。Vue.js最显著的一个功能是响应系统——模型只是普通对象,修改它则更新视图。这让状态管理非常简单且直观,不过理解它的原理也很重要,可以避... 查看详情

从架构出发探究electron运行原理(代码片段)

早期桌面应用的开发主要借助原生C/C++API进行,由于需要反复经历编译过程,且无法分离界面UI与业务代码,开发调试极为不便。后期出现的QT和WPF在一定程度上解决了界面代码分离和跨平台的问题,却依然... 查看详情

浅谈instanceof和typeof的实现原理(代码片段)

typeof实现原理typeof 一般被用于判断一个变量的类型,我们可以利用 typeof 来判断number, string, object, boolean, function, undefined,symbol 这七种类型,这种判断能帮助我们搞定一些问题,比如在判断不是obj... 查看详情

js原生实现div渐入渐出

...就可以实现。fadeIn(),fadeOut();如果我们界面没有使用jq那么原生怎么实现呢?我们讲解一下,这个原理。当我们要实现渐入的时候,首先是让隐藏的div慢慢的显示,通过让opacity慢慢从0.0(完全透明)到1.0(完全不透明)。渐出就... 查看详情

hashmap实现原理探究

前言:Java8之后新增挺多新东西,在网上找了些相关资料,关于HashMap在自己被血虐之后痛定思痛决定整理一下相关知识方便自己看。图和有些内容参考的这个文章:http://www.importnew.com/16599.htmlHashMap的存储结构如图:一个桶(bucket... 查看详情

angularjs路由path方式实现原理探究

angularjs路由https://angular.io/guide/router通过URL解释,来定位客户端生成的浏览器端视图。你可绑定路由到页面的链接上,当用户点击链接,可以浏览到相应的应用视图。Thebrowserisafamiliarmodelofapplicationnavigation:EnteraURLintheaddressbarandthebro... 查看详情