浅谈 instanceof 和 typeof 的实现原理

一.typeof 的实现原理

typeof 一般被用于判断一个 变量 的类型,

我们可以利用 typeof 来判断 number , string , object , boolean , function , undefined , symbol 这七种类型。 

这种判断能帮助我们搞定一些问题:比如在判断非object 类型的数据的时候,typeof能比较清楚的告诉我们具体是哪一类的类型。 但是,很遗憾的一点是,typeof 在判断一个 object的数据的时候只能告诉我们这个数据是 object, 而 不能细致的具体到是哪一种 object , 比如

let a = 'wangzong';
let s = new String('wangzong');

typeof a === 'String' //true
typeof s === 'object' //true

a instanceof String //false
s instanceof String //true
复制代码

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

来谈谈关于 typeof 的原理吧~

我们可以先想一个很有意思的问题, js 在底层是怎么存储数据的类型信息呢? 或者说,一个 js 的变量,在它的底层实现中,它的类型信息是怎么实现的呢?

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

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

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

null :所有机器码均为0

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

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

然而用 instanceof 来判断的话

typeof null  //object

null instanceof null
复制代码

null 直接被判断为不是 object ,这也是 JavaScript 的历史遗留bug,可以参考typeof。

因此在用 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]"
复制代码

在 ECMAScript 2015 之前,typeof总是保证为任何操作数返回一个字符串。

但是, 除了非提升,块作用域的let和const之外 ,在声明之前对 块中的let和const变量 使用typeof会抛出一个ReferenceError(查询错误)。 这与未声明的变量形成对比,typeof会返回“undefined”。 块作用域变量 在块的 头部 处于“ 暂时死区 ”,直到被初始化,在这期间,如果变量被访问将会引发错误。

typeof undeclaredVariable === 'undefined';
typeof newLetVariable; let newLetVariable; // ReferenceError
typeof newConstVariable; const newConstVariable = 'hello'; // ReferenceError
复制代码

万事都有例外

二.instanceof 操作符的实现原理

之前我们提到了 instanceof 来判断对象的具体类型,

其实instanceof运算符用于 测试构造函数的prototype属性 是否出现在 对象的原型链中 的任何位置

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

当然,instanceof 也可以判断 一个实例是否是其 父类型或者祖先类型 的实例

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

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

function new_instance_of(leftVaule, rightVaule) {
    let rightProto = rightVaule.prototype;
    leftVaule = leftVaule.__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
Function instanceof Function
Function instanceof Object
Foo instanceof Foo
Foo instanceof Object
Foo instanceof Function
复制代码

要想全部理解 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 方法。

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章