数据类型转换

289 阅读11分钟

常见的3种数据类型转换

实际开发中,常用到的数据类型直接的的转换主要有如下3种:

  1. 把其他数据类型的值转换成 Number 类型
  2. 把其他类型的值转换成 String 类型
  3. 把其他类型的值转换成 Boolean 类型

把其他类型值转为 Number 类型

有 3 个函数可以将非数值转换为数值:Number()、parseInt()和 parseFloat()。

Number() 函数

实际开发中,一般不主动调用 Number() 函数,Number() 函数多用于隐式转换中。

以下情况浏览器会使用 Number() 来进行隐式转换:

  • 进行数学运算时, 比如减法运算 10 - '5'
  • 使用 isNaN() 函数检测一个值是不是有效数字时
  • == 比较时
  • + 操作数只有一个,且在右边,例如:+ 'xxx' , 一般都是把xxx转换为数字
  • ...

Number() 函数 转换规则如下:

  1. 把字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN。
  2. 把布尔转换为数字:true->1 false->0
  3. null -> 0
  4. undefined -> NaN
  5. Symbol 无法转换为数字,会报错。
  6. BigInt 会去除“n”(超过安全数字的,会按照科学计数法处理)
  7. 把对象转换为数字,(浏览器会做以下几步操作):
    • 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法
    • 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值
    • 再调用对象的 toString 把其变为字符串
    • 最后再把字符串基于Number方法转换为数字
案例1

案例:把一个对象类型值转换为数字类型值

let obj = {
    name: 'zhufeng'
};
console.log(Number(obj)); //  NaN

转换过程描述:

  1. 浏览器会先看看 obj 是否有 Symbol.toPrimitive 属性。
obj[Symbol.toPrimitive];
// 输出结果: undefined
// 说明 obj 没有Symbol.toPrimitive 属性。
  1. 如对象没有Symbol.toPrimitive 属性,浏览器则调用当前对象的 valueOf() 方法,获取其原始值。
obj.valueOf();
//  输出结果: {name:...}
// 是个对象,不是原始值。
  1. 如对象调用对象的 valueOf() 方法获取到的不是原始值,则调用对象的 toString() 把 obj 变为字符串。
obj.toString()
//  "[object Object]"`
  1. 最后再把得到的字符串,基于Number() 方法转换为数字。
Number("[object Object]");
// 输出结果:NaN
// 基于Number() 方法把字符串转数字,
// 只要字符串中有非有效数字,结果都是NaN
案例2

案例:把数组转换为数字类型值

let arr1 = [10],
    arr2 = [10, 20];
console.log(Number(arr1));   // 10
console.log(Number(arr2));   // NaN

转换过程描述:

  1. 浏览器会先看看数组是否有 Symbol.toPrimitive 属性
arr1[Symbol.toPrimitive] // -> undefined 没有
arr2[Symbol.toPrimitive] // -> undefined 没有
  1. 如数组没有 Symbol.toPrimitive 属性,再调用数组的 valueOf() 方法,获取其原始值。
arr1.valueOf() // -> [10]
arr2.valueOf() // -> (2) [10, 20]
// 获取到的都不是原始值
  1. 调用数组的 toString() 把 数组 变为字符串
arr1.toString() // -> '10'
arr2.toString() // -> '10,20'
  1. 最后再把得到的字符串,基于Number() 方法转换为数字
Number('10')  // -> 10
Number('10, 20')  // -> NaN  字符串  '10,20' 中有非有效数字字符 “ ,”
案例3

日期对象转换为数字类型

let time = new Date();
console.log(Number(time)); // ->  623129321488
console.log( String(time) ); // ->  "Tue Jun 08 2021 13:15:21 GMT+0800 (中国标准时间)"

转换过程描述:

  1. 先看看 time 是否有 Symbol.toPrimitive 属性
time[Symbol.toPrimitive]
// [Symbol.toPrimitive]() {[native code]}
// 有是个函数
  1. 如果对象有 Symbol.toPrimitiv 属性方法,则调用执行这个方法,执行完返回结果是什么,就是转换的结果就是什么。

执行这个方法时会传递一个 hint 值:'number'/'string' / 'default' , 传递的值不同,得到的结果不一样。

time[Symbol.toPrimitive]('number')
// -> 623129321488
time[Symbol.toPrimitive]('string')
// -> "Tue Jun 08 2021 13:15:21 GMT+0800 (中国标准时间)"
time[Symbol.toPrimitive]('default')
// -> Tue Jul 26 2021 18:29:50 GMT+0800 (中国标准时间)

案例4

给没有 Symbol.toPrimitive 属性的对象,我们可以给它扩展一个

// 1. 普通对象
let obj = {
    name: 'zhufeng',
    [Symbol.toPrimitive](hint) {
        console.log(hint);
        return 10;
}
};
// 浏览器会根据场景,传不同的 hint 值
console.log(Number(obj));
// hint->"number"  Number(10)  10
console.log(String(obj));
// hint->"string"  String(10)  “10”

console.log(obj.toString()); //“[object Object]”
// 这不是把它转换为字符串「而是检测数据类型」,
// 所以不走 Symbol.toPrimitive 这一套逻辑


// 2. 数组
let arr = [10];
console.log(arr.toString());
//"10"
// 直接调用原型的 toString() 方法,
// 不会走 Symbol.toPrimitive 这套逻辑,方法内部做了处理

arr[Symbol.toPrimitive] = function (hint) {
    console.log(hint);
    return 0;
};
console.log(arr + "hhh");
// 0hhh

parseInt() & parseFloat()

语法:

parseInt([val],[radix])
parseFloat([val])

描述:

  • 这两个函数主要用于将字符串转换为数值
  • 一般用于手动转换

转换规则:

  • parseInt() 第一个参数 [val],要被转换的值。
    • 必须是一个字符串,
    • 如果不是则先转换为字符串(强制隐式转换);
    • 然后从字符串左侧第一个字符开始找,把找到的有效数字字符最后转换为数字
      • 一个有效数字都没找到就返回 NaN
      • 如果遇到一个非有效数字字符,不论后面是否还有有效数字字符,都不再查找了,把找到的有效数字返回;

parseInt() 的二个参数 [radix], 可以指定进制。 + [radix] 是2-36之间的整数,如果不在这个范围内「排除0」,则结果一定是NaN + [radix]不设置或者设置为0,默认值是10「特殊:如果左侧字符串是以“0x”开始的,默认值是16」 + 从左侧[val]字符串中,查找出符合 [radix] 进制的字符,把找到的字符看做 [radix] 进制,最后转换为10进制。 + 其他进制转换为10进制:按权展开求和。 + 返回的结果,都是其他进制整数对应的10进制整数。

parseFloat() 规则同 parseInt() 一样,可以多识别一个小数点。parseFloat 没有第二个参数。

案例 1

案例 1: parseInt() 和 Number() 转换机制完全不一致


    console.log(parseInt('10')); //10
    console.log(Number('10')); //10

    console.log(parseInt('10px')); //10
    console.log(Number('10px')); //NaN

    console.log(parseInt(null)); // parseInt('null') -> NaN
    console.log(Number(null)); //0

案例 2

0开头数字会被识别为8进制数

console.log( parseInt(0023))  // 19

数值 0023 ,以0开头数字,会被浏览器认为是 8 进制数。

浏览器会将 0023 隐式转换成10进制整数展示。

0023 由8进制转成10进制,按权展开

08^3 + 08^2 + 28^1 + 38^0 -> 0 + 0 +16 + 3

结果为:19

案例 3

什么是按权展开求和?

// '1001' 以二进制身份转换为十进制
   1*2^3 + 0*2^2 + 0*2^1 + 1*2^0  ->   8 + 0 + 0 + 1  ->  9
// '0.12' 以三进制身份转换为十进制
   0*3^0 + 1*3^-1 + 2*3^-2  ->  0 +  0.3333... + 0.1111...  ->  0.5555...
案例4

案例4:数组的 map 方法结合 parseInt() 应用

    let arr = [27.2, 0, '0013', '14px', 123];
    arr = arr.map(parseInt);
    console.log(arr);

解析 题目把 parseInt 设定为 map 方法的回调函数,即 map 每遍历一次,就调用一次parseInt()。

由于 map 回调函数会被自动传入三个参数:数组元素,元素索引,原数组本身。

而parseInt(value [,radix]) 只有两个形参,所以只接收了数组元素,元素索引

所以在 parseInt调用时 ,当前数组元素 对应的是 value 是要被转化为整数的值,数组元素的索引对应的是 [,radix] 进制。

所以上题可以分解步骤为 :

  1. parseInt(27.2,0) -> 27

    • [radix] 进制:不设置或者设置为0,默认值是10
    • parseInt(27.2,0) 即转换为10进制整数 -> 27
  2. parseInt(0,1) -> NaN

    • [radix] 进制:是2-36之间的整数,如果不在这个范围内「排除0」,则结果一定是NaN
    • 1 不在范围内所以返回 NaN
  3. parseInt('0013',2) -> 1

    • [,radix] 进制 为 2, 有效数字值只有 0 和 1 ,
    • 按规则从左往右,在字符串 '0013' 中查找有效数字,
    • 找到的有效数字字符串是001',因为找到'3'时,已经不是2进制的有效数字值了,停止继续查。
    • 再把 '001' ,按权展开,由2进制整数转换为10进制整数。
    • -> 02^2 + 02^1 + 1*2^0
    • -> 1
  4. parseInt('14px',3) -> 1

    • -> 同理,只有 '1' 符合三进制的
    • -> 1*3^0 按权展开 ,由3进制整数转换为10进制整数
    • -> 1
  5. parseInt(123,4) -> 27

    • '123' 符合四进制
    • 14^2 + 24^1 + 3*4^0 -> 16 + 8 + 3 -> 27
    • ---> 新数组:[27,NaN,1,1,27]

把其他类型值转为 Boolean 类型值

需要把其他类型值转为 Boolean 类型值的场景:

  1. 调用 Boolean() 转型函数
  2. ! 逻辑非操作符 也可以用于把任意值转换为布尔值。
  3. 同时使用两个叹号(!!),相当于调用了 Boolean() 转型函数
  4. 条件判断语句中,会(隐式)把值转换为布尔值。比如:if 语句的的条件,可以是任何表达式,但表达式的结果不一定是布尔值,JS引擎会(隐式强制)调用 Boolean() 转型函数

转换规则:

  • 只有6个值会被转换为false:false,'',0,NaN,null,undefined
  • 其他值都会被转成 true,包括空数组 [] 和 空对象 {}

把其他类型值转换为字符串

需要转换的场景:

  1. 调用 String() 转型函数
  2. 调用 toString() 方法
  3. + 加法运算符,除了表示数学加法运算外,还可能是字符串拼接。

toString() 方法

toString() 方法,其作用就是返回当前值的字符串等价物

语法格式:

[value].toString();

哪些内置类的原型对象上有 toString() 方法

  • Array.prototype.toString()
  • String.prototype.toString()
  • Number.prototype.toString()
  • Function.prototype.toString()
  • Object.prototype.toString() 【特殊】

在控制台依次输出以下代码:

(123).toString();
(true).toString();
('string').toString();
({}).toString();
(null).toString();
(undefined).toString();

输出结果如下图所示:

toString.jpg

  • 大部分都是拿引号将值包起来。
  • 特殊 Object.prototype.toString
  • null 或 undefined 没有toString() 方法,调用会报错。

参数问题: 多数情况下,toString()不接收任何参数。

不过,在对数值调用这个方法时,toString()可以接收一个[radix 进制]参数,即以什么[radix 进制] 来输出数值的字符串表示。默认情况下,toString()返回数值的十进制字符串表示。而通过传入参数,可以得到数值的二进制、八进制、十六进制,或者其他任何有效基数的字符串表示。

// 先把值转换成指定进制的数,再转成相应的字符串
let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"

String() 函数

可以转任何数据类型的值,始终会返回表示相应类型值的字符串。

语法格式:

String(value)

转换规则:

  • 如果值有 toString()方法,则调用该方法(不传参数)并返回结果。
  • 如果值是 null,返回"null"
  • 如果值是 undefined,返回"undefined"。

+ 加法运算符 会发生字符串拼接的场景

+ 两边都有操作数:

  • 如果两个操作数都是字符串,则将第二个字符串拼接到第一个字符串后面
  • 如果只有一个操作数是字符串,则将另一个操作数转换为字符串,再将两个字符串拼接在一起
  • 如果有任一操作数是对象、数值或布尔值,则先调用它们的 toString() 方法以获取字符串,再拼接字符串。

一些隐式转换规则总结

把对象隐式转换为数字/字符串的逻辑

  • 当把对象隐式转换为数字或者字符串时,使用的方法是 Number() / String() ,会有一套处理逻辑:
    • @1 检测Symbol.toPrimitive,如果有这个方法,浏览器会把方法执行,传递hint「根据场景不一样,浏览器默认传递的值也不同 'number'/'string'/'default'」;如果没有这个方法,则进行下一步;
    • @2 基于valueOf获取原始值,如果获取的不是原始值,则进行下一步;
    • @3 基于toString获取字符串
    • @4 如果需要转的是数字,则再次把字符串转为数字即可
    • 但是如果是直接 对象.toString ,相当于直接调用第三个步骤「直接调用所属类原型上的toString方法」,此时直接获取字符串,不会走这四步逻辑。

“==”比较时候的相互转换规则

“==”相等,两边数据类型不同,需要先转为相同类型,然后再进行比较 @1 对象==字符串 对象转字符串「Symbol.toPrimitive -> valueOf -> toString」 @2 null==undefined -> true null/undefined和其他任何值都不相等 null===undefined -> false @3 对象==对象 比较的是堆内存地址,地址相同则相等 @4 NaN!==NaN @5 除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后再进行比较的 “===”绝对相等,如果两边类型不同,则直接是false,不会转换数据类型「推荐」