true + true === 2 ? JS 类型转换(一些你不知道的隐式转换)

541 阅读7分钟

首先来看一看一些比较奇特的隐式转换

10 / '2' // 5
1 + true // 2
true === 1 // false 
false === 0 // false 
true + true === 2 // true 
false + false === 0 // true 
{} + [] === 0 // true 
[] + {} === 0 // false

看到这些,你肯定会感觉到很奇怪,为什么一个数字除一个字符串得到了一个数字,为什么true+true===2,可是true===1却是false,为什么{}+[]===0为真,可是[]+{}===0却为假。

要讲隐式转换,不可避免先要知道JavaScript中的类型。

JS中有两大类型,分别是简单数据类型,复杂数据类型。

  • 简单数据类型包含7种类型Symbol、null、undefined、number、string、boolean、BigInt
  • 复杂数据类型就是Object,而数组,函数等都属于Object里的子类型。

通常我们判断元素类型是使用typeof操作符

 typeof Symbol() === 'symbol' // true 
 typeof undefined === 'undefined' // true 
 typeof true === 'boolean' // true 
 typeof 1 === 'number' // true 
 typeof '1' === 'string' // true 
 typeof 111n === 'BigInt' //true
 typeof { age: 20 } === 'object' // true 
 // 但还有一个例外 
 typeof null === 'object' // true

这是因为js中早期的bug,一直都存在null, js采取32位存值的方式,前三位的代表是类型 而000代表object null前三位也是 000 所以js判断null为object

手写一个判断类型的函数

能区分Object子类型,同时把null这个错误给改正过来。
const getTypeof =(val) =>{
            var ans = typeof val;//除了function 之外的对象,除了null 外的简单数据类型
            if(nus ==='object'){
                //简单数据类型的 null
                //复杂数据类型 细化
                if(val===null){
                    ans='null'
                }else if(Array.isArray(val)){ //数组判断新方法
                    ans='array'
                }else{
                    //函数 数组 之外的对象的子类型
                    ans=Object.prototype.toString.call(val).slice(8,-1);
                }
            }
            return ans;
        }

所有的隐式转换都是基于强制类型转换的,所以我们先了解一下什么是类型之间的强制转换

ToNumber强制转换Number型

  • Number简单数据类型转换
Number("1") // 1 
Number("1,") // NaN 
Number("") // 0 
Number(false) // 0 
Number(true) // 1 
Number(null) // 0 
Number(undefined) // NaN 
Number(Symbol('s')) // TypeError...
  • 复杂数据类型转换

    • 对象在转number之前,会先转换为基础类型,再转换为number类型,这个过程称为ToPrimitive
    • ToPrimitive过程先检查对象是否存在valueOf方法,如果存在并且valueOf返回基本类型的值,则使用该值进行强制类型转换,如果没有,则使用toString方法返回的值进行强制类型转换。
    var arr = [1, 2,3]
    Number(arr)// NaN 
    //因为没有valueOf方法,使用toString方法返回的值进行强制类型转换,arr.toString()等于"1,2,3",强制转换后为NaN
    
    arr.valueOf = function() { return '123' } 
    Number(arr) // '123' 强制转换成 123
    
    arr.toString = function() { return '123' } 
    Number(arr) //'123' 强制转换成 123
    
    var obj = {} 
    Number(obj) // NaN
    //因为没有valueOf方法,使用toString方法返回的值进行强制类型转换,obj.toString()等于'[object Object]',强制转换后为NaN
    
    var obj = { valueOf: function () { return '123' } } Number(obj2) // 123
    //存在valueOf方法,返回'123','123'强制转换成123
    

ToString强制转换String类型

  • String简单数据类型转换
String(1) // "1" 
String(1n) // "1" 
String(false) // "false" 
String(true) // "true" 
String(null) // "null" 
String(undefined) // "undefined" 
String(Symbol('s')) // "Symbol(s)"
  • String复杂数据类型转换
    • 调用该类型原型上的toString方法
    String({ a: 1 }) // "[object Object]"
    相当于
    var obj ={a:1}
    obj.toString() //"[object Object]"
    
    var arr = [1,2,2]; 
    arr.toString() // "1,2,3" 
    String(arr) // "1,2,3"
    
d9eb-ixkvvue1872949.jpg

重点要来了,JavaScript中是如何进行隐式转换的?

  • 操作符+两边的隐式转换规则

    如果其中一个操作数是字符串;或者其中一个操作数是对象,且可以通过ToPrimitive操作转换为字符串,则执行字符串连接操作,其他情况执行加法操作。

    x + y 
    => if (type x === string || type y === string ) return join(x, y) 
    //满足其中一个操作数是字符串,采用连接操作
    => if (type x === object && type ToPrimitive(x) === string) return join(x, y) 
    => if (type y === object && type ToPrimitive(y) === string) return join(x, y) 
    //满足其中一个操作数是对象,且可以通过ToPrimitive操作转换为字符串,采用连接操作。
    => else return add(x, y)
    //其他情况采取加法操作
    

    对于执行加法操作的情况,如果操作数有一边不是number,则执行ToNumber操作,将操作数转换为数字类型。

    [1, 2] + {} // "1,2[object Object]" 
    /*
     因为[1, 2]和{}均不是字符串,但是[1, 2]和{}均可以通过ToPrimitive操作 
     但是[1, 2]和{}均可以通过ToPrimitive操作转换为字符串 
     所以这里执行字符串连接操作,根据ToPrimitive的规则 
     [1, 2].valueOf()的值不是基础类型,所以我们使用[1, 2].toString()的值 
     这时候就变成了 "1,2" + {} 
     而{}也可以通过ToPrimitive操作转换为"[object Object]" 
     所以最后的结果是"1,2[object Object]" 
    */
    
    var obj={
        valueOf:function(){return 1}
    }
    true + obj // 2
    /*
     因为true和obj都不是,而obj为一个对象,且obj不能通过ToPrimitive转换为字符串,所以采取加法操作。
     true先执行强制转化 1
     obj先进行ToNumber,obj为对象,进行ToPrimitive操作,obj中有valueOf方法,返回值,obj转换为1
     最后1 + 1 得到2
    */
    

不过这里有一个坑,那就是{} + [] === 0,这个坑主要在于编译器并没有将{}看成一个对象而且代码块。

    {} + [] === 0
    /*
        首先{}编译器把它看成代码块,代码块不会返回值
        因为[]为对象,且[]通过ToPrimitive转换为'',为字符串,采取加法操作,然后在ToNumber转化为0。
        {} + []得到0
    */
  • 操作符==两边的隐式转换规则

    ==操作符被称为抽象相等,一般我们会建议禁止在业务代码中使用抽象相等。

    但有时候用起来却很方便,比如下拉框选项中即使我们拉取的数据是number类型,在onChange回调中value的值却是字符串,这时候使用抽象相等就挺舒服的。

    规范

    • 对于数字和字符串的抽象比较,将字符串进行ToNumber操作后再进行比较

    • 对于布尔值和其他类型的比较,将其布尔类型进行ToNumber操作后再进行比较

    • 对于对象和基础类型的比较,将对象进行ToPrimitive操作后在进行比较

    • 对象之间的比较,引用同一个对象则为true,否则为false

        true == '1' //true
        /*
            采取第二条规范,布尔值比较,先将true进行ToNumber操作得到1
            然后在根据第一条规范,将'1'ToNumber转换为1
            最后就是1 == 1  输出true
        */
        
        var obj = { 
            valueOf: function() 
                { 
                    return '1' 
                } 
        } 
        true == obj // true
        /*
        先采取第二条规范,true转换为1
        然后采取第三条规范,obj进行ToPrimitive操作,有 ,返回值为'1'
        最后采取第一条规范,将'1'ToNumber转换为1
        得到1 == 1 true
        */
        
        [] == ![] // true
        /*
        首先![]对[]进行强制布尔转换,也就是[]==false
        然后采取第二条规范,false转换为0
        然后采取第三条规范,[]进行ToPrimitive操作,没有valueOf方法,采取toString得到''
        最后采取第一条规范,将''ToNumber转换为0
        即 0 == 0 true
        */
    

总结

隐式类型转换==规则也就是以上的四个规范一直嵌套使用,然后就是每个规范要与类型转换类型起来。通常我们都是使用===全等表示两个数据相等,不常用==,但是==其中的类型转换我们还是要理解一下其基础原理,不然可能看不懂别人写的代码。这篇文章包含了隐式转换+的规则和==的四大规范,以及各种类型转换的原理,其次就是手写判定类型方法

感谢各位同学的阅读,如果可以的话,希望能留个赞鼓励一下,同时,如果有错误的话,希望能在评论区指出。