1. 为什么有的编程规范要求用 void 0 代替 undefined?
Undefined 类型表示未定义,它的类型只有一个值,就是 undefined。任何变量在赋值前是 Undefined 类型、值为 undefined,一般我们可以用全局变量 undefined(就是名为 undefined 的这个变量)来表达这个值,或者 void 运算来把任意一个表达式变成 undefined 值。
但是呢,因为 JavaScript 的代码 undefined 是一个变量,而并非是一个关键字,这是 JavaScript 语言公认的设计失误之一,所以,为了避免无意中被篡改,我们最好使用 void 0 来获取 undefined 值。
2.字符串是否有最大长度
在 JavaScript 中,String 类型用于表示文本数据。官方文档指出,字符串对象的最大长度是 2^53 - 1 个字符,但理解这一长度的限制需要更多地关注字符串的内部编码方式。
UTF-16 编码
JavaScript 使用 UTF-16 来表示字符串,这意味着每个字符在字符串中以 16 位(2 字节)编码。然而,并不是所有 Unicode 字符都能用一个 16 位单元来表示。Unicode 字符集包括了一些需要更多位才能表示的字符,比如某些表情符号或特殊的文字。
UTF-16 采用了一种称为“代理对”(surrogate pair)的机制来表示这些超出基本多文种平面(BMP,即 U+0000 到 U+FFFF)的字符。代理对使用两个 16 位单元(四个字节)来表示一个字符。
示例
为了更好地理解这一点,看看下面的例子:
let str = 'A'; // 字符 'A' 的 UTF-16 代码单元是 U+0041
console.log(str.length); // 输出 1
let emoji = '😊'; // 表情符号的 UTF-16 代理对由 U+D83D 和 U+DE0A 组成
console.log(emoji.length); // 输出 2
在这个例子中,尽管表情符号被视为一个字符,但在 UTF-16 编码中,它实际上占用了两个 16 位单元。因此,使用 charAt、charCodeAt、length 等方法时,这些方法基于 UTF-16 代码单元来操作。
最大长度的实际含义
既然字符串最大长度为 2^53 - 1,这意味着最大可以有 2^53 - 1 个 Unicode 代码单元,而不是字符。如果字符串包含许多代理对字符(例如许多表情符号),实际能够使用的字符数将少于这个最大值,因为每个代理对字符占用了两个代码单元。
常见的开发误区
- 字符 vs. 代码单元:开发者容易将符号数量和代码单元数量混淆。
String.prototype.length返回的是代码单元的数量,而非字符的数量。 - 字符串操作函数:需要考虑代理对的情况。例如,
str[i]只与代码单元相关,而不是字符。 - 遍历字符串:要正确地处理所有 Unicode 字符,可以使用
for...of迭代字符串,因为它以字符为单位工作,而不是代码单元。
应对方法
如果需要准确处理包括代理对字符在内的字符串,可以使用 ES6 提供的工具,例如:
for (let char of '😊') {
console.log(char); // 直接输出整个表情符号,而非其各个代码单元
}
console.log([...emoji].length); // 使用扩展操作符处理字符串,正确输出 1
总结
JavaScript 中的字符串长度并非简单的字符计数,而是基于 UTF-16 编码单元的计数。因此,最大长度 2^53 - 1是单元的最大值,其中可能包含代理对,因此字符数可能更少。理解编码细节对于正确处理字符串至关重要。
3. 0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?
浮点数表示
在JavaScript中,(以及大多数编程语言中),浮点数是用有限的二进制位来表示的。这意味着某些十进制数在二进制中不能被精确表示,因此会出现精度误差。例如,0.1和0.2这样的数在二进制中无法精确表示,而是被表示为某个接近值。在计算机系统中,浮点数通常使用IEEE 754标准表示,这是一种基于二进制的浮点数表示方法。下面我们来分别看看0.1是如何在二进制中表示的。
小数0.1在二进制中的表示是一个无限循环小数。首先我们来看一下如何通过手动计算来获取0.1的二进制表示,以下是一个步骤:
- 乘2取整:
- 0.1 × 2 = 0.2 → 整数部分是0
- 0.2 × 2 = 0.4 → 整数部分是0
- 0.4 × 2 = 0.8 → 整数部分是0
- 0.8 × 2 = 1.6 → 整数部分是1
- 0.6 × 2 = 1.2 → 整数部分是1
- 0.2 × 2 = 0.4 → 整数部分是0
- ...(这个过程以后会不断重复)
因此,0.1在二进制中的表示是:0.0001100110011001100110011001100110011...(无限循环)。
IEEE 754标准表示
上述无穷二进制小数在计算机内部存储为有穷位数的二进制数,根据IEEE 754标准,浮点数由三个部分组成:
- 符号位(1 位): 正数为0,负数为1
- 指数(8 位): 表示数值的幂(需要加一个偏移量,单精度为127)。
- 尾数(23 位,单精度):二进制有效数字的小数部分。
例如,0.1和0.2在JavaScript中会被近似为:
- 0.1的 IEEE 754单精度表示大约为:0 01111011101 10011001100110011001101
由于上述原因,当你进行浮点数运算时,这些精度误差可能会累积。例如:
0.1 + 0.2 // 实际上并不严格等于0.3
如何正确比较 0.1 + 0.2 是否等于 0.3
Number.EPSILON 是JavaScript中的一个常量,是数字中 1 和可表示的比 1 大的最小数字之间的差值,因为双精度浮点格式只有 52 位来表示尾数,并且最低位的有效值为 2^-52。
Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON;
这行代码用了绝对值函数 Math.abs 来计算, 0.1 + 0.2 和 0.3 的差值,并用 <= Number.EPSILON 来判断这个差值是否很小,是不是在可接受的精度误差范围内。这实际上是在做浮点数比较的一个常用技术。
换句话说,这段代码是为了验证在计算 0.1 + 0.2 过程中,由于浮点数精度误差,结果差值是否可以忽略不计。这个代码返回的结果通常是 true, 这意味着在正常浮点数精度误差内,0.1 + 0.2 可以被认为是等于 0.3。