乐趣区

关于javascript:了解JavaScript中的类型转换

1. 什么是类型转换?

类型转换的定义很容易了解,就是值从一个类型转换为另一个类型,比如说从 String 类型转换为 Number 类型,'42'→42。然而,如果咱们对 JS 中的类型转换规定理解的并不足够的话,咱们就会遇到很多令人蛊惑的问题,就如同如果没有学习物理和化学,生存中处处都是魔法一样。

JS 中的类型转换大略能够分为 显式类型转换 隐式类型转换 两种。两者的区别非常显著,比方显式类型转换 ’String(42)‘, 咱们能够显著的看出,这行代码就是为了将 42 转为 String 类型。然而隐式类型转换通常是某些操作的副作用,比方''+42,就没那么显著了,它是加法操作的副作用。

如果你没有了解 JS 中的类型转换,上面的几个例子可能会让你王德发。

//ex.1
console.log([] == ![]); //true

//ex.2
const a1 = {},
    a2 = {};
console.log(a1 < a2); //false
console.log(a1 == a2); //false
console.log(a1 > a2); //false
console.log(a1 >= a2); //true 
console.log(a1 <= a2); //true

//ex.3
const a3 = {
    i: 1,
    valueOf() {return this.i++;},
};

console.log(a3 == 1 && a3 == 2); //true

这些奇怪的景象都有背地的情理,当然,前提是你要了解它。

2. 类型转换的形象操作

在具体介绍显式和隐式类型转换之前,咱们须要先通晓 StringNumberBoolean之间类型转换的根本规定。ES5 标准中定义了这些类型之间转换的形象操作(或者说转换规则)。上面简略介绍几种形象操作规定。

  1. ToPrimitive(负责对象转为根本数据类型)

    为了将值转换为相应的根本类型值,形象操作 ToPrimitive 会首先查看该值是否有 valueOf 办法。如果有并且该办法返回 根本类型值,就应用该值进行强制类型转换。

    如果没有就应用 toString的返回值(如果存在)来进行强制类型转换。
    如果 valueOftoString返回根本类型值,会产生 TypeError 谬误。

    例如:

    //ex.4
    const testObj = {};
    console.log(testObj.valueOf()); // {}  testObj 自身
    console.log(testObj.toString()); //'[object Object]' string 字符串
    /*
        首先调用 testObj 的 valueOf 办法,返回值是 testObj 自身,不是原始(根本)值类型;所以调用 testObj 的 toString 办法,返回了字符串 '[object Object]',是根本类型,所以应用 '[object Object]' 进行操作。*/
    console.log(''+ testObj); //'[object Object]'testObj.valueOf = () =>'valueOf';
    /*
        此时调用 testObj 的 valueOf 办法,返回的是字符串 'valueOf',是根本类型,所以会用该返回值进行操作,而不会再持续调用 toString 办法了。*/
    console.log(''+ testObj); //'valueOf'
  1. ToString(负责非字符串转为字符串)

    a. 当要转换的值是 根本类型 时,

    要转换的值 转成 String 之后的后果
    null 'null'
    undefined 'undefined'
    true , false 'true' , 'false'
    Symbol(‘example’) 仅反对显式类型转换失去'Symbol('example')'
    一般数字 0,1,2 合乎通用规定 '0','1','2'
    极大或极小的数字 1/10000000 应用指数模式 '1e-7' ,'1e+21'

    b. 当要转换的值是对象时,如果是显式类型转换,则调用该值的 toString 办法,如果是隐式类型转换,则先通过 ToPrimitive 操作转为根本类型,再依据 规定 a 转为 String 类型。

    当须要转换的值的 valueOf 办法和 toString 办法没有被批改时,罕用的对象转换如下:

    要转换的值 转成 String 之后的后果
    Object '[object Object]'
    Array [1,2,3] 用逗号分隔的每个数组元素,'1,2,3'
    数组元素是 null 或者 undefined 时,[null],[undefined] 空字符串 ''
    Date 美式英语日期格局的字符串,'Fri Oct 15 2021 14:04:28 GMT+0800 (中国规范工夫)'
    Function 示意函数源代码的一个字符串
  1. ToNumber(负责非数字转为数字)

    要转换的值 转成 Number 之后的后果
    true 1
    false 0
    undefined NaN
    null 0
    string ’42’,’42px’ 根本遵循数字常量的相干规定,解决失败时返回NaN 42,NaN
    symbol 不可转换
    Object 等简单类型 先通过 ToPrimitive 转为根本类型,再转成 Number 类型

    例如:

    console.log(Number([null])); //0
    // [null] 通过 ToPrimitive 转成根本类型是空字符串 '',空字符串转为 Number 失去 0 
  2. ToBoolean

    JavaScript 中的值能够分为以下两类:
    (1) 能够被类型转换为 false 的值:undefinednull+0、- 0 和 NaNfalse''
    (2) 其余(被类型转换为 true 的值),即除了下面的几个值以外的。

    要留神的是有一个概念称为 假值对象 ,假值对象和一般对象的区别在于,当他被转为 Boolean 时,会失去 false。这类状况十分少见,然而的确存在,比方document.all 对象,console.log(!!document.all); //false

好了,目前咱们曾经对这几种形象操作有肯定理解了,上面就能够略微深刻一下类型转换了。

3. 显式类型转换

  1. 字符串和数字之间的显式类型转换

    ​ 字符串和数字之间的类型转换应该是咱们最常见的一种了,它们之间的显式类型转换时通过 JS 中的 原生构造函数 String 和 Number实现的,然而 通过 new 机进行结构函数调用。这两个函数在进行类型转换时,都遵循 ToStringToNumber形象操作。

    例如:

    console.log(Number('3')); // 3
    console.log(String(3)); //'3'
  1. 显式 解析 字符串

    解析字符串中的数字(parseIntparseFloat)和将字符串转换为数字尽管失去的后果都是数字,然而二者的转换规则是不一样的。

    ​ a. 绝对于 Number 而言,parseInt容许传入的字符串参数中含有非数字字符,解析依照从左到右的程序,遇到非数字字符就进行解析。例如console.log(parseInt('10px')); //10 ,console.log(Number('10px'));//NaN

    ​ b. parseInt是针对字符串的函数,当传入的第一个参数不是字符串时,会先将其转为字符串再进行解析。

    ​ c. parseInt有第二个参数,代表解析时应用的 进制,比方 console.log(parseInt('11', 8)); //9, 用八进制对 ’11’ 进行解析。该参数的范畴是 2 -36,传入范畴之外的值(如果传入 0,会被设为 10 进制),会返回 NaN。

    在应用 parseInt(str,radix) 时,radix 并没有指定默认值(只是大部分状况 radix 会被默认设为 10),也就是说如果你不传入这个参数的话,失去的后果可能是你意料之外的。比方当 str0x或者 0X(大写的 X)结尾时,radix 会被默认设为16,所以当 console.log(parseInt('0x11')); //17 会失去 17。

    有个很乏味的例子:console.log([1, 2, 3].map(parseInt)); //[1, NaN, NaN],就是利用了这个进制参数,后果数组的三项别离是parseInt(1,0),parseInt(2,1),parseInt(3,2),在第一项中,因为进制传入了 0,被当为 10 进制解决,所以返回了 1;第二项中因为进制传入了1,不在非法范畴(2-36)内,所以返回了NaN;第三项中,因为进制传入 2,而要解析的参数是 3(二进制只有 0 和 1 的表白才是非法的),所以返回了NaN

  1. 显示转换 Boolean

    StringNumber一样,Boolean是显式的 ToBoolean 类型转换,遵循 ToBoolean 操作规定。不过个别 !! 的办法应用的比拟多。例如:console.log(Boolean(1), !!1);

4. 隐式类型转换

隐式类型转换个别是其余操作的副作用。

  1. 加法操作:

    如果操作数是 对象 ,则先对象通过ToPrimitive 操作转为根本类型,而后再持续操作。

    如果其中一个操作数是 字符串(String)类型,则将另一个操作数也转换为 字符串(String)类型,进行字符串拼接。

    如果操作数中 没有 字符串(String)类型,则将两个操作数作为 数字(Number)类型,进行加法运算。

    例如:

    // 因为 'hello' 是字符串,所以把 1 转成了 '1' , 而后进行字符串拼接
    console.log(1 + 'hello'); // '1hello'
    // 因为没有字符串类型的操作数,所以进行加法运算,将 true 转成数字类型为 1,所以后果为 2
    console.log(1 + true); // 2
    // 因为 [2] 是对象类型,将其通过 ToPrimitive 操作之后,转成字符串 '2' , 进而须要把 1 转成 '1' , 进行字符串拼接
    console.log(1 + [2]); // '12'
  1. 减法、除法、乘法等操作:

    如果操作数是 对象 ,则先对象通过ToPrimitive 操作转为根本类型,而后再持续操作。

    将两个操作数作为 数字(Number)类型,进行运算。

    例如:

    // [3] - [2]  → '3' - '2' → 3 - 2 =1
    console.log([3] - [2]); // 1
    //3 - 1 =2
    console.log([3] - true); // 2
  1. 将其余类型隐式转换为 Boolean 的操作:

    当值作为判断条件时。如 if 语句 中的条件判断表达式;for 循环 语句中的条件判断表达式;while 循环 do..while 循环 中的条件判断表达式;三目运算符 (?:)中的条件判断表达式; 逻辑运算符 || 和 && 中的值作为条件判断表达式。该值会被隐式转换为 Boolean 类型进行判断,遵循ToBoolean 操作规定。

  2. 宽松相等(==):

    在 JS 中进行关系比拟时,宽松相等(==)常常被解释为:“只比拟二者的值是否相等,而不比拟类型”,其实这种解释有些问题,这种解释中的“值”,应该怎么了解呢?

    比方我在进行比拟console.log(0 == ''); //true,0 和空字符串 ”,二者的原始值和类型都不雷同,然而却是宽松相等的。

    更为精确地解释,应该是“当进行宽松相等的关系比拟时,如果二者类型雷同,则仅比拟二者的值是否雷同即可;如果二者类型不同,则须要先进行类型转换为同一类型,再比拟值是否雷同”。

    也就是说,当两个类型不同的值进行宽松相等的比拟时,会产生隐式的类型转换。

    宽松相等中,类型不同时的类型转换规定如下(假如两个操作数别离为x,y):

    1. 如果 x 和 y 的类型别离为 字符串(String) 数字(Number),则将 字符串 类型 转为数字 而后比拟。

      // 先将字符串 '1' 转成了数字 1 , 再进行比拟 
      console.log(1 == '1');//true
    2. Boolean 类型 的操作数,都须要转成 数字(Number)类型。

      // 先将 false 转成了 0,而后再依据规定 1,将 '0' 转成了 0,而后进行比拟
      console.log('0' == false);//true
    3. 如果一方是 对象类型 ,则须要通过ToPrimitive 形象操作规定,转成根本类型当前再持续比拟。

      // 先将对象类型的数组 [1] 转成了字符串 '1',再依据规定 1,将字符串 '1' 转成了数字 1,而后进行比拟
      console.log([1] == 0);//false
    4. null 和 undefined 是宽松相等的,能够说,在宽松相等的比拟中,null 和 undefined 是一回事。

    另外要理解的是:

    • NaN 不等于 NaN。
    • +0 等于 -0

  3. 大于(>),小于(<), 大于等于(>=),小于等于(<=):

    在进行 大于 小于 这两种比拟时:

    a. 如果有对象类型,首先通过 ToPrimitive 操作转成根本类型,在进行比拟。

    b. 如果两者都是字符串,则按字符的 ASCII 码值比拟。

    c. 若果有一方不是字符串,则将二者都通过 ToNumber 形象操作转为 Number 类型之后,再进行比拟。

    在进行 大于等于 小于等于 这两种比拟时:

    大于等于代表着不小于,即 a>= b 的后果是 !(a<b)。小于等于同理。

5. 回顾

理解了 JS 类型转换规定之后,在回过头看之前的几个例子,想来咱们都可能了解这些奇怪的景象了。

ex.1:

//ex.1
console.log([] == ![]); //true
//[] 通过 ToPrimitive 转成根本类型,失去 '',  ![]失去 false。宽松相等比拟规定,转成数字 0
// 即 ''==0 , 空字符串'' 有转成了数字 0,所以最终为 true 

ex.2:

//ex.2
const a1 = {},
    a2 = {};
//a1 a2 都是对象类型,通过 ToPrimitive 操作之后,后果都是 '[object Object]'
// 显然 '[object Object]'<'[object Object]' 和 '[object Object]'>'[object Object]' 都是 false
console.log(a1 < a2); //false
console.log(a1 > a2); //false
// 在进行宽松相等比拟时,二者类型雷同,不会进行类型转换,而是比拟二者的值,a1,a2 显然指向的地址不同,所以 a1==a2 = 也为 false
console.log(a1 == a2); //false

// 小于等于  大于等于 别离为 大于和小于的后果取反,所以都为 true
console.log(a1 >= a2); //true 
console.log(a1 <= a2); //true

ex.3:

//ex.3
const a3 = {
    i: 1,
    valueOf() {return this.i++;},
};

console.log(a3 == 1 && a3 == 2); //true

//a3 是对象类型,通过 ToPrimitive 操作,转成根本类型失去 a3.i (即 1), 所以 a3==1 为 true
// 然而在下面执行 valueOf 的时候,i 自增了一次,所以在 a3== 2 的比拟时,a3 转为根本类型的值时,失去的就是 2 了,所以 a3== 2 也为 true。
退出移动版