美文网首页
深入了解JS类型判断

深入了解JS类型判断

作者: 前端开发博客 | 来源:发表于2020-11-01 16:37 被阅读0次

    关注『前端开发博客』公众号,回复 加群,加入高薪前端群

    JS中判断数据类型的方式有很多

    • typeof
    • Object.prototype.toString
    • instanceof
    • Array.isArray

    一、回顾

    JS数据类型分为基本类型和引用类型。
    基本类型

    • undefined
    • null
    • Number
    • String
    • Boolean
    • Symbol

    引用类型

    • Object
    • Function

    函数是一种特殊的对象,即可调用的对象。

    二、typeof

    2.1 语法

    typeof操作符可以区分基本类型,函数和对象。

    console.log(typeof null) // object
    console.log(typeof undefined) // undefined
    console.log(typeof 1) // number
    console.log(typeof 1.2) // number
    console.log(typeof "hello") // string
    console.log(typeof true) // boolean
    console.log(typeof Symbol()) // symbol
    console.log(typeof (() => {})) // function
    console.log(typeof {}) // object
    console.log(typeof []) // object
    console.log(typeof /abc/) // object
    console.log(typeof new Date()) // object
    
    1. typeof有个明显的bug就是typeof nullobject;
    2. typeof无法区分各种内置的对象,如Array, Date等。

    2.2 原理

    JS是动态类型的变量,每个变量在存储时除了存储变量值外,还需要存储变量的类型。JS里使用32位(bit)存储变量信息。低位的1~3个bit存储变量类型信息,叫做类型标签(type tag)

    .... XXXX X000 // object
    .... XXXX XXX1 // int
    .... XXXX X010 // double
    .... XXXX X100 // string
    .... XXXX X110 // boolean
    
    1. 只有int类型的type tag使用1个bit,并且取值为1,其他都是3个bit, 并且低位为0。这样可以通过type tag低位取值判断是否为int数据;
    2. 为了区分int,还剩下2个bit,相当于使用2个bit区分这四个类型:object, double, string, boolean
    3. 但是nullundefinedFunction并没有分配type tag

    如何识别Function

    函数并没有单独的type tag,因为函数也是对象。typeof内部判断如果一个对象实现了[[call]]内部方法则认为是函数。

    如何识别undefined

    undefined变量存储的是个特殊值JSVAL_VOID(0-2^30)typeof内部判断如果一个变量存储的是这个特殊值,则认为是undefined

        #define JSVAL_VOID              INT_TO_JSVAL(0 - JSVAL_INT_POW2(30))
    

    如何识别null

    null变量存储的也是个特殊值JSVAL_NULL,并且恰巧取值是空指针机器码(0),正好低位bit的值跟对象的type tag是一样的,这也导致著名的bug:

    typeof null // object
    

    很不幸,这个bug也不修复了,因为第一版JS就存在这个bug了。祖传代码,不敢修改啊。

    有很多方法可以判断一个变量是一个非null的对象,之前遇到一个比较经典的写法:

    // 利用Object函数的装箱功能
    function isObject(obj) {
        return Object(obj) === obj;
    }
    
    isObject({}) // true
    isObject(null) // false
    

    三、Object.prototype.toString

    一般使用Object.prototype.toString区分各种内置对象。

    3.2 语法

    console.log(Object.prototype.toString.call(1)); // [object Number],隐式类型转换
    console.log(Object.prototype.toString.call('')); // [object String],隐式类型转换
    console.log(Object.prototype.toString.call(null)); // [object Null],特殊处理
    console.log(Object.prototype.toString.call(undefined)); // [object Undefined],特殊处理
    console.log(Object.prototype.toString.call(true)); // [object Boolean],隐式类型转换
    console.log(Object.prototype.toString.call( {})); // [object Object]
    console.log(Object.prototype.toString.call([])); // [object Array]
    console.log(Object.prototype.toString.call(function(){})); // [object Function]
    
    1. 如果实参是个基本类型,会自动转成对应的引用类型;

    Object.prototype.toString不能区分基本类型的,只是用于区分各种对象;

    1. nullundefined不存在对应的引用类型,内部特殊处理了;

    3.3 原理

    内部属性[[Class]]

    每个对象都有个内部属性[[Class]],内置对象的[[Class]]的值都是不同的("Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"),并且目前[[Class]]属性值只能通过Object.prototype.toString访问。

    Symbol.toStringTag属性

    其实Object.prototype.toString内部先访问对象的Symbol.toStringTag属性值拼接返回值的。

    var a = "hello"
    console.log(Object.prototype.toString.call(a)); // "[object String]"
    
    // 修改Symbol.toStringTag值
    Object.defineProperty(String.prototype, Symbol.toStringTag, {
        get() {
            return 'MyString'
        }
    })
    
    console.log(Object.prototype.toString.call(a)); // "[object MyString]"
    

    如果哪个货偷偷修改了内置对象的Symbol.toStringTag属性值,那Object.prototype.toString也就不能正常工作了。

    3.4 Object.prototype.toString内部逻辑

    综上可以总结Object.prototype.toString的内部逻辑:

    1. 如果实参是undefined, 则返回"[object Undefined]";

    2. 如果实参是null, 则返回"[object Null]";

    3. 把实参转成对象

    4. 获取对象的Symbol.toStringTag属性值subType

      • 如果subType是个字符串,则返回[object subType]
      • 否则获取对象的[[Class]]属性值type,并返回[object type]

    四、instanceof

    4.1 语法

    object instanceof constructorFunc
    

    instanceof 操作符判断构造函数constructorFuncprototype属性是否在对象object的原型链上。

    Object.create({}) instanceof Object // true
    Object.create(null) instanceof Object // false
    
    Function instanceof Object // true
    Function instanceof Function // true
    Object instanceof Object // true
    
    1. 作为类型判断的一种方式,instanceof 操作符不会对变量object进行隐式类型转换
    "" instanceof String; // false,基本类型不会转成对象
    new String('') instanceof String; // true
    
    1. 对于没有原型的对象或则基本类型直接返回false
    1 instanceof Object // false
    Object.create(null) instanceof Object // false
    
    1. constructorFunc必须是个对象。并且大部分情况要求是个构造函数(即要具有prototype属性)
    // TypeError: Right-hand side of 'instanceof' is not an object
    1 instanceof 1
    
    // TypeError: Right-hand side of 'instanceof' is not callable
    1 instanceof ({})
    
    // TypeError: Function has non-object prototype 'undefined' in instanceof check
    ({}) instanceof (() => {})
    

    4.2 intanceof的缺陷

    不同的全局执行上下文的对象和函数都是不相等的,所以对于跨全局执行上下文intanceof就不能正常工作了。

    <!DOCTYPE html>
    <html>
        <head></head>
        <body>
            <iframe src=""></iframe>
            <script type="text/javascript">
                var iframe = window.frames[0];
                var iframeArr = new iframe.Array();
    
                console.log([] instanceof iframe.Array) // false
                console.log(iframeArr instanceof Array)  // false
                console.log(iframeArr instanceof iframe.Array)  // true       
            </script>
        </body>
    </html>
    

    4.3 原理

    Symbol.hasInstance函数

    instanceof操作符判断构造函数constructorFuncprototype属性是否在对象object的原型链上。但是可以利用Symbol.hasInstance自定义instanceof操作逻辑。

    var obj = {}
    
    // 自定义Symbol.hasInstance方法
    Object.defineProperty(obj, Symbol.hasInstance, {
        value: function() {
            return true;
        }
    });
    
    1 instanceof obj // true
    

    当然了这个举例没有任何实际意义。只是说明下Symbol.hasInstance的功能。Symbol.hasInstance本意是自定义构造函数判断实例对象的方式,不要改变instanceof 的含义。

    原型链

    4.4 instanceof内部逻辑

    综上可以梳理instanceof内部逻辑

    object instanceof constructorFunc
    
    1. 如果constructorFunc不是个对象,或则是null,直接抛TypeError异常;

    2. 如果constructorFunc[Symbole.hasInstance]方法,则返回!!constructorFunc[Symbole.hasInstance](object )

    3. 如果constructorFunc不是函数,直接抛TypeError异常;

    4. 遍历object的原型链,逐个跟constructorFunc.prototype属性比较:

      • 如果object没有原型,则直接返回false;
      • 如果constructorFunc.prototype不是对象,则直接抛TypeError异常。

    五、内置的类型判断方法

    5.1 Array.isArray

    ES5引入了方法Array.isArray专门用于数组类型判断。Object.prototype.toStringinstanceof都不够严格

    var arr = []
    Object.defineProperty(Array.prototype, Symbol.toStringTag, {
        get() {
            return 'myArray'
        }
    })
    
    console.log(Object.prototype.toString.call(arr)); // [object myArray]
    console.log(Array.isArray(arr)); // true
    
    console.log(Array.prototype instanceof Array); // false
    console.log(Array.isArray(Array.prototype)); // true
    

    不过现实情况下基本都是利用Object.prototype.toString作为Array.isArray的polyfill:

    if (!Array.isArray) {
        Array.isArray = function(arg) {
        return Object.prototype.toString.call(arg) === '[object Array]';
        };
    }
    

    六、内置对象的prototype属性类型判断

    内置的对象Number, String, Boolean, Object, Function, Date, RegExp, Array都是各自类型对象的构造函数,并且他们的prototype属性都是各自实例对象的原型。但是这些内置对象的prototype属性又是什么类型呢?

    6.1 Number.prototype

    Number.prototype也是个数字,类似Number(0),但是Number.prototype并不是Number的实例。

    var prototype = Number.prototype
    
    console.log(prototype == 0); // true
    
    console.log(prototype instanceof Number); // false
    console.log(Object.prototype.toString.call(protoype)); // [object Number]
    

    6.2 String.prototype

    String.prototype也是字符串,类似"",但是String.prototype并不是String的实例。

    var prototype = String.prototype
    
    console.log(prototype == ''); // true
    console.log(prototype instanceof String); // false
    console.log(Object.prototype.toString.call(prototype)); // [object String]
    

    6.3 Boolean.prototype

    Boolean.prototype也是Boolean,类似false,但是Boolean.prototype并不是Boolean的实例。

    var prototype = Boolean.prototype
    
    console.log(prototype == false); // true
    console.log(prototype instanceof Boolean); // false
    console.log(Object.prototype.toString.call(prototype)); // [object Boolean]
    

    6.4 Object.prototype

    Object.prototype也是Object,类似Object.create(null)的值(原型为null的空对象),但是Object.prototype并不是Object的实例。

    var prototype = Object.prototype
    
    Object.getPrototypeOf(prototype); // null
    console.log(prototype instanceof Object); // false
    console.log(Object.prototype.toString.call(prototype)); // [object Object]
    

    6.5 Function.prototype

    Function.prototype也是Function,是个空函数,但是Function.prototype并不是Function的实例。

    var prototype = Function.prototype
    
    console.log(prototype()) // undefined
    console.log(prototype instanceof Function); // false
    console.log(Object.prototype.toString.call(prototype)); // [object Function]
    

    6.6 Array.prototype

    Array.prototype也是Array,是个空数组,但是Array.prototype并不是Array的实例。

    var prototype = Array.prototype
    
    console.log(prototype instanceof Array); // false
    console.log(Array.isArray(prototype)) // true
    console.log(Object.prototype.toString.call(prototype)); // [object Array]
    

    6.6 RegExp.prototype

    RegExp.prototype并不是RegExp的实例。但是关于RegExp.prototypeRegExp还是对象存在兼容性问题,有些浏览器下RegExp.prototype也是RegExp,并且是个总返回true的正则。

    var prototype = RegExp.prototype
    console.log(prototype.test('hello')) // true
    console.log(prototype instanceof RegExp); // false
    // Chrome v84返回"[object Object]", IE返回"[object RegExp]"
    console.log(Object.prototype.toString.call(prototype)); // 
    

    整理自gitHub笔记:

    1. JS-类型&类型判断
    2. JS-操作符-instanceof

    关注公众号「前端开发博客」,回复1024,领取前端资料包

    原文:https://segmentfault.com/a/1190000037674599

    相关文章

      网友评论

          本文标题:深入了解JS类型判断

          本文链接:https://www.haomeiwen.com/subject/pfquvktx.html