一文搞懂instanceof实现的原理是什么!

简介: 前言类型的判断可以说在我们前端开发过程中无处不在,特别是在 Typescript 还未推出之前,我们在 JS 里面做类型判断显得就更加重要了。判断数据类型的方式有特别多,比如大家常用的 typeof、instanceof、Object.prototype.tostring.call()等等,那么每一种判断数据类的方法大家知道其中的原理吗?比如说 instanceof 的原理,今天我们就聊一聊 instanceof 是如何判断数据类型的。

1.基本概念


想要了解 instanceof 的原理,我们至少应该知道它的基本概念吧,我们可以先来看看官网是如何解释它的。


官网解释:

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


虽然官方的解释只有简短的一句话,但是对于许多人来说还是挺难理解的,不过我们可以抓出这句话中的几个关键点:

  • 运算符
  • 构造函数的 prototype
  • 对象原型链


很明显,instanceof 与原型和原型链有关,所以强烈建议小伙伴们先去学一学原型和原型链的相关知识。


为了让小伙伴现有一个大概理解,我们用我们自己的话简单说一下 instance


通俗的解释:

instanceof 是一个运算符,它可以用来判断某一个对象的类型,具体原理就是利用了原型和原型链。


基本用法:

A instanceof B  // true or false


上段代码中的 A 就是我们需要判断类型的对象,B 就是官方所说的构造函数,形如我们的 ObjectFunction 都可以称之为构造函数。


2.与 typeof 对比


我们判断类型的时候通常是将 typeofinstanceof 结合使用,虽然它们都可以判断数据类型,但是它们还是有很多不同点的,如下:

  • typeof:主要用来判断基础数据类型,比如:NumberString 等等。
  • instanceof:主要用来判断对象数据类型,比如 FunctionArray 等等。
  • typeof 直接返回数据类型,而 instanceof 重在判断,它返回布尔值。


我们来看一段代码大家可能会更好理解一些。


代码如下:

<script>
  function say() { };
  // typeof 判断数据类型
  console.log(typeof '小猪课堂'); // string
  console.log(typeof 100); // number
  console.log(typeof true); // boolean
  console.log(typeof undefined); // undefined
  console.log(typeof {}); // object
  console.log(typeof []); // object
  console.log(typeof null); // object
  console.log(typeof say); // function
  // instanceof 判断数据类型
  let a = new String('123');
  let b = new say();
  console.log('123' instanceof String); // false
  console.log(a instanceof String); // true
  console.log([] instanceof Array); // true
  console.log(b instanceof say); // true
</script>


从上段代码我们可以看出 typeof 只能判断基础数据类型(null 除外),当判断其它数据类型时,它总是返回 object 或者 function


instanceof 可以用来判断对象数据类型,返回的是布尔值。


3.instanceof 特点


上节中有一段代码我们可以拿出来再看一看:

let b = new say();
console.log(b instanceof say); // true


上段代码中 b 是实例对象,say 是构造函数,我们利用 instanceof 来进行判断时,返回的 true,由此我们可以总结出 instanceof 如下特点:

  • instanceof 左侧是一个实例对象,右侧是一个构造函数。
  • 如果实例对象属于构造函数,那么 instanceof 就会返回 true


我们判断类型是使用的 ArrayObjectString 等等其实就是一个构造函数。


总结:

由上可以得出,判断数据类型并不是 instanceof 最准确的说法,它主要是用来判断实例对象与构造函数之间的关系的。而判断数据类型只是我们利用它的特点变相实现罢了。


4.instanceof 原理


到这里我们知道 instanceof 其实不仅仅是用来判断数据类型的,它实际上是用来判断一个实例对象与一个构造函数之间的关系的。


那么我们通常如何判断一个实例对象与一个构造函数之间的关系的呢?


答案就是利用原型和原型链! 我们都知道每一个函数都有一个显式原型 prototype,每一个对象都有一个隐式原型__proto__,当我们对象的原型链中存在构造函数的显式原型 prototype 时,我们就可以确定它们之间时存在关系的。


更简单的说法:

  • 我们拿到 instanceof 左侧对象的原型链
  • 再拿到 instanceof 右侧构造函数的显式原型 prototype
  • 如果原型链中存在显式原型 prototypeinstanceof 返回 true,否则返回 false


如果大家对上面的说明看的模糊,那么快去补一补原型和原型链的知识。

我们可以简单实现一个 instanceof 函数,大家就更容易理解了。


代码如下:

/**
  * @description 判断对象是否属于某个构造函数
  * @prams left: 实例对象  right: 构造函数
  * @return boolean
*/
function myInstanceof(left, right) {
  let rightPrototype = right.prototype; // 获取构造函数的显式原型
  let leftProto = left.__proto__; // 获取实例对象的隐式原型
  while (true) {
    // 说明到原型链顶端,还未找到,返回 false
    if (leftProto === null) {
      return false;
    }
    // 隐式原型与显式原型相等
    if (leftProto === rightPrototype) {
      return true;
    }
    // 获取隐式原型的隐式原型,重新赋值给 leftProto
    leftProto = leftProto.__proto__
  }
}


代码比较简单,主要就是需要循环实例对象的原型链。

我们回过头再看一遍代码:

let b = new say();
console.log(b instanceof say); // true


上段代码为什么会返回 true 呢,其实是因为在 new 的操作过程中,有一步操作便是将 say()构造函数的显式原型 prototype 赋值给了 b 的隐式原型__proto__,所以我们利用 instance 判断时,必然会满足 leftProto === rightPrototype 条件。


至于 new 操作符具体做了什么,大家可以去参考我的另一篇文章。


大家也可以直接打印看看结果:

console.log(b.__proto__ === say.prototype); // true


5.补充


instanceof 判断数组时,如果把它归纳为 Array 是返回 true,如果把它归纳为 Object 也是返回 true 的。


代码如下:

let array = [1, 2, 3]
console.log(array instanceof Array); // true
console.log(array instanceof Object); // true


究其原因其实是我们的数组也是一个对象,只不过这个对象稍微特殊一点罢了,大家也可以把数组的原型打印出来看看,一下就会明白了。



15.png


总结


看到这儿,你再回过头去看看官网关于 instanceof 的解释,相信你会恍然大悟!

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


如果觉得文章太繁琐或者没看懂,可以观看视频: 小猪课堂

相关文章
|
7月前
02 # 手写 instanceof 的原理
02 # 手写 instanceof 的原理
84 0
【面试题精讲】Throwable 类常用方法有哪些?
【面试题精讲】Throwable 类常用方法有哪些?
|
1月前
|
安全 Java Python
instanceof 的实现原理
`instanceof` 是 Java 中的一个关键字,用于判断一个对象是否属于某个类或其子类。其原理是通过检查对象的类层次结构,确定该对象是否是指定类的实例。具体实现涉及对象头中的类元数据信息和类加载器的作用。
|
7月前
|
存储 消息中间件 安全
27道 Handler 经典面试题,你能答出多少?
27道 Handler 经典面试题,你能答出多少?
|
4月前
|
Java
【Java基础面试二十一】、说一说hashCode()和equals()的关系
这篇文章讨论了Java中`hashCode()`和`equals()`方法之间的关系,强调如果两个对象相等,它们必须有相同的哈希码,但有相同哈希码的对象未必相等,并解释了这一关系在HashSet集合中判断元素是否重复的应用场景。
【Java基础面试二十一】、说一说hashCode()和equals()的关系
|
4月前
|
Java
【Java基础面试十八】、说一说重写与重载的区别
这篇文章阐述了Java中重写与重载的区别:重载是同一个类中方法名相同但参数列表不同的方法之间的关系,而重写是子类中方法与父类中相同方法名和参数列表的方法之间的关系,且子类的返回值应小于等于父类,访问修饰符应大于等于父类。
【Java基础面试十八】、说一说重写与重载的区别
|
4月前
|
Java
【Java基础面试二十三】、==和equals()有什么区别?
这篇文章区分了Java中的`==`运算符和`equals()`方法:`==`用于基本数据类型时比较值是否相等,用于引用类型时比较内存地址是否相同;而`equals()`默认实现按内存地址比较,但通常被重写以根据对象内容比较是否相等。
【Java基础面试二十三】、==和equals()有什么区别?
|
4月前
|
Java
【Java基础面试二十二】、为什么要重写hashCode()和equals()?
这篇文章解释了为什么需要重写`hashCode()`和`equals()`方法:因为Object类的`equals()`默认使用`==`比较,这在业务中通常是不够的,我们需要根据对象内容来比较相等性;同时,为了保持`hashCode()`与`equals()`的联动关系,一旦重写了`equals()`,通常也需要重写`hashCode()`。
【Java基础面试二十二】、为什么要重写hashCode()和equals()?
|
7月前
|
Java 编译器
还没搞懂重写和重载吗?这篇文章可以帮助你
还没搞懂重写和重载吗?这篇文章可以帮助你
50 1
|
7月前
|
Java 程序员 编译器
【JavaEE初阶】 Synchronized 原理详解
【JavaEE初阶】 Synchronized 原理详解