发布于 2026-01-06 6 阅读
0

理解 Javascript 中一些奇怪的部分

理解 Javascript 中一些奇怪的部分

由于 JavaScript 是一种弱类型语言,值可以在不同类型的值之间自动转换,这被称为隐式类型转换。不了解 JavaScript 特性的人往往会通过分享类似的表情包来嘲笑这门语言。

js 梗

在这篇文章中,我将逐一分析这个梗图中的每一个例子,并尽量简要地解释,如果可能的话,还会提供参考资料。

虽然这个梗图里提到的每一个例子都足以单独写一篇长文,但我为了简洁明了、方便新手理解,还是尽量写得简短一些。以后可能会单独对一些例子进行更详细的阐述。

让我们开始吧

1.

console.log(typeof NaN) // "number";
Enter fullscreen mode Exit fullscreen mode

根据 ECMAScript 标准,数字应为 IEEE-754 浮点数据。这包括无穷大、负无穷大以及 NaN。

NaN 代表“非数字”。

让我们看看什么时候会返回 NaN:

  • 零除以零。(0/0)
  • 无穷大除以无穷大。(无穷大 / 无穷大)
  • 无穷大乘以零。(无穷大 * 0)
  • 任何以 NaN 为操作数的运算。(NaN + 2)
  • 将任何未定义或非数字字符串转换为数字。Number("abc")

发现什么共同点了吗?
只有当进行数值运算时才会返回 NaN。

根据定义,NaN 是那些结果为未定义“数值”的操作的返回值。
因此,NaN 的类型显然是数字。

参考 :

2.

console.log(999999999999) // 10000000000000;
Enter fullscreen mode Exit fullscreen mode

这是因为 JavaScript 只支持 53 位整数。JavaScript
中的所有数字都是浮点数,这意味着整数总是以 53 位整数的形式表示。

sign × mantissa × 2^exponent
Enter fullscreen mode Exit fullscreen mode

小数部分占用第 0 到 51 位,指数部分占用第 52 到 62 位,符号部分占用第 63 位。
因此,如果数字很大,js 会出现精度损失,最低有效位会消失。

JavaScript 最近新增了“BigInt”类型,解决了表示大于
2^53 - 1 的整数的问题。

参考:

3.

console.log(0.5 + 0.1 == 0.6); // true
console.log(0.1 + 0.2 == 0.3); //false
Enter fullscreen mode Exit fullscreen mode

我上面已经提到过了,但还是再说一遍:JavaScript 使用的是符合 IEEE 754 标准的 64 位浮点数表示法。64
位二进制浮点格式根本无法精确表示像 0.1、0.2 或 0.3 这样的数字。虽然大多数语言会对这些数字进行四舍五入以得到预期的结果,但 JavaScript 不会。

如果将 0.1 转换为二进制表示,你会得到 0.00011001100110011...(0011 无限重复)。
这篇文章对此进行了详细解释。
在双精度浮点数(JS)中,使用 53 位,因此原本无限的表示会被舍入到 53 位有效数字。所以十进制的结果总是不精确的。
这个 Stack Overflow 回答也解释得很清楚 - https://stackoverflow.com/a/28679423

参考 :

4.

console.log(Math.max())  //-Infinity
conosle.log(Math.min()) //+Infinity

Enter fullscreen mode Exit fullscreen mode

首先要明确一点……
它们不返回最大值或最小值,对于这些需求,我们有 Number.MAX_VALUE 和 NUMBER.MIN_VALUE。

Math.max() 和 Math.min() 是静态方法,分别返回各自参数中的最大值和最小值。
因此,根据规范,如果调用它们时不带任何参数,它们将分别返回 -Inf 和 +Inf。

虽然规范中没有解释为什么会这样,所以我查看了 Chromium 的源代码来寻找答案。
说实话,我找到了我之前想到的答案:当你使用单个参数调用 `Math.max()` 方法时(例如 `Math.max(100)`),它会将结果与负无穷大进行比较,并返回数值本身,因为如果数值有效,它总是大于负无穷大。`Math.min
()` 方法也是如此。
因此,当没有参数进行比较时,它会返回负无穷大,因为它是介于 0 和负无穷大之间的最大值。

5.

console.log([]+[]) // ""

Enter fullscreen mode Exit fullscreen mode

根据规范,当 JavaScript 遇到加法运算符 ( + ) 时,它会执行以下步骤。

这些步骤将为接下来的几个例子奠定基础。

a. 将两个操作数都转换为原始类型
。b. 如果其中一个操作数是字符串类型,则返回字符串拼接后的结果
。c. 否则,使用 `ToNumber()` 方法将两个操作数都转换为数字类型。d
. 如果一个操作数的类型与其他操作数不同,则抛出 `TypeError` 异常
。e. 否则,返回数学和。

那么让我们一起来看看这个例子:

a. 首先,如果操作数不是原始值,则将其转换为原始值,而本例中的操作数不是原始值。

b. 现在,ToPrimitive 函数将对象类型转换为基本类型。除了输入之外,ToPrimitive 还接受一个可选的“首选类型”参数,该参数用于向 ToPrimitive 提供类型提示。

c. 转换为原始类型后,如果其中任何原始类型是字符串类型,则进行字符串连接,在这种情况下是真的(如下所述),因此我们看到结果为“”。

让我们看看 ToPrimitive 是如何工作的:

  • 如果没有给出提示,ToPrimitive 默认将提示设置为 Number。

  • 确定提示后,它会按照预定义的顺序将其与两个方法的列表进行比较。
    如果提示是数字,则使用 [valueOf, toString] 方法;如果提示是字符串,则使用 reverse 方法。

  • 在这种情况下,它使用默认提示,因此执行以下步骤:
    a) [].valueof 返回数组本身,因为它不是原始类型,所以它转到第二个方法
    ;b) [].toString 返回 "",因为它返回原始值,所以它作为原始值返回。

参考资料:
https://tc39.es/ecma262/#sec-addition-operator-plus

6.

console.log([]+{}) // "[object Object]"

Enter fullscreen mode Exit fullscreen mode

补充以上解释,{}.toString 是 [object Object],所以通过字符串连接我们得到这个结果。

7.

{} + []
Enter fullscreen mode Exit fullscreen mode

现在,这个例子会返回与 [] +{} 相同的结果。

但这是否意味着这个梗图有拼写错误?

不,但如果你在 Chrome 或 Firefox 的控制台中运行这个例子,它会返回 0。
这是因为开头的对象字面量会被视为一个空代码块,被解释器忽略,最终只剩下
表达式“+ []”。
现在,一元运算符“+”会将其操作数转换为数字,而 Number([]) 等于零。

参考:
- https://tc39.es/ecma262/#sec-unary-plus-operator

8.


console.log(true + true + true) // 3
console.log( true - true) //0

Enter fullscreen mode Exit fullscreen mode

根据算法第五点,我们已经得到了一个原始值(布尔值)。由于操作数都不是字符串,我们将前两个操作数转换为数值型。根据规范,`ToNumber(Boolean)` 函数的值为 1 表示真,0 表示假。
因此,真 + 真 = 1 + 1 = 2

不,我们有“2 + true”,它的处理方式与前两个操作数相同,我们得到 3。

所以现在看来​​,这两个结果都说得通了。

参考链接:
- https://tc39.es/ecma262/#sec-tonumber

9.

console.log(true ==1) //true
console.log(true ===1) //false
Enter fullscreen mode Exit fullscreen mode
  • 第一个表达式使用抽象相等性比较进行比较,这允许强制转换,因此根据规范

如果 Type(x) 为布尔值,则返回 ToNumber(x) == y 的比较结果。

因此,利用这一点,我们得到 ToNumber(true) == 1

  • 第二个表达式使用严格相等比较进行比较,严格相等比较不允许强制类型转换,因此根据规范……

如果 Type(x) 与 Type(y) 不同,则返回 false。

显然,类型不同,因此结果为假。

10.

console.log((!+[]+[]+![]).length) // 9 

Enter fullscreen mode Exit fullscreen mode

我第一次看到这个的时候简直震惊了 :p

让我们把它分成四部分。

让我们从 !+[] 开始。

这里,我们不有两个操作数,而是两个一元运算符(! 和 +)。由于 ! 和 + 的优先级相同,我们从左到右进行运算。
首先遇到的运算符是“!”,它的结合律是从右到左,所以我们计算“+[]”,结果为 0(我们在第七篇帖子中已经讨论过为什么结果为零)。

根据规范,否定运算符会将操作数转换为布尔值(如果操作数不是布尔值的话)。例如,如果我们将 0 转换为布尔值,则会得到 false。
因此,“!false” 返回 true,其类型为布尔值。

现在我们有 (true + [] + ![]).length

根据加法运算符的规则,对“true + []”进行求值,我们得到字符串类型的结果“true”,因为空数组的原始值是空字符串,如果任何操作数是字符串类型,则执行字符串连接。

现在我们剩下 ("true"+![]).length

根据“!”运算符的规则,我们将[]转换为布尔值,结果为true(根据规范,ToBoolean(object)返回true)。
现在,将true替换为空数组,得到“!true”,结果为false。由于其中一个操作数是字符串类型,我们将这两个操作数连接起来,结果为“truefalse”。

现在很明显为什么 console.log("truefalse".length) 返回 9 了。

参考资料:
- https://tc39.es/ecma262/#sec-toboolean
- https://tc39.es/ecma262/#sec-logical-not-operator

11.

console.log(9+"1") //91

Enter fullscreen mode Exit fullscreen mode

我们之前已经讨论过这个问题,但我还是要再提一遍。
根据 JavaScript 规范,如果操作数中任何一个原始值是字符串类型,那么我们就将这两个操作数的原始值连接起来,结果就是“91”。

console.log(9-"1") // 90
Enter fullscreen mode Exit fullscreen mode

根据规范,在减法运算中,操作数被强制转换为数字,如果 ToNumber 的结果有效,则最终结果为数学减法。

console.log([]==0) //true
Enter fullscreen mode Exit fullscreen mode

如前所述,Double equals 使用抽象相等比较,允许强制类型转换,因此我们的空数组会被转换为其原始值,即空字符串。根据规范,

如果 Type(x) 为字符串且 Type(y) 为数字,则返回比较结果 ToNumber(x) == y。

ToNumber("") 为 0,所以才会得到 true。


所以下次如果有人给你发这个梗图,你就可以用这些话让他们闭嘴了。

文章来源:https://dev.to/royal_bhati/understanding-weird-parts-of-javascript-44o