日常开发中经常遇到隐式转换,作为BUG产生元凶的隐式转换也能提高开发效率,现在我们一起来探索一下隐式转换的秘密
思维导图
JS两大数据类型
首先我们来了解一下JS的数据类型
基本数据类型(值类型/原始值类型)
- number
- string
- boolean
- null
- undefined
- symbol
- bigint
引用数据类型
- object
- 普通对象 Map
- 实例对象
- 数组对象 Set
- 正则对象
- 日期对象
- Math数学函数对象
- prototype 原型对象
- function
其中symbol / BigInt 为ES6新增的两种数据类型
symbol
// Symbol([value]) 创建唯一值
console.log(Symbol('A') === Symbol('A')) // false
BigInt
BigInt的加入是因为jS存在最大、最小安全数字,超过会引起计算精度的问题,他们分别是
console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991
BigInt(10) => 10n BigInt类型的标志是后面多出来一个n
let num = 9007199254740993
console.log(num + 1) // 9007199254740992
let bignum = 9007199254740993n // 后面加n
console.log(bignum + 1n) // 9007199254740994n
几个特殊的数字类型
- NaN
console.log(typeof NaN) // number
console.log(NaN === NaN) // false
console.log(Object.is(NaN, NaN)) // true ,这里为true的原因是Object.is 内部做了处理
- 想检测一个值是否为有效数字
console.log(isNaN(10)) // false
console.log(isNaN('AA')) // true 在检测的时候如果当前这个值不是number类型,先隐式转换为number类型(Number)再判断
- Infinity 无穷大值
小试牛刀
let res = parseFloat('left: 200px')
if(res === 200) {
alert(200)
} else if(res === NaN) {
alert(NaN)
} else if(typeof res === 'number') {
alert('number') // alert输出的结果都会转换成字符串
} else {
alert('Infinity number')
}
这是一道大厂面试题 parseFloat 会检测有效数字 因为第一位是字符串'l' 所以会返回NaN ,而NaN !== NaN 所以做好会alert 字符串'number'
数据类型检测
那知道了数据类型有哪些,怎么来检测变量的数据类型,就用到了四大法宝
typeof
- 特性 检测出来的结果是字符串
- BUG typeof null => "object" 计算机存储采用二进制 如果开头为000 表示为object
- 局限 不能检测对象细分的类型,不论是数组对象还是正则对象等结果都是"object"
小试牛刀
var a = typeof typeof typeof [12, 22]
console.log(a)
instanceof
检测某一个实例是否属于某一个类
- 特性 可以检测数组对象和正则对象了
- 局限 用instanceof检测的时候,只要当前的这个类在实例的原型链上(可以通过原型链__proto__找到它),检测出来的结果都是true,所以数组用Object检测也为true 基本数据类型的值是不能用instanceof来检测的
console.log([] instanceof Array); //->true
console.log(/^$/ instanceof RegExp); //->true
console.log([] instanceof Object); //->true
console.log(1 instanceof Number); //->false
constructor
检测某一个实例是否属于某一个类, constructor可以避免instanceof检测的时候,用Object也是true的问题
console.log([].constructor === Object); //->false
Object.prototype.toString.call([value])
找到Object原型上的toString方法,让方法执行,并且让方法中的this变为value(value->就是我们要检测数据类型的值)
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(null) // "[object Null]"
数据类型转换
接下来是我们的重头戏,数据类型转换 (主动转换 / 隐式转换)
其他转换为Number
- 主动转换
- 特定需要转换为Number的
- parseInt/parseFloat([val])
- 隐式转换(浏览器内部默认先转换为number再进行计算)
- isNaN([val])
- 数学运算(特殊情况: + 在出现字符串的时候不是数学运算,是字符串拼接 PS: +i/++i/i++ 这种情况为数学运算 var n = '10' ++n)
- 在==比较的时候
Number机制
- 只要出现非有效数字 结果就是NaN null转换为0 undefined转换为NaN
- 对象转数字 先valueOf 没有原始值仔toString转换为字符串 在转换为数字
parseInt机制
- 先变成字符串,从字符串左侧第一个字符开始,查找有效数字字符(遇到第一个非有效字符停止查找,把找到的有效数字字符转化为数字,如果一个都没有结果就是NaN)
parseFloat机制
- 比parseInt多识别一个小数点
其他类型转String
其他类型转换为字符串,一般都是直接""包起来 只有{}普通对象调用toString()不是转换字符串而是检测数据类型,原因是调用了原型上的toString
- 主动转换
- toString()
- String()
- 隐式转换(一般都是调用其toString)
- 加号运算的时候,如果某一边出现字符串,则是字符串拼接(特殊情况,如果+只有一方有值则为数字运算)
- 把对象转换为数字,需要先toString()转换为字符串,再去转换为数字
- 基于alert/confirm/prompt/document.write...这些方式输出内容,都是把内容先转换为字符串,然后再输出的
把其它数据类型转换为布尔
- 基于以下方式可以把其它数据类型转换为布尔
- ! 转换为布尔值后取反
- !! 转换为布尔类型
- Boolean([val])
- 隐式转换
- 在循环或者条件判断中,条件处理的结果就是布尔类型值、
规则:只有 ‘0、NaN、null、undefined、空字符串’ 五个值会变为布尔的FALSE,其余都是TRUE
在==比较的过程中,数据转换的规则:
- 类型一样的几个特殊点
- {}=={}:false 对象比较的是堆内存的地址
- []==[]:false
- NaN==NaN:false
- 类型不一样的转换规则
- null==undefined:true,但是换成===结果是false(因为类型不一致),剩下null/undefined和其它任何数据类型值都不相等
- 字符串==对象 要把对象转换为字符串
- 剩下如果==两边数据类型不一致,都是需要转换为数字再进行比较
小试牛刀
面试题一、
let result = 10+false+undefined+[]+'Tencent'+null+true+{};
console.log(result);
我们来一步步拆解一下 10 + false 数学运算 需要先讲false转换为 number Number(false) > 0 所以答案是10 10 + undefined 数学运算 10 + NaN 结果还是NaN NaN + [] 数学运算 但是[]参与数字计算是需要先转为字符串再转为数字 [].toString() > "" NaN + "" 结果还是'NaN' 'NaN' + 'Tencent' 遇到了字符串所以是字符串拼接 结果是'NaNTencent' 'NaNTencent' + null 仍然是字符串拼接 String(null) > 'null' 结果是'NaNTencentnull' 'NaNTencentnull' + true 仍然是字符串拼接 String(true) > 'true' 结果是'NaNTencentnulltrue' 'NaNTencentnulltrue' + {} 仍然是字符串拼接 String({}) > "[object Object]" 结果是'NaNTencentnulltrue[object Object]'
let result = 10+false+undefined+[]+ +'Tencent'+null+true+{};
console.log(result);
如果在 'Tencent' 前加一个'+'结果就大不一样了 我们来拆解一下 'NaN' + + 'Tencent' 首先会转换 + 'Tencent' 因为+号只有一侧有值所以是数学计算 Number('Tencent') > NaN 所以结果为'NaNNaN' 'NaNNaN' + null 数学计算 Number(null) > 0 结果为'NaNNaNnull' 'NaNNaNnull' + true 数学计算 Number(true) > 1 结果为'NaNNaNnulltrue' 'NaNNaNnulltrue' + {} 数学计算 但是{}参与数字计算是需要先转为字符串再转为数字 String({}) > "[object Object]" 此时 + 的一边出现了字符串 所以就变成了 字符串拼接 结果是 "NaNNaNnulltrue[object Object]"
面试题二、
parseInt("")
Number("")
isNaN("")
parseInt(null)
Number(null)
isNaN(null)
parseInt("12px")
Number("12px")
isNaN("12px")
parseFloat("1.6px")+parseInt("1.2px")+typeof parseInt(null)
isNaN(Number(!!Number(parseInt("0.8"))))
typeof !parseInt(null) + !isNaN(null)
parseInt("") -> NaN 没有任何一个有效数字 所以为NaN
Number("") -> 0
isNaN("") -> false isNaN会先把值转换为number isNaN(0)
parseInt(null) -> NaN parseint会先把值转换为string
Number(null) -> 0
isNaN(null) -> false
parseInt("12px") -> 12
Number("12px") -> NaN
isNaN("12px") -> true
parseFloat("1.6px")+parseInt("1.2px")+typeof parseInt(null) -> '2.6number'
1.6 + 1 + typeof NaN
1.6 + 1 + 'number'
'2.6number'
isNaN(Number(!!Number(parseInt("0.8")))) -> false
typeof !parseInt(null) + !isNaN(null) -> 'booleantrue'
面试题三、
let arr = [10.18, 0, 10, 25, 23];
arr = arr.map(parseInt);
console.log(arr);
举一反三的时候到了,最后一道思考题,有兴趣的童鞋可以留言写下答案和思路
加更parseInt
parstInt会接收两个参数 [value],[radix]
- [radix]这个值是一个进制,不写或者写0默认都按照10处理(特殊情况:如果value是以0X开头,则默认值不是10而是16)
- 进制有一个取值的范围:2~36之间,如果不在这之间,整个程序运行的结果一定是NaN
- 把[value]看做[radix]进制,最后把[radix]进制转化为十进制
如何将其他进制转换为十进制呢
- 拿到位权值 [位权值:每一位的权重,个位是0,十位是1...]
- 结果就是 当前数字 * 进制数 ^ 位权值
接下来我们来解答一下面试题三
从左侧第一个值开始查找符合这个进制的值,找到符合[radix]进制的值,遇到不符合的则,停止查找,把找到的值变为数字,再按照把[radix]转换成为十进制的规则处理
- parseInt('10.18', 0) -> 10 不写或者写0默认都按照10处理
- parseInt('0', 1) NaN 2~36之间,如果不在这之间,整个程序运行的结果一定是NaN
- parseInt('10', 2) 12^1 + 02^0 -> 2
- parseInt('25', 3) 2*3^0 -> 2 PS: 5 不符合3进制
- parseInt('23', 4) 24^1 + 34^0 -> 11