把其他数据类型的值转换成 Number 类型
主要有3种 Number() 、parseInt() 和 parseFloat() 三个函数。
Number() 函数
实际开发中,一般不主动调用 Number() 函数,Number() 函数多用于隐式转换中。
以下情况浏览器会使用 Number() 来进行隐式转换:
- 进行数学运算时, 比如减法运算
10 - '5'
- 使用 isNaN() 函数检测一个值是不是有效数字时
- == 比较时
- …
Number() 函数 转换规则如下:
- 把字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN。
- 把布尔转换为数字:true->1 false->0
- null -> 0
- undefined -> NaN
- Symbol 无法转换为数字,会报错。
- BigInt 会去除“n”(超过安全数字的,会按照科学计数法处理)
- 把对象转换为数字,(浏览器会做以下几步操作):
- 先调用对象的 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