当前位置 博文首页 > _Fatman:深入了解typeof与instanceof的使用场景及注意事项

    _Fatman:深入了解typeof与instanceof的使用场景及注意事项

    作者:_Fatman 时间:2021-02-09 16:27

    如何判断数据属于哪种类型是JavaScript中非常重要的一个知识点,其中最常用的两个方法就是分别使用typeof与instanceof这两个关键字来对数据的类型进行判断。 typeof与instanceof虽然都可以用来对数据所属的类型进行判断,但是它们之间还是存在差异的,而这种差异主要存在于两个方面: 1.作用点的不同; typeof主要用来判断基础数据类型,instanceof则是用来判断引用数据类型。 2.底层逻辑的不同; typeof是根据数据在存储单元中的类型标签来判断数据的类型,instanceof则是根据函数的prototype属性值是否存在于对象的原型链上来判断数据的类型。

    JavaScript中的数据类型分为两类,undefined,number,boolean,string,symbol,bigint,null[1]组成的基础类型Object、Function、Array等类型组成的引用类型

    在这里插入图片描述
    如何判断数据属于哪种类型是JavaScript中非常重要的一个知识点,其中最常用的两个方法就是分别使用typeof与instanceof这两个关键字来对数据的类型进行判断。

    typeof与instanceof虽然都可以用来对数据所属的类型进行判断,但是它们之间还是存在差异的,而这种差异主要存在于两个方面:
    1.作用点的不同;
    typeof主要用来判断基础数据类型,instanceof则是用来判断引用数据类型。

    2.底层逻辑的不同;
    typeof是根据数据在存储单元中的类型标签来判断数据的类型,instanceof则是根据函数的prototype属性值是否存在于对象的原型链上来判断数据的类型。

    typeof判断数据类型共有8个值,它们分别是‘undefined’、‘number’、‘boolean’、‘string’、‘symbol’、‘bigint’、‘object’和‘function’。

    使用typeof就可以很好的判断数据类型undefined、number、boolean、string、symbol和bigint。
    不过在判断基础类型null时,使用typeof便不再准确了。这个问题的产生可以追溯到JavaScript的第一个版本[2],在这个版本中,单个值在栈中占用32位的存储单元,而这32位的存储单元又可以划分为类型标签(1-3位)和实际数据,类型标签存储于低位中,具体可以分成5种:
    1.当第0位、第1位和第2位皆为0时,typeof判断此数据类型为’object’;
    2.当第0位为1时,typeof判断此数据类型为’number(整数)’;
    3.当第0位与第2位皆为0,而第1位为1时,typeof判断此数据类型为’number(浮点数)’;
    4.当第0位与第1位皆为0,而第2位为1时,typeof判断此数据类型为’string’;
    5.当第1位与第2位皆为1,而第0位为0时,typeof判断此数据类型为’boolean’;

    此外还有两种特殊情况:
    undefined:整数?2^30 (整数范围之外的数字)
    null:第0位到第31位皆为0

    当数据值为null时,正好满足当第0位、第1位和第2位皆为0时,typeof判断类型为’object’的条件,所以typeof null === 'object'的结果为true。

    使用typeof判断function也是存在问题的:
    在 IE 6, 7 和 8 上,很多宿主对象是对象而不是函数。
    例如:

    typeof alert === 'object';//true
    

    还有老版本Firefox中的

    typeof /[0-9]/ === 'function';//true
    

    像这种的还有很多,就不一样举例了,多半是浏览器实现差异,现在已经统一标准了。

    我在ie11上运行的结果:

    typeof alert === 'function';//true
    

    在当前最新版Firefox上运行的结果:

    typeof reg === 'object';//true
    

    typeof在判断引用类型还存在一些问题,例如:

    typeof {} === 'object';//true
    typeof [] === 'object';//true
    typeof window === 'object';//true
    typeof new Map() === 'object';//true
    

    这个时候如果想要知道更详细的信息就需要使用instanceof关键字了。

    alert instanceof Function;//true
    ({}) instanceof Object;//true
    ([]) instanceof Array;//true
    window instanceof Window;//true
    (new Map()) instanceof Map;//true
    

    使用instanceof运算符,我们可以清楚的判断对象的原型链上是否存在函数的prototype属性值。

    不过instanceof也并不能完全可信,比如通过Symbol.hasInstance属性可以影响instanceof的判断结果:

    function Person(){
    }
    
    Object.defineProperty(Person,Symbol.hasInstance,{
        value : function(){
            return false;
        }
    })
    
    let p = new Person();
    
    p instanceof Person;//false
    

    但是Symbol.hasInstance属性并不会影响到数据的原型链,使用自定义的myInstanceof方法[3]不会受到Symbol.hasInstance属性的影响:

    /**
    * obj 变量
    * fn 构造函数
    */
    function myInstanceof(obj,fn){
        let _prototype = Object.getPrototypeOf(obj);
        if(null === _prototype){
            return false;
        }
        let _constructor = _prototype.constructor;
        if(_constructor === fn){
            return true;
        }
        return myInstanceof(_prototype,fn);
    }
    
    function Person(){
    }
    
    Object.defineProperty(Person,Symbol.hasInstance,{
        value : function(){
            return false;
        }
    })
    
    let p = new Person();
    
    p instanceof Person;//false
    
    myInstanceof(p,Person);//true
    

    自定义的myInstanceof方法改进版:

    /**
    * obj 变量
    * fn 构造函数
    */
    function myInstanceof(obj,fn){
        let _prototype = Object.getPrototypeOf(obj);
        if(null === _prototype){
            return false;
        }
        let _constructor = _prototype.constructor;
        if(_constructor[Symbol.hasInstance]){
            return _constructor[Symbol.hasInstance](obj);
        }
        if(_constructor === fn){
            return true;
        }
        return myInstanceof(_prototype,fn);
    }
    
    function Person(){
    }
    
    Object.defineProperty(Person,Symbol.hasInstance,{
        value : function(){
            return false;
        }
    })
    
    let p = new Person();
    
    p instanceof Person;//false
    
    myInstanceof(p,Person);//false
    

    数据和类型不在一个全局变量下时instanceof也会输出错误的结果

    比方说现在定义两个html文件,分别为main.html和iframe.html,代码如下:

    main.html

    <!DOCTYPE html>
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
            <title>main</title>      
            <script type="text/javascript">
                window.onload = function(){
                    console.log('document.domain : ' + document.domain);
                    let iframe = document.documentElement.getElementsByTagName('iframe')[0];
                    let p = iframe.contentWindow.window.document.documentElement.getElementsByTagName('p')[0];
                    console.log('p instanceof Object : ' + (p instanceof Object));//p instanceof Object : false
                }
            </script>  
        </head>
        <body>
            <iframe src="./.html"></iframe>
        </body>
    </html>
    

    iframe.html

    <!DOCTYPE html>
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
            <title>iframe</title>       
        </head>
        <body>
            <p>1</p>
        </body>
    </html>
    

    npx http-server打开main.html后,得到结果p instanceof Object : false

    图片

    造成这种结果原因在于:

    iframe.contentWindow.window.Object === window.Object;//false
    

    那浏览器要为什么这样呢?都用一个Object构造函数不好吗?

    我们这样看:

    main.html打开一个window(我们现在叫它main_window),iframe.html打开一个window(我们现在叫它iframe_window)。
    我们现在从iframe_window中获取一个p元素对象,它的原型链为—HTMLParagraphElement.prototype -> HTMLElement.prototype -> Element.prototype -> Node.prototype -> EventTarget.prototype -> Object.prototype
    然后我们在main.html文件中,去判断p instanceof Object,也就是判断 p instanceof main_window.Object。p元素是在iframe.html文件中被构造的,所以p instanceof iframe_window.Object === true。如果想让p instanceof main_window.Object === true
    那么要满足iframe_window.Object === main_window.Object。但是这个条件肯定是不能满足的。如果iframe_window.Object === main_window.Object,那么我在iframe.html文件中修改Object函数,就会作用到main.html中,这样会引发很严重的安全的问题,以及一系列莫名其妙的bug。

    所以不同的全局变量下的同名构造函数并不是同一个函数,这导致了instanceof在数据与函数位于不同全局变量下时会判断出错

    不过在这个例子使用typeof倒是可以解决问题,只是要记住判断数据是不是null类型:

    null !== p && typeof p === 'object';//true
    

    因为typeof判断的是存储单元中的标签类型,所以不会受到影响。

    参考

    • [1] The history of “typeof null”
    • [2] MDN-typeof
    • [3] 各浏览器对typeof运算符的实现差异

    1. 一般基础类型我是用小写字母开头。 ??

    2. 此时还没有Symbol、BigInt,故不在讨论范围内。 ??

    3. myInstanceof方法的由来。 ??

    bk
    下一篇:没有了