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

💪🏻 初衷

只是因为好玩

“只是为了好玩:一个意外革命的故事”, 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

💡 说明:

规范严格定义了这种行为背后的逻辑:

  1. 如果 Type(x) 不同于 Type(y),返回 false
  2. 如果 Type(x) 数值, 然后
    1. 如果 xNaN,返回 false
    2. 如果 yNaN,返回 false
    3. ……

7.2.14 严格模式相等比较

根据 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 “语言”中,NaNNaN 的值是相同的,但却不是严格相等。NaN === NaN 返回 false 是因为历史包袱,记住这个特例就行了。

基于同样的原因,-00 是严格相等的,但它们的值却不同。

关于 NaN === NaN 的更多细节,请参阅上一个例子。

它是 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

[] 是真值,但不等于 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_VALUE5e-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 抽象相等比较 一节描述了这些行为。

undefinedNumber

无参数调用 Number 构造函数会返回 0。我们知道,当函数没有接受到指定位置的实际参数时,该处的形式参数的值会是 undefined。因此,你可能觉得当我们传入 undefined 时应当同样返回 0。然而实际上传入 undefined 返回的是 NaN

Number(); // -> 0
Number(undefined); // -> NaN

💡 说明:

<

以上是关于wtfjs:一个有趣和棘手的 JavaScript 示例列表的主要内容,如果未能解决你的问题,请参考以下文章

当鼠标悬停在 Javascript 中时,棘手的按钮会移开? [关闭]

javascript javascript承诺很棘手

7 个简单但棘手的 JavaScript 面试问题

javascript JS棘手的吊装1

Selenium:在javascript代码中关闭棘手的javascript弹出窗口

javascript JS关闭棘手2