浅谈 instanceof 和 typeof 的实现原理

typeof 实现原理

typeof 通常被用于判断一个变量的类型,咱们能够利用 typeof 来判断number, string, object, boolean, function, undefined, symbol 这七种类型,这种判断能帮助咱们搞定一些问题,好比在判断不是 object 类型的数据的时候,typeof能比较清楚的告诉咱们具体是哪一类的类型。可是,很遗憾的一点是,typeof 在判断一个 object的数据的时候只能告诉咱们这个数据是 object, 而不能细致的具体到是哪种 object, 好比👉javascript

let s = new String('abc');
typeof s === 'object'// true
s instanceof String // true
复制代码

要想判断一个数据具体是哪种 object 的时候,咱们须要利用 instanceof 这个操做符来判断,这个咱们后面会说到。java

来谈谈关于 typeof 的原理吧,咱们能够先想一个颇有意思的问题,js 在底层是怎么存储数据的类型信息呢?或者说,一个 js 的变量,在它的底层实现中,它的类型信息是怎么实现的呢?数组

其实,js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息👉ide

  • 000:对象
  • 010:浮点数
  • 100:字符串
  • 110:布尔
  • 1:整数

but, 对于 undefinednull 来讲,这两个值的信息存储是有点特殊的。函数

null:全部机器码均为0ui

undefined:用 −2^30 整数来表示spa

因此,typeof 在判断 null 的时候就出现问题了,因为 null 的全部机器码均为0,所以直接被当作了对象来看待。prototype

然而用 instanceof 来判断的话👉code

null instanceof null // TypeError: Right-hand side of 'instanceof' is not an object
复制代码

null 直接被判断为不是 object,这也是 JavaScript 的历史遗留bug,能够参考typeofcdn

所以在用 typeof 来判断变量类型的时候,咱们须要注意,最好是用 typeof 来判断基本数据类型(包括symbol),避免对 null 的判断。

还有一个不错的判断类型的方法,就是Object.prototype.toString,咱们能够利用这个方法来对一个变量的类型来进行比较准确的判断

Object.prototype.toString.call(1) // "[object Number]"

Object.prototype.toString.call('hi') // "[object String]"

Object.prototype.toString.call({a:'hi'}) // "[object Object]"

Object.prototype.toString.call([1,'a']) // "[object Array]"

Object.prototype.toString.call(true) // "[object Boolean]"

Object.prototype.toString.call(() => {}) // "[object Function]"

Object.prototype.toString.call(null) // "[object Null]"

Object.prototype.toString.call(undefined) // "[object Undefined]"

Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"
复制代码

instanceof 操做符的实现原理

以前咱们提到了 instanceof 来判断对象的具体类型,其实 instanceof 主要的做用就是判断一个实例是否属于某种类型

let person = function () {
}
let nicole = new person()
nicole instanceof person // true
复制代码

固然,instanceof 也能够判断一个实例是不是其父类型或者祖先类型的实例。

let person = function () {
}
let programmer = function () {
}
programmer.prototype = new person()
let nicole = new programmer()
nicole instanceof person // true
nicole instanceof programmer // true
复制代码

这是 instanceof 的用法,可是 instanceof 的原理是什么呢?根据 ECMAScript 语言规范,我梳理了一下大概的思路,而后整理了一段代码以下

function new_instance_of(leftVaule, rightVaule) { 
    let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
    leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
    while (true) {
    	if (leftVaule === null) {
            return false;	
        }
        if (leftVaule === rightProto) {
            return true;	
        } 
        leftVaule = leftVaule.__proto__ 
    }
}
复制代码

其实 instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上便可。所以,instanceof 在查找的过程当中会遍历左边变量的原型链,直到找到右边变量的 prototype,若是查找失败,则会返回 false,告诉咱们左边变量并不是是右边变量的实例。

看几个颇有趣的例子

function Foo() {
}

Object instanceof Object // true
Function instanceof Function // true
Function instanceof Object // true
Foo instanceof Foo // false
Foo instanceof Object // true
Foo instanceof Function // true
复制代码

要想所有理解 instanceof 的原理,除了咱们刚刚提到的实现原理,咱们还须要知道 JavaScript 的原型继承原理。

关于原型继承的原理,我简单用一张图来表示

咱们知道每一个 JavaScript 对象均有一个隐式的 __proto__ 原型属性,而显式的原型属性是 prototype,只有 Object.prototype.__proto__ 属性在未修改的状况下为 null 值。根据图上的原理,咱们来梳理上面提到的几个有趣的 instanceof 使用的例子。

  • Object instanceof Object

    由图可知,Object 的 prototype 属性是 Object.prototype, 而因为 Object 自己是一个函数,由 Function 所建立,因此 Object.__proto__ 的值是 Function.prototype,而 Function.prototype__proto__ 属性是 Object.prototype,因此咱们能够判断出,Object instanceof Object 的结果是 true 。用代码简单的表示一下

    leftValue = Object.__proto__ = Function.prototype;
    rightValue = Object.prototype;
    // 第一次判断
    leftValue != rightValue
    leftValue = Function.prototype.__proto__ = Object.prototype
    // 第二次判断
    leftValue === rightValue
    // 返回 true
    复制代码

    Function instanceof FunctionFunction instanceof Object 的运行过程与 Object instanceof Object 相似,故再也不详说。

  • Foo instanceof Foo

    Foo 函数的 prototype 属性是 Foo.prototype,而 Foo 的 __proto__ 属性是 Function.prototype,由图可知,Foo 的原型链上并无 Foo.prototype ,所以 Foo instanceof Foo 也就返回 false 。

    咱们用代码简单的表示一下

    leftValue = Foo, rightValue = Foo
    leftValue = Foo.__proto = Function.prototype
    rightValue = Foo.prototype
    // 第一次判断
    leftValue != rightValue
    leftValue = Function.prototype.__proto__ = Object.prototype
    // 第二次判断
    leftValue != rightValue
    leftValue = Object.prototype = null
    // 第三次判断
    leftValue === null
    // 返回 false
    复制代码
  • Foo instanceof Object

    leftValue = Foo, rightValue = Object
    leftValue = Foo.__proto__ = Function.prototype
    rightValue = Object.prototype
    // 第一次判断
    leftValue != rightValue
    leftValue = Function.prototype.__proto__ = Object.prototype
    // 第二次判断
    leftValue === rightValue
    // 返回 true 
    复制代码
  • Foo instanceof Function

    leftValue = Foo, rightValue = Function
    leftValue = Foo.__proto__ = Function.prototype
    rightValue = Function.prototype
    // 第一次判断
    leftValue === rightValue
    // 返回 true 
    复制代码

总结

简单来讲,咱们使用 typeof 来判断基本数据类型是 ok 的,不过须要注意当用 typeof 来判断 null 类型时的问题,若是想要判断一个对象的具体类型能够考虑用 instanceof,可是 instanceof 也可能判断不许确,好比一个数组,他能够被 instanceof 判断为 Object。因此咱们要想比较准确的判断对象实例的类型时,能够采起 Object.prototype.toString.call 方法。

相关文章
相关标签/搜索