wtfjs:一个有趣和棘手的 JavaScript 示例列表
Posted 凯小默
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了wtfjs:一个有趣和棘手的 JavaScript 示例列表相关的知识,希望对你有一定的参考价值。
说明
本文转载自:https://github.com/denysdovhan/wtfjs/blob/master/README-zh-cn.md
What the f*ck javascript?
一个有趣和棘手的 JavaScript 示例列表。
JavaScript 是一个不错的语言。它的语法简单,生态系统也很庞大,最重要的是,它拥有最伟大的社区力量。
我们知道,JavaScript 是一个非常有趣的语言,但同时也充满了各种奇怪的行为。这些奇怪的行为有时会搞砸我们的日常工作,有时则会让我们忍俊不禁。
WTFJS 的灵感源于 Brian Leroux。这个列表受到他 在 2012 年的 dotJS 上的演讲 “WTFJS” 的高度启发。
适用于 NodeJS 的指南手册
你可以通过 npm
安装该项目的指南手册。只需运行:
$ npm install -g wtfjs
然后在命令行中运行 wtfjs
,将会在命令行中打开手册并跳转至你选择的页数 $PAGER
。这不是必需的步骤,你也可以继续在这里阅读。
源码在此处: https://github.com/denysdovhan/wtfjs
翻译
如今,wtfjs 已被翻译成多种语言:
注意: 翻译由该语言的译者维护,因此可能缺失部分例子,或存在过时的例子等。
Table of Contents
- 💪🏻 初衷
- ✍🏻 符号
- 👀 例子
- 数组中的尾逗号
- 数组的相等性是深水猛兽
undefined
和Number
parseInt
是一个坏蛋true
和false
的数学运算- HTML 注释在 JavaScript 中有效
NaN
不是一个数值[]
和null
是对象- 神奇的数字增长
0.1 + 0.2
精度计算- 扩展数字的方法
- 三个数字的比较
- 有趣的数学
- 正则表达式的加法
- 字符串不是
String
的实例 - 用反引号调用函数
- 到底 call 了谁
constructor
属性- 将对象做为另一个对象的 key
- 访问原型
__proto__
`$Object`
- 使用默认值解构
- 点和扩展运算符
- 标签
- 嵌套标签
- 阴险的
try..catch
- 这是多重继承吗?
- yield 返回自身的生成器
- 类的类
- 不可转换类型的对象
- 棘手的箭头函数
- 箭头函数不能作为构造函数
arguments
和箭头函数- 棘手的返回
- 对象的链式赋值
- 使用数组访问对象属性
Number.toFixed()
显示不同的数字min
大于max
- 比较
null
和0
- 相同变量重复声明
- Array.prototype.sort() 的默认行为
- resolve() 不会返回 Promise 实例
是 undefined
arguments
绑定- 来自地狱的
alert
- 没有尽头的计时
setTimeout
对象- 点点运算符
- 再 new 一次
- 你应该用上分号
- 用空格分割(split)字符串
- 对字符串 stringify
- 对数字和
true
的非严格相等比较
- 其他资源
- 🤝 捐赠支持
- 🎓 许可证
💪🏻 初衷
只是因为好玩
— “只是为了好玩:一个意外革命的故事”, Linus Torvalds
这个列表的主要目的是收集一些疯狂的例子,并尽可能解释它们的原理。我很喜欢学习以前不了解的东西。
如果您是初学者,您可以根据此笔记深入了解 JavaScript。我希望它会激励你在阅读规范上投入更多时间和精力。
如果您是专业开发人员,您将从这些例子中看到人见人爱的 JavaScript 也充满了非预期的边界行为。
总之,古人云:三人行,必有我师焉。我相信这些例子总能让你学习到新的知识。
⚠️ Note: 如果这些例子帮助到你,请务必赞助收集了这些例子的作者.
✍🏻 符号
// ->
表示表达式的结果。例如:
1 + 1; // -> 2
// >
表示 console.log
等输出的结果。例如:
console.log("hello, world!"); // > hello, world!
//
则是用于解释的注释。例如:
// 将一个函数赋值给 foo 常量
const foo = function() ;
👀 例子
[]
等于 ![]
数组等于一个数组取反:
[] == ![]; // -> true
💡 说明:
抽象相等运算符会将其两端的表达式转换为数字值进行比较,尽管这个例子中,左右两端均被转换为 0
,但原因各不相同。数组总是真值(truthy),因此右值的数组取反后总是为 false
,然后在抽象相等比较中被被类型转换为 0
。而左值则是另一种情形,空数组没有被转换为布尔值的话,尽管在逻辑上是真值(truthy),但在抽象相等比较中,会被类型转换为数字 0
。
该表达式的运算步骤如下:
+[] == +![];
0 == +false;
0 == 0;
true;
了解更多:[]
是真值,但并非 true
.
true
不等于 ![]
,也不等于 []
数组不等于 true
,但数组取反也不等于 true
;
数组等于 false
数组取反也等于 false
:
true == []; // -> false
true == ![]; // -> false
false == []; // -> true
false == ![]; // -> true
💡 说明:
true == []; // -> false
true == ![]; // -> false
// 根据规范
true == []; // -> false
toNumber(true); // -> 1
toNumber([]); // -> 0
1 == 0; // -> false
true == ![]; // -> false
![]; // -> false
true == false; // -> false
false == []; // -> true
false == ![]; // -> true
// 根据规范
false == []; // -> true
toNumber(false); // -> 0
toNumber([]); // -> 0
0 == 0; // -> true
false == ![]; // -> true
![]; // -> false
false == false; // -> true
true 是 false
!!"false" == !!"true"; // -> true
!!"false" === !!"true"; // -> true
💡 说明:
考虑以下步骤:
// true 是真值(truthy),并且隐式转换为数字1,而字符串 'true' 会被转换为 NaN。
true == "true"; // -> false
false == "false"; // -> false
// 'false' 不是空字符串,所以它的值是 true
!!"false"; // -> true
!!"true"; // -> true
baNaNa
"b" + "a" + +"a" + "a";
这是用 JavaScript 写的老派笑话,原版如下:
"foo" + +"bar"; // -> 'fooNaN'
💡 说明:
这个表达式可以转化成 'foo' + (+'bar')
,但无法将'bar'
强制转化成数值。
NaN
不是 NaN
NaN === NaN; // -> false
💡 说明:
规范严格定义了这种行为背后的逻辑:
- 如果
Type(x)
不同于Type(y)
,返回 false。- 如果
Type(x)
数值, 然后
- 如果
x
是 NaN,返回 false。- 如果
y
是 NaN,返回 false。- ……
根据 IEEE 对 NaN 的定义:
有四种可能的相互排斥的关系:小于、等于、大于和无序。当比较操作中至少一个操作数是 NaN 时,便是无序的关系。换句话说,NaN 对任何事物包括其本身比较都应当是无序关系。
— StackOverflow 上的 “为什么对于 IEEE754 NaN 值的所有比较返回 false?”
奇怪的 Object.is()
和 ===
Object.is()
用于判断两个值是否相同。和 ===
操作符像作用类似,但它也有一些奇怪的行为:
Object.is(NaN, NaN); // -> true
NaN === NaN; // -> false
Object.is(-0, 0); // -> false
-0 === 0; // -> true
Object.is(NaN, 0 / 0); // -> true
NaN === 0 / 0; // -> false
💡 说明:
在 JavaScript “语言”中,NaN
和 NaN
的值是相同的,但却不是严格相等。NaN === NaN
返回 false 是因为历史包袱,记住这个特例就行了。
基于同样的原因,-0
和 0
是严格相等的,但它们的值却不同。
关于 NaN === NaN
的更多细节,请参阅上一个例子。
- 这是 TC39 中关于 Object.is 的规范
- MDN 上的[相等比较与相同值比较]](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness)
它是 fail
你可能不会相信,但……
(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]];
// -> 'fail'
💡 说明:
将大量的符号分解成片段,我们注意到,以下表达式经常出现:
![] + []; // -> 'false'
![]; // -> false
所以我们尝试将 []
和 false
加起来。但是因为一些内部函数调用(binary + Operator
- >ToPrimitive
- >[[DefaultValue]
]),我们最终将右边的操作数转换为一个字符串:
![] + [].toString(); // 'false'
将字符串作为数组,我们可以通过[0]
来访问它的第一个字符:
"false"[0]; // -> 'f'
剩下的部分以此类推,不过此处的 i
字符是比较讨巧的。fail
中的 i
来自于生成的字符串 falseundefined
,通过指定序号 ['10']
取得的。
更多的例子:
+![] // -> 0
+!![] // -> 1
!![] // -> true
![] // -> false
[][[]] // -> undefined
+!![] / +![] // -> Infinity
[] + // -> "[object Object]"
+ // -> NaN
- 烧脑预警:疯狂的 JavaScript
- 写个句子干嘛要用字母 — 用 JavaScript 生成任意短语
[]
是真值,但不等于 true
数组是一个真值,但却不等于 true
。
!![] // -> true
[] == true // -> false
💡 说明:
以下是 ECMA-262 规范中相应部分的链接:
null
是假值,但又不等于 false
尽管 null
是假值,但它不等于 false
。
!!null; // -> false
null == false; // -> false
但是,别的被当作假值的却等于 false
,如 0
或 ''
。
0 == false; // -> true
"" == false; // -> true
💡 说明:
跟前面的例子相同。这是一个相应的链接:
document.all
是一个 object,但又同时是 undefined
⚠️ 这是浏览器 API 的一部分,对于 Node.js 环境无效 ⚠️
尽管 document.all 是一个类数组对象(array-like object),并且通过它可以访问页面中的 DOM 节点,但在通过 typeof
的检测结果是 undefined
。
document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'
同时,document.all
不等于 undefined
。
document.all === undefined; // -> false
typeof document.all; // -> 'undefined'
但是同时,document.all
不等于 undefined
:
document.all === undefined; // -> false
document.all == null; // -> true
不过:
document.all == null; // -> true
💡 说明:
document.all
作为访问页面 DOM 节点的一种方式,在早期版本的 IE 浏览器中较为流行。尽管这一 API 从未成为标准,但被广泛使用在早期的 JS 代码中。当标准演变出新的 API(例如document.getElementById
)时,这个 API 调用就被废弃了。因为这个 API 的使用范围较为广泛,标准委员会决定保留这个 API,但有意地引入一个违反 JavaScript 标准的规范。
这个有意的对违反标准的规范明确地允许该 API 与undefined
使用严格相等比较得出false
而使用抽象相等比较 得出true
。— “废弃功能 - document.all” at WhatWG - html spec
— YDKJS(你不懂 JS) - 类型与语法 中的 “第 4 章 - ToBoolean - 假值
最小值大于零
Number.MIN_VALUE
是最小的数字,大于零:
Number.MIN_VALUE > 0; // -> true
💡 说明:
Number.MIN_VALUE
是5e-324
,即可以在浮点精度内表示的最小正数,也是在该精度内无限接近零的数字。它定义了浮点数的最高精度。
现在,整体最小的值是
Number.NEGATIVE_INFINITY
,尽管这在严格意义上并不是真正的数字。— StackOverflow 上的“为什么在 JavaScript 中
0
小于Number.MIN_VALUE
?”
函数不是函数
⚠️ V8 v5.5 或更低版本中出现的 Bug(Node.js <= 7) ⚠️
大家都知道 undefined 不是 function 对吧?但是你知道这个吗?
// 声明一个继承null的类
class Foo extends null
// -> [Function: Foo]
new Foo() instanceof null;
// > TypeError: function is not a function
// > at … … …
💡 说明:
这不是规范的一部分。这只是一个缺陷,且已经修复了。所以将来不会有这个问题。
Super constructor null of Foo is not a constructor (Foo 的超类的构造函数 null 不是构造函数)
这是前述缺陷的后续行为,在现代环境中可以复现(在 Chrome 71 和 Node.js v11.8.0 测试成功)。
class Foo extends null
new Foo() instanceof null;
// > TypeError: Super constructor null of Foo is not a constructor
💡 说明:
这并不是缺陷,因为:
Object.getPrototypeOf(Foo.prototype); // -> null
若当前类没有构造函数,则在构造该类时会顺次调用其原型链上的构造函数,而本例中其父类没有构造函数。补充一下,null
也是一个 object
:
typeof null === "object";
因此,你可以继承 null
(尽管在面向对象编程的世界里这是不允许的),但是却不能调用 null
的构造函数。若你把代码改成这样:
class Foo extends null
constructor()
console.log("something");
将会报错:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
// 引用错误:在访问`this`或返回之前,你需要在子类中先调用super构造函数
但是当你加上 super
时:
class Foo extends null
constructor()
console.log(111);
super();
JS 抛出错误:
TypeError: Super constructor null of Foo is not a constructor
// 类型错误:Foo的超类的构造函数null不是构造函数
数组相加
如果你尝试将两个数组相加:
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
💡 说明:
数组之间会发生串联。步骤如下:
[1, 2, 3] +
[4, 5, 6][
// 调用 toString()
(1, 2, 3)
].toString() +
[4, 5, 6].toString();
// 串联
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6");
数组中的尾逗号
假设你想要创建了一个包含 4 个空元素的数组。如下所示,最终只能得到一个包含三个元素的数组,原因在于尾逗号:
let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'
💡 说明:
尾逗号 (trailing commas,有时也称为“最后逗号”(final commas)) 在向 JavaScript 代码中添加新元素、参数或属性时非常有用。如果您想添加一个新属性,若前一行已经有尾逗号,你无需修改前一行,只要添加一个新行并加上尾逗号即可。这使得版本控制历史较为干净,编辑代码也很简单。
— MDN 上的 尾逗号
数组的相等性是深水猛兽
数组之间进行相等比较是 JS 中的深水猛兽,看看这些例子:
[] == '' // -> true
[] == 0 // -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == '' // -> false
[''] == 0 // -> true
[null] == '' // true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0 // true
[[]] == 0 // true
[[]] == '' // true
[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0 // true
[[[[[[ null ]]]]]] == 0 // true
[[[[[[ null ]]]]]] == '' // true
[[[[[[ undefined ]]]]]] == 0 // true
[[[[[[ undefined ]]]]]] == '' // true
💡 说明:
仔细阅读上面的例子!规范中的 7.2.13 抽象相等比较 一节描述了这些行为。
undefined
和 Number
无参数调用 Number
构造函数会返回 0
。我们知道,当函数没有接受到指定位置的实际参数时,该处的形式参数的值会是 undefined
。因此,你可能觉得当我们传入 undefined
时应当同样返回 0
。然而实际上传入 undefined
返回的是 NaN
。
Number(); // -> 0
Number(undefined); // -> NaN
💡 说明:
<以上是关于wtfjs:一个有趣和棘手的 JavaScript 示例列表的主要内容,如果未能解决你的问题,请参考以下文章
当鼠标悬停在 Javascript 中时,棘手的按钮会移开? [关闭]