数据类型转换-转换成 Number 类型

把其他数据类型的值转换成 Number 类型

主要有3种 Number() 、parseInt() 和 parseFloat() 三个函数。

Number() 函数

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

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

  • 进行数学运算时, 比如减法运算 10 - '5'
  • 使用 isNaN() 函数检测一个值是不是有效数字时
  • == 比较时

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方法转换为数字

总结:

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

案例


//  1. 把一个对象类型值转换为数字类型值
let obj = {
    name: 'zhufeng'
};
console.log(Number(obj)); //  NaN

/**
 * 转换过程描述:
 * @1 先看看 obj 是否有 Symbol.toPrimitive 属性
 * obj[Symbol.toPrimitive] -> undefined
 * 说明 obj 没有Symbol.toPrimitive 属性
 *
 * @2 调用对象的 valueOf() 方法
 * bj.valueOf() -> {name:...} 执行结果是个对象,不是原始值。不是原始值,则继续揍 @3
 *
 * @3 调用对象的 toString() 把 obj 变为字符串
 * obj.toString() -> "[object Object]"
 *
 * @4 最后再把得到的字符串,基于Number() 方法转换为数字
 * Number("[object Object]") -> NaN  字符串转数字,只要字符串中有非有效数字,结果都是NaN
 */


// 2 把数组转换为数字类型值
let arr1 = [10],
    arr2 = [10, 20];
console.log(Number(arr1));
console.log(Number(arr2));

/**
 * 转换过程描述:
 * @1 先看看 数组 是否有 Symbol.toPrimitive 属性
 * arr1[Symbol.toPrimitive] -> undefined
 * arr2[Symbol.toPrimitive] -> undefined
 *
 * @2 调用数组的 valueOf() 方法
 * arr1.valueOf() -> [10]
 * arr2.valueOf() -> [10, 20]
 * 获取的都不是原始值
 *
 * @3 调用数组的 toString() 把 数组 变为字符串
 * arr1.toString() -> '10'
 * arr2.toString() -> '10,20'
 *
 * @4 最后再把得到的字符串,基于Number() 方法转换为数字
 * Number('10') -> 10
 * Number('10, 20') -> NaN 字符串中有非有效数字字符 “ ,”
 *
 * =》都没有 Symbol.toPrimitive 属性
 * =》通过 valueOf() 获取得都不是原始值
 */


// 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]}
 * =》 日期对象 拥有  Symbol.toPrimitive 属性,其值是个函数。
 *
 * 如果有 Symbol.toPrimitiv 属性,那就执行 Symbol.toPrimitive ,其执行返回结果是什么,就是转换的结果
 * 执行这个方法时会传递一个值:'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)
 *
 * =》 Number(time)
 * 此时,我们需要转换为 Number 类型的数值,
 * 因此浏览器在 time[Symbol.toPrimitive] 执行时会传 'number'
 * time[Symbol.toPrimitive]('number')
 * ->  623129321488
 *
 * =》 String(time)
 * 同理,我们需要转换为 String 类型的数值
 * time[Symbol.toPrimitive] 执行时传的是 'string',即:
 * time[Symbol.toPrimitive]('string')
 * -> "Tue Jun 08 2021 13:15:21 GMT+0800 (中国标准时间)"
 *
 * 日期对象不允许我们重写  Symbol.toPrimitive 属性
 */


// 4
// 由前面案例得知,普通对象没有 Symbol.toPrimitive 属性,我们自行给它扩展一个
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]”
// Object.prototype.toString.call(obj) 这不是把它转换为字符串「而是检测数据类型」,所以不走Symbol.toPrimitive这一套逻辑


// 5
let arr = [10];
arr[Symbol.toPrimitive] = function (hint) {
    console.log(hint);
    return 0;
};
// console.log(arr.toString()); //"10" 直接调用原型的 toString() 方法,不会走Symbol.toPrimitive这套逻辑,方法内部做了处理
console.log(arr + ""); //  数组加字符串时,默认(隐式)要把数组 转成字符串,然后进行字符串拼接。这时就需要走 Symbol.toPrimitive这套逻辑 ;    hint -> default 因为浏览器不知道 + 是要做数学运算还是字符串拼接,所以传的是‘default’


复制代码

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

  • 一般用于手动转换
  • 规则:
    • [val]值必须是一个字符串,如果不是则先转换为字符串(强制隐式转换);
    • 然后从字符串左侧第一个字符开始找,把找到的有效数字字符最后转换为数字
      • 一个有效数字都没找到就是NaN;
      • 如果遇到一个非有效数字字符,不论后面是否还有有效数字字符,都不再查找了,把找到的有效数字返回;
    • parseFloat 规则一样,可以多识别一个小数点;
  • 第二个参数 [radix]
    • radix 是2-36之间的整数,如果不在这个范围内「排除0」,则结果一定是NaN
    • [radix]不设置或者设置为0,默认值是10「特殊:如果左侧字符串是以“0x”开始的,默认值是16」
    • 从左侧[val]字符串中,查找出符合[radix]进制的字符,把找到的字符看做[radix]进制,最后转换为10进制。
    • 其他进制转换为10进制:按权展开求和。
    • parseInt 返回的结果,都是其他进制整数对应的10进制整数。
//  案例 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

    // 一个特例
   console.log( parseInt(0023))  // 19
// 数值 0023 ,
// 以0开头数字,会被浏览器认为是 8 进制数,
// 浏览器会将 0023 隐式转换成10进制整数展示
// 0023 由8进制转成10进制,按权展开
// 0*8^3 +  0*8^2 + 2*8^1 + 3*8^0 -> 0 + 0 +16 + 3
// -> 19


复制代码

什么是按权展开求和?

// '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...
复制代码
//  案例 2
    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] 进制。
 *
 * 所以上题可以分解步骤为 :
 * parseInt(27.2,0) -> 27
 *    [,radix] 进制:不设置或者设置为0,默认值是10
 *    parseInt(27.2,0) 即转换为10进制整数 -> 27
 * parseInt(0,1)  -> NaN
 * ·   [,radix] 进制:是2-36之间的整数,如果不在这个范围内「排除0」,则结果一定是NaN
 *     1 不在范围内所以返回  NaN
 * parseInt('0013',2) -> 1
 *      [,radix] 进制 为 2, 有效数字值只有  0 和 1 ,
 *      按规则从左往右,在字符串 '0013' 中查找有效数字,
 *      找到的有效数字字符串是001',因为找到'3'时,已经不是2进制的有效数字值了,停止继续查找。
 *      再把 '001' ,按权展开,由2进制整数转换为10进制整数。
 *     -> 0*2^2 + 0*2^1 + 1*2^0
 *     -> 1
 * parseInt('14px',3) -> 1
 *   -> 同理,只有 '1' 符合三进制的
 *   ->  1*3^0 按权展开 ,由3进制整数转换为10进制整数
 *    -> 1
 * parseInt(123,4)  -> 27
 *      '123' 符合四进制
 *      1*4^2 + 2*4^1 + 3*4^0  -> 16 + 8 + 3 -> 27
 *  ---> 新数组:[27,NaN,1,1,27]
 */

/**
 * map( callback , thisArg)   「Array.prototype.map()」
 * 返回一个新数组。迭代原数组的每一项,每迭代一次执行一次 callback ,然后将 callback 返回值,按索引值存储在要返回的新数组中。
 *
 * callback(currentValue[, index[, array]
 * 回调函数会被自动传入三个参数:数组元素,元素索引,原数组本身。每执行一次这个函数,都把它的返回值按 [,index] 值存储在新数组中。
 *
 */

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享