浅谈 instanceof 和 typeof 的实现原理

转载自:浅谈 instanceof 和 typeof 的实现原理

typeof 实现原理

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

let s =newString('abc');
typeof s ==='object'// true
s instanceofString// 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 来判断的话?

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

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]"
复制代码

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){
            returnfalse;    
        }
        if(leftVaule === rightProto){
            returntrue;    
        }
        leftVaule = leftVaule.__proto__ 
    }
}
复制代码

其实 instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。

看几个很有趣的例子

functionFoo(){
}
ObjectinstanceofObject// true
FunctioninstanceofFunction// true
FunctioninstanceofObject// true
FooinstanceofFoo// false
FooinstanceofObject// true
FooinstanceofFunction// 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 方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值