一文详解JavaScript有那些数据类型
这同样是一道面试中常见的问题。
JS有8种数据类型,分别是:
- Null
- Undefined
- Boolean
- Number
- BigInt
- String
- Symbol
- Object
1. Null
Null表示变量定义了但值为空。Null类型只有一个null值,null是一个关键字,大小写敏感,一般对象和数组可以初始化为null。
2. Undefined
Undefined表示变量未定义。Undefined类型只有一个undefined值,undefined是一个全局变量。我们可以通过window.hasOwnProperty方法确定一个变量是不是全局变量,如下:
window.hasOwnProperty(undefined) // true window.hasOwnProperty(null) // false
可以确定undefined确实是一个全局变量,而null不是。
既然undefined是一个全局变量,我们是不是可以更改undefined的值呢?在控制台尝试
window.undefined = 1 window.undefined // undefined
可以看出来undefined并不能修改,这是为什么呢?我们猜测undefined作为window的属性,应该是不可修改的,我们用Object.getOwnPropertyDescriptor方法获取undefined属性的属性描述:
Object.getOwnPropertyDescriptor(window, 'undefined')
// 输出
{
configurable: false
enumerable: false
value: undefined
writable: false
}可以看出writable的值是false,表示该属性不可被更改
3. Boolean
Boolean类型表示一个逻辑实体,有两个值true和false。一般用于条件判断种,三元运算符(?:)、if、while、for中
4. Number
Number类型表示数字。我们可以通过Number.MAX_VALUE和Number.MIN_VALUE分别得到Number的最大和最小值:
Number.MAX_VALUE // 1.7976931348623157e+308 Number.MIN_VALUE // 5e-324
可以看出Number类型可以表示的值的范围是非常大,我们日常使用几乎不会用到这么大的数。不过这里有个问题,虽然可以表示这么大的数,但是我们可以看出有效位数是小数点后16位,在加整数位一共是17位,就是千万亿级别可以精确表示,再大后面就省略为0了,只有级别增加。所以要精确表示数字,我们得将数字的计算范围控制再安全边界以内,js给出了2个安全边界:
Number.MAX_SAFE_INTEGER // 9007199254740991 即 2^53 – 1 Number.MIN_SAFE_INTEGER // -9007199254740991 即 -(2^53 - 1)
为什么是这两个数呢,因为双精度浮点格式只有 52 位用于表示尾数,因此它只能安全地表示介于 -(2^53 – 1) 和 2^53 – 1 之间的整数。
这里就引出JS中Number的编码格式:
IEEE 754 双精度浮点数使用 64 位来表示 3 个部分:
- 1 位用于表示符号(sign)(正数或者负数)
- 11 位用于表示指数(exponent)(-1022 到 1023)
- 52 位用于表示尾数(mantissa)(表示 0 和 1 之间的数值)
尾数(也称为有效数)是表示实际值(有效数字)的数值部分。指数是尾数应乘以的 2 的幂次。将其视为科学计数法:
Number=(−1)sign⋅(1+mantissa)⋅2exponent
尾数使用 52 比特存储,在二进制小数中解释为
1.…之后的数字。
2^53表示成2进制是1后面53个0,需要54位来表示;而2^53-1表示成2进制是1后面52个1,我们在控制台验证:
Number.MAX_SAFE_INTEGER.toString(2) // 2^53-1 // 输出:'11111111111111111111111111111111111111111111111111111' Number.MAX_SAFE_INTEGER.toString(2).length // 输出:53 (Number.MAX_SAFE_INTEGER+1).toString(2) // 2^53 // 输出:'100000000000000000000000000000000000000000000000000000' (Number.MAX_SAFE_INTEGER+1).toString(2).length // 输出:54
可以看出,2^53-1是可以用53位精确表示的,而2^53却需要54位来精确表示,只是后面全是0还可以用52位尾数和指数精确表示,若+1,最后的尾数就被省略了,也就是说这个编码最多可以精确表示到2^53,这满足安全数+1也必须精确表示的定义,所以最大安全数就是2^53-1。
那么我们考虑一下小数的加法运算
0.1+0.2===0.3 // 这个成立吗?答案是false
0.1.toString(2)
// 输出:'0.0001100110011001100110011001100110011001100110011001101'
0.2.toString(2)
// 输出:'0.001100110011001100110011001100110011001100110011001101'
0.0001100110011001100110011001100110011001100110011001101 +
0.001100110011001100110011001100110011001100110011001101 =
0.0100110011001100110011001100110011001100110011001100111
// 计算小数位
binFrac = '0100110011001100110011001100110011001100110011001100111'
let value = 0;
for (let i = 0; i < binFrac.length; i++) {
if (binFrac[i] === '1') {
value += 2 ** (-(i + 1)); // 第 i 位对应 2^(-(i+1))
}
}
value
// 输出 0.30000000000000004由于二进制小数的精度,使得0.1+0.2时没有得到准确的0.3,0.1和0.2都是循环小数。是不是所有小数加法都这样呢?并不是,只有当两个操作数及其运算结果都能表示为 有限二进制小数(即分母为 2 的幂)时,加法才是精确的。例如 0.5 + 0.25 = 0.75 是精确的,因为 0.5 = 1/2, 0.25 = 1/4, 0.75 = 3/4,分母均为 2 的幂。而为了使得小数计算准确,我们应该排除最后的那个小数位,JS有一个Number.EPSILON表示1 与大于 1 的最小浮点数之间的差值,值为2^-52,此时我们可以这么比较0.1+0.2和0.3的值
0.1+0.2 -0.3 < Number.EPSILON // true Math.abs(0.1+0.2 -0.3).toString(2) // 输出:'0.000000000000000000000000000000000000000000000000000001' Number.EPSILON.toString(2) // 输出:'0.0000000000000000000000000000000000000000000000000001' (0.1+0.2).toString(2) // 输出:'0.0100110011001100110011001100110011001100110011001101' 0.3.toString(2) // 输出:'0.010011001100110011001100110011001100110011001100110011'
5. BigInt
BigInt可以表示比2^53-1更大的整数,可以表示任意大的整数。写法上在整数后面加n即可得到一个BigInt,或者使用BigInt函数,如下:
const big1 = 9007199254740991n; const big2 = BigInt(9007199254740991);
它与Nubmer的区别在于,不能使用Math对象中的方法,不能和Number类型的值直接做运算
1n+1 // 报错Uncaught TypeError: Cannot mix BigInt and other types, // use explicit conversions Number(1n)+1 // 输出2 BigInt(1) + 1n // 输出2n Math.abs(-1n) // 报错Uncaught TypeError: Cannot convert a BigInt value to a number
BigInt 不能参与 JSON 序列化(JSON.stringify(1n)会报错)。
6. String
String类型用于保存文本数据。字符串基本上表示为的 UTF-16 码元序列。在 UTF-16 编码中,每个码元都是 16 位长。那么String类型有最大长度限制吗?ECMAScript 规范规定字符串长度必须≤ 2^53 - 1 ,因为 .length 和索引访问(如charAt、[i])依赖于可精确表示的整数。但实际引擎(如 V8)出于性能考虑,通常限制为 2^30 - 1(约 10 亿)。”我们可以在控制台验证:
const a = new Array(2**32) // 报错:Uncaught RangeError: Invalid array length const a = new Array(2**32-1) // 正常
7. Symbol
Symbol是一种唯一并且不可变的原始值。每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。
const symbol1 = Symbol('a');
console.log(symbol1) // 输出:Symbol(a)8. Object
对象可以被看作是一个属性的集合。对象属性等价于键值对。属性键要么是字符串,要么是 symbol。当其他类型(如数字)用于索引对象时,值会隐式地转化为字符串。属性值可以是任何类型的值,包括其他对象,从而可以构建复杂的数据结构。
有两种类型的对象属性:数据属性和访问器属性。每个属性都有对应的特性。可以通过Object.defineProperty()设置属性特性,或通过Object.getOwnPropertyDescriptor()获取属性特性。
数据属性的特性有:
value: 通过属性的get获取到的值
writable:布尔值,表示属性是否可以通过赋值进行修改。
enumerable:布尔值,是否可以通过for...in循环枚举
configurable:布尔值,表示属性是否可以删除,是否可以更改为访问器属性,以及是否可以更改其特性。
访问器属性:
get: 当执行值的 get 访问时,使用一个空的参数列表调用函数获取属性值。
set: 使用包含赋予的值的参数调用函数。每当尝试更改指定属性时执行。
enumerable和configurable与上面一样。
在Object上有很多复杂的东西,关于对象的原型链、属性描述符、Proxy 等高级特性,我们将在后续文章中深入探讨。。
总结
到此这篇关于JavaScript有那些数据类型的文章就介绍到这了,更多相关JS数据类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论