从大厂面试题看数据类型转换

607 阅读8分钟

日常开发中经常遇到隐式转换,作为BUG产生元凶的隐式转换也能提高开发效率,现在我们一起来探索一下隐式转换的秘密

思维导图

UeThPf.png

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