• 详解instanceof底层原理,从零手写一个instanceof


    壹 ❀ 引

    本道题的核心考点还是对于javascript原型的掌握程度,比如__proto__prototype相关概念,以及instanceof底层原理的理解。若你对于原型已经非常熟悉,那么可以直接开始阅读,以及在看完instanceof原理后可以尝试自己先手写。若不太熟悉,强烈建议先阅读博主关于原型的博文,不然原型链这点可能会把你绕晕。

    简单易通,七千字长文助你彻底弄懂原型与原型链。

    蛋生鸡鸡生蛋的哲学探讨,Javascript中Function与Object究竟谁出现的更早?

    那么请跟随我的思路,本文开始。

    贰 ❀ instanceof底层查找原理

    要想实现一个东西,我们总得先知道它是个啥东西,先从基本概念说起,instanceof运算符语法如下:

    object instanceof constructor
    

    其中,object表示某个实例对象,constructor表示某个构造函数。它的作用是用于检查constructor.prototype是否存在于参数object的原型链上。

    翻译直白一点,假设我们是A instanceof B,那么就是看B的原型,是否出现在A的原型链上。

    我们来通过几个例子加深这段概念的理解:

    function Person(name) {
      this.name = name;
    };
    Person.prototype.sayName = function () {
      return this.name
    };
    const p = new Person('听风是风');
    p.sayName(); // 听风是风
    

    此时用instanceof来检验:

    p instanceof Person; // true
    

    为什么为true,用概念来解释,Person的原型也就是Person.prototype,它直接等于p.__proto__,既然都相等,那自然有出现在p的原型上,所以上述代码等同于:

    p.__proto__ === Person.prototype;// true
    

    考虑到有同学可能忘记了原型相关的概念,简单复习下,javascript中,除了undefinednull之外的万物皆为对象,不管你是字符串,数组,都会有__proto__属性指向创建自己的构造函数的原型,也就是prototype。所以上述例子实例p__proto__指向创建自己的构造函数Person.prototype,既然相当,那instanceoftrue合情合理。

    来看第二个例子:

    p instanceof Object;// true
    

    还是上面的例子,只是此时我们判断Object.prototype有没有出现在实例p的原型链上,要解释这个问题,我们就得明白原型链的意思。

    还记得上面p.sayName()调用吗?p自身其实并没有这个方法,但能成功调用,这是因为p能通过p.__proto__访问到构造函数的原型,从而使用到这个方法,那假设Person.prototype也没有呢?由于原型prototype本质就是一个对象,既然你是对象,那么原型肯定也有自己的__proto__,用于指向创建自己的构造器的原型,而这个查找过程就形成了一个链,当然原型链也是有顶点的也就是是null,而这个查找的过程也就是所谓的原型链了。

    解释了原型链,我们来看看Person.prototype__proto__是谁,如下图:

    这里的[[Prototype]]其实就是我们所说的__proto__,它指向了创建自己的构造器的原型,所以也是个对象,而原型prototype上都会有一个constructor属性,这个玩意可以理解为原型的标识,用于标识它是哪个构造器的原型,如上图,很明显这个构造器就是Object了。

    所以上面的instanceof本质上等同于:

    // p.__proto__ 找到了Person的原型
    // 原型也是个对象,p.__proto__.__proto__又找到了Object的原型
    p.__proto__.__proto__ === Object.prototype;// true
    

    而关于我们所说的原型链顶端是null的问题,因为你一个原型链不可能像死循环一样没有终点,只要你是{}这种对象类型,创建你们的造世主都是构造器Object(),所以这种对象都能访问到Object.prototype,我们都说Object是普通对象的造世主了,它的原型很显然没办法再往上找,终点就被设置为null了,具体可见下图。

    叁 ❀ 手写instanceof

    我们在上面花了较大的篇幅用来解释instanceof的含义以及底层查找过程,毕竟知道了原型我们才知道如何去手写实现它。说到底,以A instanceof B为例,本质做的就是看递归A的原型链,不断的.__proto__一层层网上找一直找到null为止,看看有没有某一处的.__proto__等于B.prototype,如果有就返回true,找到null了都不相等那就返回false,既然知道了原理,一个递归搞定,实现如下:

    const instanceof_ = (A, B) => {
      // 前置判断,不符合直接访问false
      if (typeof A !== 'object' || A === null || typeof B !== 'function') {
        return false;
      };
      const prototype = B.prototype;
      let __proto__ = A.__proto__;
      while (true) {
        // 假设找到顶端了,返回false
        if (__proto__ === null) {
          return false;
        };
    
        // 假设相等,返回true
        if (prototype === __proto__) {
          return true;
        };
    
        // 一直顺着__proto__往上找
        __proto__ = __proto__.__proto__;
      }
    }
    console.log(instanceof_(p, Person)); // true
    console.log(instanceof_(1, Number)); // false
    console.log(instanceof_(new Number(1), Number)); // true
    

    有同学可能不理解这个函数内部的前置条件为什么这么写,这里我再解释下,当然如果你原型的理解非常透彻,这部分解释就可以直接跳过了。

    根据语法A instanceof B,我们可以得知两点,首先A得是一个实例对象,B得是一个构造器函数。

    为什么这么说?因为在javascript中只有函数才有prototype属性,函数是一等公民,老大哥,这就是它的特殊之处,因此只要B不是函数类型,那就不用判断了,我这里是直接返回了false,你其实可以抛错,这里我们就不拘小节了。

    那为什么我们说A应该是个对象呢?看下面这个例子:

    1 instanceof Number;// false
    new Number(1) onstanceof Number;// true
    

    其实语法我们就知道了,A一定得是个对象,上面例子1很明显不算是一个标准的对象,它算是一个包装对象,因为不算是数字还是字符串,它们也有__proto__属性,不然数字字符串上的方法哪来的,它们本质还是对象,关于这点可以阅读博主关于原型的博客,里面有解释。

    1..__proto__ === Number.prototype;// true
    '听风是风'.__proto__ === String.prototype;// true
    

    我们实现instanceof原理就是判断__proto__与构造器原型是否相等,很明显如果这样写但不对基本类型做判断,返回的结果就与原生instanceof不一致了,因此才做了typeof A !== 'object'的限制,而typeof null === 'object'算是javascript中公认的设计缺陷了,因此就加了不等于null的判断了。

    肆 ❀ 总

    你看,说是实现一个instanceof,本质上考核的还是对于原型概念的理解,如果你之前对于原型掌握的非常熟悉,我相信本文阅读下来你会非常的流程,毕竟博主自己写下来就是行云流水 = =,原型这块可是老熟了,好了,本文就到这里结束了。

  • 相关阅读:
    Qt 3D教程(二)初步显示3D的内容
    linux关于ftp查看不到文件列表的问题
    Mahout推荐算法API具体解释【一起学Mahout】
    GBK编码具体解析(附GBK码位分布图)
    HTML5实战与剖析之媒体元素(3、媒体元素的事件及方法)
    CentOS添加swap分区
    Transaction: atomicity, consistency, separability, persistence
    redis ins 调试
    jemalloc/jemalloc.h: No such file or directory
    MySQL表设计基础
  • 原文地址:https://www.cnblogs.com/echolun/p/16032403.html
Copyright © 2020-2023  润新知