JS 中有哪些数据类型?
在 JS 中,有以下 7 种内置的基本类型,也叫做原始类型:
-
number -
boolean -
string -
symbol -
null -
undefined -
bigint
和一个引用类型,也叫做复杂数据类型或者复合类型:
object
像由 Function(), Array(), RegExp(), Date(), Math(), Number(), String(), Boolean(), Proxy(), Promise(),Map() 等等这些内置的构造函数所产生的对象,都可以将其归于引用类型的范畴
// example
const date = new Date()
console.log(typeof date) // object
console.log(date instanceof Object) // true
类型转换
需要注意的一点是,JS 作为一门动态语言,它在编译时不会检查你所声明的变量的值和类型是否匹配,当运行时,我们才知道变量的值和类型。换句话说,**变量可以持有任何值, 值持有其类型。**这就涉及到类型转换这个十分令人头疼的问题
当对不同类型的变量之间做运算和比较时,都会涉及到类型转换
事实上,JS 引擎有着十分复杂的编译、执行流程,它甚至会采用一些延迟编译、重编译等手段来优化执行效率
附上经典图片一张:
简单讲解一下不涉及到类型转换的坑
typeof NaN === 'number',NaN是一个全局对象的属性,不可写、不可配置、不可枚举
console.log(Object.getOwnPropertyDescriptor(window, NaN))在 Chrome 控制台下结果是![]()
NaN意为 not a number,但其实 ECMA 标准将其归于 Number 的范畴,所以不用过于纠结这个问题。可参考这里关于好几个 9 变成 10 的 n 次方,是由于绝对值大于或等于 2^53 的数值文本过大,无法用整数准确表示
为什么是 2^53?因为 IEEE754 标准规定了浮点数由 64 位二进制数表示,有效数字由 52 位二进制数表示。但有效数字第一位总是 1 且不保存在 64 位之中,所以有 53 位数用来表示一个浮点数,那么大于 2^53 就会导致精度缺失,没有多余的位来描述这个数了
console.log(0.5 + 0.1 === 0.6) // true console.log(0.1 + 0.2 === 0.3) // false事实上,浮点数类型的值精度最高可达到小数点后 17 位(也和 IEEE754 标准有关喔)。这样子会带来一定精度上误差,是无法避免的。0.1 转化为二进制的结果是 0.0001100110011...,是个无限循环的数。它和 0.2 转化为二进制的结果相加,再转化为十进制,结果是 0.300 000 000 000 000 04。如果想做到在允许误差范围内比较,可以利用
Number.EPSILON,它代表了 1 和从 1 的左边最趋近于 1 的数的差值const a = 0.1 + 0.2 const b = 0.3 console.log(Math.abs(a - b) < Number.EPSILON) // true,可以认为 a 和 b 相等console.log(Math.max()) // -Infinity console.log(Math.min()) // Infinity其实
Math.max()的默认值就是 -Infinity。Math.max()接受若干个数字作为参数,并且返回其中的最大值。
接下来让我们简单了解类型转换的规则:
基本类型相关
-
其他类型转化为
Boolean(使用 Boolean(),表格基于《JavaScript 高级程序设计第 4 版》,做了些许删改)数据类型 转换为 true的值转换为 false的值String 非空字符串 ""(空字符串) Number 非零数值(包括无穷值) 0、NaN Undefined undefined Null null Symbol 都是true BigInt 非零数值(包括无穷值) 0n、NaN
tips: JS 的假值只有以下几个:
- null
- undefined
- 0
- ""
- NaN
- 0n
- false
-
其他类型转化为
Number(讨论使用Number()的情况)-
String:- 如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值
因此,Number("1") 返回 1,Number("123") 返回 123,Number("011") 返回 11(忽略前面
的零)
-
如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)
-
如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整
数值
-
如果是空字符串(不包含字符),则返回 0
-
如果字符串包含除上述情况之外的其他字符,则返回 NaN
-
Boolean: true转化为1, false转化为0 -
null: 0 -
undefined:NaN -
BigInt: 转化成相应的数值或者无穷,超过“安全范围”会出现精度缺失 -
Symbol: 无法转换为Number
-
-
其他类型转化为
String(讨论使用String()的情况)Number: 直接转化为字符串,但是十六进制数和二进制数会先转化为十进制数null: "null"undefined: "undefined"Boolean: true 转化为"true",false 转化为 "false"Symbol: 比如Symbol('foo')为转化成字符串字面量 “Symbol(foo)”BigInt: 同Number
复杂类型相关
对于任何转换,Object 会优先调用内置的[[ToPrimitive]],可以通过给对象添加[Symbol.toPrimitive]属性,改写内置的[[ToPrimitive]]。需要注意的是,[Symbol.toPrimitive]() {}只允许返回一个基本类型值
const obj = {
valueOf() {
return 123
},
toString() {
return 'I am an Object'
},
[Symbol.toPrimitive]() {
return true
}
}
console.log(String(obj)) // 'true'
console.log(obj == 1) // true
如果没有该属性,其次是调用 valueOf(),如果 valueOf() 不存在或者不返回一个基本类型值,最后再调用 toString()。toString() 也不返回呢?那么转换就失败了,会出现 error
这是对一个对象做转换所调用方法的顺序
操作符相关
- ==
==仅仅比较等号两边的值是否相等,故其允许对两边的值在比较过程中做隐式转换,这也牵扯到很多类型转换的问题
- 布尔值与其他值比较,先将布尔值转换成 number 类型
- 对象与基本类型的值比较,先让对象转化为原始值,即按照上文的顺序转换
- 字符串与数字比较,先将字符串转化为数值
console.log(null == undefined) // true
- +
-
一元运算符
+可以让值强制转换为数值 -
二元运算符
+,如果一个操作数是字符串(或者是可以转化为字符串的对象),则执行字符串拼接,否则执行加法操作
- -
-则会尝试让操作数都变成数值
- 字位操作符(^, | ,~)
- 让操作数强制转化为32位整数,再对其进行位运算
- !
- 强制让操作数转化为真值或者假值
解答
罗列了这么多规则,那现在我们给出Thanks for inventing JavaScript一图中的答案了
-
[] + []; // ""在
+的转化规则下,两个空数组都调用toString()成为空字符串再进行拼接,答案自然是空字符串 -
console.log([] + {}) // "[object Object]" console.log({} + []) // 0在
[] + {}中,JS 引擎解析这条语句,认为它在做空数组 + 空对象这一操作,而{}被转化为字符串的结果是[object Object]而在
{} + []中,引擎认为{}是一个空的作用域,把它忽略了,所以变成了一元操作符+再跟上一个空数组。所以,+把[]转化为 0 -
console.log(true + true + true === 3) // true console.log(true - true) // 0 console.log(true == 1) // true console.log(true === 1) // falsetrue + true + true,true 是布尔值,不满足以下两个条件之一:- 是字符串
- 是可以调用
[[ToPrimitive]]转化为字符串的对象
故
+被解释为数字加法,true 转化为1,进行相加true - true,true 被转化为1==将 true 转化为1===是严格相等,同时比较值和类型是否相等
-
(!+[] + [] + ![]).length; //9第一眼看上去可能比较迷惑,但也不复杂。
!+[],先执行+[]操作,结果为0。0是假值,!0得到布尔值true- 现在式子变成了
true + [] + ![],[]能够转换为空字符串,故true + []结果为"true",式子变为"true" + ![] []是真值,![]得到布尔值 false。"true" + false结果是"truefalse",length属性返回其长度9
-
剩下三个比较简单,就不在此赘述
小结
可以发现,JS 所涉及到的类型转换是比较复杂的。应该在经验的累积上,进行记忆。我们在日常开发时,也应该考虑清楚这部分的代码怎么写,力求简洁易读