6 个 JavaScript WTF 以及从中可以学到什么
嗨!我相信,在你的 JavaScript 开发历程中,你每天 至少会遇到一次“undefined”不是函数,或者你会问自己为什么NaN 的类型实际上是数字。好吧,有时候 JavaScript 就是想给你来个压力测试。
在本文中,你将踏上一段探索这门美丽编程语言有趣(也充满黑暗)一面的旅程。让我们开始吧!
1. 最小值 > 最大值
Math.min() > Math.max() //true
解释:
好的,首先让我们定义一些事情。
Math
是一个内置对象,具有数学常量和函数的属性和方法。不是函数对象。- 静态函数
Math.max()
返回传递给它的最大值数字,或者NaN
如果任何参数不是数字并且不能转换为数字,则返回最大值数字。
完美,现在我们知道了 Math 对象在 JavaScript 中代表什么,以及.max()静态函数的作用。同样,.min()函数会执行相反的操作。到目前为止,我们的直觉可能会认为,如果没有提供任何参数,Math.max()应该返回Number.MAX_VALUE 。
然而,这种假设是错误的。原因如下。想象一下,你需要实现一个函数,用于从数组中找出最大值。这很简单!最简单的方法是遍历整个数组,比较元素并存储最大值。但问题在于!存储最大值的变量应该用一个非常非常小的值初始化,也就是最小值。
你可能现在会想,好吧,JavaScript 中最小的值是Number.MIN_VALUE (5e-324),没错。但是,JavaScript 为这种特殊情况准备了一些东西,那就是Infinity。
全局
Infinity
属性是表示无穷大的数值。
最后,.max()函数的完整描述:
Math.max() 返回给定数字中的最大值。如果一个或多个参数无法转换为数字,则返回 。如果没有提供任何参数,则
NaN
返回结果为。-Infinity
Math.min() > Math.max() -> Infinity > -Infinity //true
要点:
- 什么是Math对象
- min()和max()函数的行为方式
- JavaScript 中的无限对象
2. 0.1 + 0.2 = ?
嗯,这太简单了。0.1 + 0.2 = 0.3,对吧?JavaScript 里可不是这样!(或者 JAVA、C++、C#,或者……你懂的)
0.1 + 0.2 === 0.3 //false
解释:
这怎么可能呢?在你重新思考之前学过的所有基础数学知识之前,让我先来介绍一下浮点数学。
计算机本身只能存储整数,因此需要某种方式来表示十进制数。这种表示方式存在一定程度的不准确性。
这个问题很复杂,需要投入大量的时间。不过,我会尽量简化,以适应这个特殊情况。
在十进制中,唯一能清晰表达的分数是以质因数为底的分数(½、¼、½ 等)。相比之下,½ 的分数有循环小数 (0、33333 等)。现在,如果我们将此信息应用于二进制,那么清晰的分数是½、¼ 和 ½,而½ 和 ½ 的分数有循环小数。这导致本例中出现了一些剩余小数。
0.1 + 0.2 === 0.30000000000000004 //true
当您对这些循环小数进行计算时,最终会得到剩余的数字,当您将计算机的 2 进制(二进制)数字转换为更易于人类阅读的 10 进制数字时,这些剩余的数字会被结转。
要点:
- 浮点数学概述
- 这个概念适用于大多数编程语言
3. 巴纳纳
好,解决完那道难题后,我们来做点有趣的事吧!
"b" + "a" + +"a" + "a" -> baNaNa
解释:
与其他两个WTF不同,这个稍微简单一些。这是因为你已经解决了75%的问题。现在我们只需要弄清楚一个小细节:++“a”会返回什么。
JavaScript 语法有效,因为第二个+不是加法运算符,而是一元运算符。
一元运算符+将其操作数转换为 Number 类型。任何不支持的值都将被评估为
NaN
。
这太轻松了!所以我们的表达式将如下所示,因为“a”不能转换为数字。
"b" + "a" + NaN + "a" -> baNaNa
为了得出结论,我们需要确认另一个谜题。String + String + NaN + String会返回什么?加法运算符会如何表现?
加法运算符可以执行字符串连接或数字加法。
因此,按照这个特定的顺序,可以进行两种类型的加法:字符串连接或数字加法。算法的工作原理如下:
使用ToPrimitive()函数将操作数转换为原语。
如果其中一个操作数是String,则将另一个操作数转换为 String 并执行字符串连接。否则,将两个操作数都转换为Number,并执行数值加法。
"b" + "a"-> "ba"
"ba" + NaN -> "ba" + "NaN" -> "baNaN"
"baNaN" + "a" -> "baNaNa"
要点:
- 什么是一元运算符
- 加法运算符算法
- ToPrimitive()函数及其一个用例
4.声明前初始化?
以此代码为例:
message = "don't forget to hit the heart button if you liked it.";
console.log(promoteArticle("Stranger"));
function promoteArticle(name) {
return `${name}, ${message}`;
};
var message;
控制台会提示什么?是ReferenceError错误,提示信息未定义吗?或者可能是字符串"Stranger, undefined"。不,一定是TypeError 错误,因为 promoteArticle 不是一个函数。
幸运的是,输出正是我们想要的:“陌生人,如果你链接了,别忘了点击爱心按钮”。但为什么呢?这要归功于 JavaScript 的“提升” (流行语) 。
提升是 JavaScript 的默认行为
moving declarations to the top
。
注意:这仅适用于使用 var 关键字定义的变量和声明的函数。
利用这条信息,我们可以声称我们的代码在编译后将如下所示:
function promoteArticle(name) {
return `${name}, ${message}`;
};
var message;
message = "don't forget to hit the heart button if you liked it.";
console.log(promoteArticle("Stranger"));
让我们一步一步来。promoteArticle () 函数位于顶部,因为函数声明是第一个移到顶部的元素,其次是 var 变量声明。
此外,没有抛出任何错误并且消息具有正确的值,因为在调用函数时,变量已被声明和初始化。
为了确保不引起任何混淆,我将提到声明函数和表达式函数之间的区别。下面是一个包含这两种类型的示例。
function declaredPromoteArticle(name) {
return `${name}, ${message}`;
};
var expressionPromoteArticle = function(name) {
return `${name}, ${message}`;
}
编译后:
function declaredPromoteArticle(name) {
return `${name}, ${message}`;
};
var expressionPromoteArticle; // just the variable definition was hoisted
expressionPromoteArticle = function(name) {
return `${name}, ${message}`;
}
要点:
- 什么是提升
- 函数声明与函数表达式
5. typeof NaN == 'number'
这句话可能看起来有点奇怪,尤其是从词汇角度来说,“非数字就是数字”,但马上就能理解了。首先,我们来看一下它的定义:
全局 属性
NaN
是表示非数字的值。
NaN 的定义简洁明了,但我们可以从“全局”这个词中发现其中的奥秘。与我们的第一直觉相反,NaN 并非关键字(例如 null、if、var 等),而是一个全局属性。哪个全局对象可能包含这个属性?没错,你猜对了,就是Number 对象。
typeof NaN == 'number' -> typeof Number.NaN == 'number' //true
Number.MIN_VALUE
可表示的最小正数 - 即最接近零的正数(但实际上不是零)。 特殊的“非数字”值。Number.NaN
你可能会问,为什么我还提取了 MIN_VALUE 属性。这是因为这样可以更清楚地解释为什么 JavaScript 编译器无法区分MIN_VALUE和NaN属性,因此它们都是数字类型。
要点:
- NaN 不是关键字,而是属性
- 在这种情况下,运算符类型如何表现
6. Array.prototype.sort()
最后一个 WTF 的主题是sort()方法的行为,没有发送任何参数。
[32, 3, 6].sort() //[3, 32, 6]
好吧,这和我们预期的不太一样。为什么这些值的顺序是那样?我们再举几个例子,大胆尝试一下。
[32, 3, true, 6].sort() //[3, 32, 6, true]
[32, 3, true, 6, NaN].sort() //[3, 32, 6, NaN, true]
[32, 3, true, 6, NaN, undefined].sort() //[3, 32, 6, NaN, true, undefined]
明白了吗?没错,默认算法会将每个值转换为字符串,然后进行相应的排序。
为了达到预期的结果,sort() 方法需要一个 compare 函数作为参数。该函数接收两个参数并返回一个描述它们之间关系的数字。
符号 a < b 表示 comparefn(a, b) < 0;a = b 表示 comparefn(a, b) = 0(任意符号);a > b 表示 comparefn(a, b) > 0。
下面是一个使用用户数组的示例。排序算法基于每个用户的年龄属性。
let users = [
{
name: "Andrei",
age: 24
},
{
name: "Denisa",
age: 23
}];
users.sort((first, second) => first.age - second.age);
//[ { name: 'Denisa', age: 23 }, { name: 'Andrei', age: 24 } ]
要点:
- Array.prototype.sort()默认行为
- 如何实现特定的排序机制
奖金:NaN
不是NaN
惊喜的是,还有更多!
NaN === NaN //false
这一个指的是严格相等比较及其实现。
- 如果Type (x) 与Type (y)不同,则返回 false。
- 如果类型(x)是数字,那么
- 如果 x 是
NaN
,则返回 false。- 如果 y 是
NaN
,则返回 false。...- 返回SameValueNonNumber (x, y)。
我们知道,NaN 类型是数字,所以第二个 if 条件满足。之后,如果任何一个操作数为 NaN,则返回false 。
要点:
- 严格相等比较实现的第一部分
- 该算法的最后一部分使用另一个名为SameValueNonNumber的算法
终于,我们完成了。你可能会觉得这些“WTF”很幼稚 (有些想法确实很幼稚),但它们背后可能隐藏着一些小 bug (但影响很大),会浪费你大量的时间和精力。
此外,当你的代码中出现可疑之处时,养成在官方文档中搜索并了解编译器如何“思考”的习惯可以真正提高你的技能。
注: 这是我第一次尝试撰写技术文章。如果您有任何反馈,请在评论区留言,并告诉我您感兴趣的主题。祝您编程愉快!
鏂囩珷鏉ユ簮锛�https://dev.to/andreib123/6-javascript-wtfs-and-what-to-learn-from-them-406d