翻译自 What the f*ck JavaScript
文中提到了挺有趣的一些JavaScript现象,对于深入了解和学习这么语言很有用。
粗的斜体字是翻译的人(也就是我XD)自己加的一些注解之类的
如果有啥翻译的不对或者不好的地方一定要告诉我呀,谢谢大家>3<
What the f*ck JavaScript?
JavaScript你怎么这样啊
A list of funny and tricky JavaScript examples
一篇有很多JavaScript有趣例子的文章
JavaScript is a great language. It has a simple syntax, large ecosystem and, what is most important, a great community.
JavaScript是一门很棒的语言。它有非常简单的语法,很有规模的生态,最重要的是还有一个非常棒的社区。
At the same time, we all know that JavaScript is quite a funny language with tricky parts. Some of them can quickly turn our everyday job into hell, some of them can make us laugh out loud.
同时,我们也知道,JavaScript是一门很有趣的语言,它有很多非常微妙的部分。有一些可以刚让我们每天的工作秒变地狱,还有一些可以让我们开怀大笑。
The original idea for WTFJS belongs to Brian Leroux. This list is highly inspired by his talk “WTFJS” at dotJS 2012:
这篇文章最最原始的想法是源自Brain Leroux。这篇文章的灵感很大程度上都源自于他2012年在dotJS的演讲“WTFJS“:
Node Packaged Manuscript
node封装的文件包
You can install this handbook using npm. Just run:
你可以通过npm安装这本指南。运行:
$ npm install -g wtfjs
You should be able to run wtfjs at the command line now. This will open the manual in your selected $PAGER. Otherwise, you may continue reading on here.
你现在应该可以在编译器上运行wtfjs了。他会在你选定的$PAGER上打开。或者你也可以继续在这里阅读它。
The source is available here: https://github.com/denysdovhan/wtfjs
源文件在这里可以找到https://github.com/denysdovhan/wtfjs
Table of contents
目录
- Motivation
- Notation
- Examples
- [] is equal ![]
- true is false
- baNaNa
- NaN is not a NaN
- It’s a fail
- [] is truthy, but not true
- null is falsy, but not false
- Minimal value is greater than zero
- function is not function
- Adding arrays
- Trailing commas in array
- Array equality is a monster
- undefined and Number
- parseInt is a bad guy
- Math with true and false
- HTML comments are valid in JavaScript
- NaN is not a number
- [] and null are objects
- Magically increasing numbers
- Precision of 0.1 + 0.2
- Patching numbers
- Comparison of three numbers
- Funny math
- Addition of RegExps
- Strings aren’t instances of String
- Calling functions with backticks
- Call call call
- A constructor property
- Object as a key of object’s property
- Accessing prototypes with proto
${{Object}}
- Destructuring with default values
- Dots and spreading
- Labels
- Nested labels
- Insidious try..catch
- Is this multiple inheritance?
- A generator which yields itself
- A class of class
- Non-coercible objects
- Tricky arrow functions
- Tricky return
- Accessing object properties with arrays
- Other resources
- License
Motivation
动机
Just for fun
— “Just for Fun: The Story of an Accidental Revolutionary”, Linus Torvalds
只为欢乐
—《只为欢乐:一次偶然的革命》, Linus Torvalds
中文译名:《只为欢乐》(Just for Fun: The Story of an Accidental Revolutionary),是Linux核心的创建者林纳斯·托瓦兹(Linus Torvalds)的幽默自传。
The primary goal of this list is to collect some crazy examples and explain how they work, if possible. Just because it’s fun to learn something that we didn’t know before.
这篇文章的主要目的是收集一些疯狂的例子并且尽可能解释清楚他们是怎么运作的。只是因为他们学起来很有趣,并且我们之前从未了解过。
If you are a beginner, you can use these notes to get a deeper dive into JavaScript. I hope these notes will motivate you to spend more time reading the specification.
如果你是初学者,你可以借助这篇文章来更深入了解JavaScript。我希望这篇文章能够成为你想更深入理解JavaScript规范的动力。
If you are a professional developer, you can consider these examples as a great reference for all of the quirks and unexpected edges of our beloved JavaScript.
如果你是一个专业开发人员,你可以将这些例子作为我们亲爱的JavaScript的一些出人意料行为的一些参考。
In any case, just read this. You’re probably going to find something new.
无论如何,来看看吧。你也许会发现一些新鲜的东西。
Notation
符号
// ->
is used to show the result of an expression. For example:
// ->
用来表示表达式的结果。例如:
1 + 1 // -> 2
// >
means the result of console.log
or another output. For example:
// >
表示console.log
的结果或其他输出。例如:
console.log(‘hello, world!’) // > hello, world!
//
is just a comment used for explanations. Example:
//
只是用来解释的注释。例如:
// Assigning a function to foo constant
const foo = function () {}
Examples
例子
1. []
is equal ![]
1. []
与 ![]
相等
Array is equal not array:
数组与非数组相等:
[] == ![] // -> true
Explanation:
说明:
2. true is false
2. true是false
!!'false' == !!'true' // -> true
!!'false' === !!'true' // -> true
Explanation:
说明:
Consider this step-by-step:
一步一步地考虑:
true == 'true' // -> true
false == 'false' // -> false
// 'false' is not empty string, so it's truthy value
//因为字符串'false'并不是一个空字符串,所以它的值为真
!!'false' // -> true
!!'true' // -> true
3. baNaNa
3.小黄人(ba~ba~banana)
'b' + 'a' + + 'a' + 'a'
This is an old-school joke in JavaScript, but remastered. Here’s the original one:
这是一个JavaScript的老笑话,但是从不过时。它的原型是:
'foo' + + 'bar' // -> 'fooNaN'
Explanation:
说明:
The expression is evaluated as 'foo' + (+'bar')
, which converts ‘bar’ to not a number.
表达式通过'foo' + (+'bar')
求值,这里把'bar'
转换为了一个非数值(NaN)。
4. NaN is not a NaN
4. NaN不等于它自己
NaN === NaN // -> false
Explanation:
说明:
The specification strictly defines the logic behind this behavior:
规范严格定义了行为背后的逻辑:
If Type(x) is different from Type(y), return false.
If Type(x) is Number, then
If x is NaN, return false.
If y is NaN, return false.
… … …
— 7.2.14 Strict Equality Comparison
Following the definition of NaN from the IEEE:
按照IEEE对NaN的定义:
Four mutually exclusive relations are possible: less than, equal, greater than, and unordered. The last case arises when at least one operand is NaN. Every NaN shall compare unordered with everything, including itself.
有四个互斥关系是可能的:小于、等于、大于以及无穷。最后一个情况出现在至少一个操作数是NaN的情况下。每个NaN都可以比较无穷尽的一切,包括它自己。(NaN不等于任何一个数值,包括它自己)
— “What is the rationale for all comparisons returning false for IEEE754 NaN values?” at StackOverflow
5. It’s a fail
5. 这是一个fail
You would not believe, but …
你可能不会相信,但是…
(![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]
// -> 'fail'
Explanation:
说明:
By breaking that mass of symbols into pieces, we notice that the following pattern occurs often:
把这一堆符号分解成片段来看,我们注意到了以下这几种模式:
(![]+[]) // -> 'false'
![] // -> false
So we try adding[]
to false
. But due to a number of internal function calls (binary + Operator
-> ToPrimitive
-> [[DefaultValue]]
) we end up converting the right operand to a string:
所以我们尝试把[]
和 false
加在一起。但是由于一些函数内部的调用(二级制+运算符
-> 转换为原始值
-> [[默认值]]
) 我们最后把右边的操作数转换为了一个字符串:
(![]+[].toString()) // 'false'
Thinking of a string as an array we can access its first character via [0]:
把这个字符串想成一个数组,我们可以访问它的第一个字符,用[0]
'false'[0] // -> 'f'
The rest is obvious, but the i
is tricky. The i
in fail
is grabbed by generating the string 'falseundefined'
and grabbing the element on index ['10']
剩下的就很明显了,但是得到字母i
需要一点技巧。fail
中的i
是通过生成字符串'falseundefined'
以及取index['10']
得到的
6. []
is truthy, but not true
6. []
为真,但不等于true
An array is a truthy value, however, it’s not equal to true
.
数组为真值,但它并不等于true。
!![] // -> true
[] == true // -> false
Explanation:
说明:
Here are links to the corresponding sections in the ECMA-262 specification:
以下是ECMA-262规范中相应部分的链接:
7. null
is falsy, but not false
7. null
为假, 但不等于 false
Despite the fact that null
is a falsy
value, it’s not equal to false
.
尽管null
值为假,但是并不等于false
.
!!null // -> false
null == false // -> false
At the same time, other falsy values, like 0
or ''
are equal to false
.
与此同时,还有一些其他为假的值,如 0
和 ''
,他们可以与false
划等号。
0 == false // -> true
'' == false // -> true
Explanation:
说明:
The explanation is the same as for previous example. Here’s the corresponding link:
说明与上一个例子相等,这是有关的链接:
8. Minimal value is greater than zero
8. 最小值大于0
Number.MIN_VALUE
is the smallest number, which is greater than zero:
Number.MIN_VALUE
是最小的值,它比0要大:
Number.MIN_VALUE > 0 // -> true
Explanation:
说明:
Number.MIN_VALUE
is5e-324
, i.e. the smallest positive number that can be represented within float precision, i.e. that’s as close as you can get to zero. It defines the best resolution that floats can give you.
Number.MIN_VALUE
是5e-324
,也就是说,最小的正数可以被浮点精度表示,也就是说这是最接近0的数。他定义了能达到的最大的浮点精度。Now the overall smallest value is
Number.NEGATIVE_INFINITY
although it’s not really numeric in a strict sense.
现在,整体最小的值是Number.NEGATIVE_INFINITY,尽管它在严格意义上并不是真正的数字。— “Why is 0 less than Number.MIN_VALUE in JavaScript?” at StackOverflow
9. function is not function
9. function不是function
⚠️ A bug present in V8 v5.5 or lower (Node.js <=7) ⚠️
⚠️ 小于等于v5.5版本的v8的一个bug(Node.js <=7) ⚠️
All of you know about the annoying undefined is not a function, but what about this?
你们都知道烦人的undefined并不是一个函数,但是这个呢?
// Declare a class which extends null
//声明一个扩展自null的类
class Foo extends null {}
// -> [Function: Foo]
new Foo instanceof null
// > TypeError: function is not a function
// > at … … …
Explanation:
说明:
This is not a part of the specification. It’s just a bug that has now been fixed, so there shouldn’t be a problem with it in the future.
这不是规范的一部分。 这只是一个已经修复的错误,所以今后不应该有问题了。
10. Adding arrays
10. 相加的数组
What if you try to add two arrays?
如果你尝试将两个数组相加会发生什么?
[1, 2, 3] + [4, 5, 6] // -> '1,2,34,5,6'
Explanation:
说明:
The concatenation happens. Step-by-step, it looks like this:
这样连接的情况真的会发生。分解来看,它们是这样的:
[1, 2, 3] + [4, 5, 6]
// call toString()
[1, 2, 3].toString() + [4, 5, 6].toString()
// concatenation
'1,2,3' + '4,5,6'
// ->
'1,2,34,5,6'
11. Trailing commas in array
11. 数组末尾的逗号
You’ve created an array with 4 empty elements. Despite all, you’ll get an arrary with three elements, because of trailing comas:
你创建了一个有四个空元素的数组。尽管如此,你会得到一个拥有三个元素的数组,因为末尾的逗号(会被忽略):
let a = [,,,]
a.length // -> 3
a.toString() // -> ',,'
Explanation:
说明:
Trailing commas (sometimes called “final commas”) can be useful when adding new elements, parameters, or properties to JavaScript code. If you want to add a new property, you can simply add a new line without modifying the previously last line if that line already uses a trailing comma. This makes version-control diffs cleaner and editing code might be less troublesome.
末尾的逗号(有时候被称作最后的逗号)在JavaScript代码中添加新的元素,参数或属性的时候非常有用。如果你想添加一个新属性,您可以简单地添加一个新行,而不修改之前的最后一行,如果该行已经使用了一个逗号。这使得版本控制差异更清晰,编辑代码可能不那么麻烦。— Trailing commas at MDN
12. Array equality is a monster
12. 数组相等犹如小怪兽
Array equality is a monster in JS, think below:
数组相等在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
Explanation:
说明:
You should be very careful for above! This is a complex examples, but it’s described in 7.2.13 Abstract Equality Comparison section of the specification.
你应该非常小心上边的这些表达式! 这是一个复杂的例子,但在规范的7.2.13抽象平等比较部分中有详细的描述。
13. undefined
and number
13. undefined
和 number
If we don’t pass any arguments into the Number
constructor, we’ll get 0
. The value undefined
is assigned to formal arguments when there are no actual arguments, so you might expect that Number
without arguments takes undefined
as a value of its parameter. However, when we pass undefined
, we will get NaN
.
如果我们不传给Number
构造器任何实参,我们会得到0
。当没有实参时,undefined
将会被分配给参数,所以你也许会认为没有实参的Number
会将undefined
作为参数传入。但是当我们传入undefined
时,得到的是NaN
Number() // -> 0
Number(undefined) // -> NaN
Explanation:
说明:
According to the specification:
根据规范,有:
- If no arguments were passed to this function’s invocation, let
n
be+0
.
如果没有参数传给函数调用,那么n为+0 - Else, let
n
be ?ToNumber(value)
.
另外,让n
是啥?ToNumber(value)
- In case of
undefined
,ToNumber(undefined)
should returnNaN
.
在undefined
的情况下,ToNumber(undefined)
应该返回NaN
Here’s the corresponding section:
这是相关的部分:
14. parseInt
is a bad guy
14. parseInt
是个坏蛋
parseInt
is famous by its quirks:
parseInt
以他的怪异闻名:
parseInt('f*ck'); // -> NaN
parseInt('f*ck', 16); // -> 15
Explanation: This happens because parseInt will continue parsing character-by-character until it hits a character it doesn’t know. The f in ‘f*ck’ is the hexadecimal digit 15.
说明:这是因为parseInt逐行解析字符,直到它触及不知道的字符。 ‘f * ck’中的f是十六进制数字15。
Parsing Infinity
to integer is something…
解析Infinity
(无穷大)到整数会这样…
//
parseInt('Infinity', 10) // -> NaN
// ...
parseInt('Infinity', 18) // -> NaN...
parseInt('Infinity', 19) // -> 18
// ...
parseInt('Infinity', 23) // -> 18...
parseInt('Infinity', 24) // -> 151176378
// ...
parseInt('Infinity', 29) // -> 385849803
parseInt('Infinity', 30) // -> 13693557269
// ...
parseInt('Infinity', 34) // -> 28872273981
parseInt('Infinity', 35) // -> 1201203301724
parseInt('Infinity', 36) // -> 1461559270678...
parseInt('Infinity', 37) // -> NaN
Be careful with parsing null
too:
null
也要小心:
parseInt(null, 24) // -> 23
Explanation:
说明:
It’s converting
null
to the string"null"
and trying to convert it. For radixes 0 through 23, there are no numerals it can convert, so it returns NaN. At 24, “n”, the 14th letter, is added to the numeral system. At 31, “u”, the 21st letter, is added and the entire string can be decoded. At 37 on there is no longer any valid numeral set that can be generated and NaN is returned.
这个行为将null
转换为了字符串"null"
,并尝试转换它。对于基数0到23,没有可以转换的数字,因此返回NaN。 在24处,将第14个字母的“n”加到数字系统中。在31处,添加第二十一个字母“u”,整个字符串就被解码了。 在37处,不再有可以生成的有效数字集合,并且返回NaN。— “parseInt(null, 24) === 23… wait, what?” at StackOverflow
Don’t forget about octals:
不要忘记了八进制数:
parseInt(‘06’); // 6
parseInt(‘08’); // 8 if support ECMAScript 5
parseInt(‘08’); // 0 if not support ECMAScript 5
Explanation: If the input string begins with “0”, radix is eight (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 specifies that 10 (decimal) is used, but not all browsers support this yet. For this reason always specify a radix when using parseInt
.
说明:如果输入字符串以”0”开头,那么基数为八进制或十进制。选择哪个基数是依赖于实现的。ECMAScript 5指定使用十进制,但并不是所有浏览器都支持。 因此,在使用parseInt
时总是指定一个基数。
parseInt
always convert input to string:
parseInt
会把input转换为字符串:
parseInt({ toString: () => 2, valueOf: () => 1 }) // -> 2
Number({ toString: () => 2, valueOf: () => 1 }) // -> 1
15. Math with true
and false
15. Math与true
和 false
Let’s do some math:
我们来整点数学问题:
true + true // -> 2
(true + true) * (true + true) - true // -> 3
Hmmm…
这是咋回事呢…
Explanation:
说明:
We can coerce values to numbers with the Number
constructor. It’s quite obvious that true
will be coerced to 1
:
我们可以使用构造函数Number将值强制转换为数字。很明显的,true
会被强制转换为1
Number(true) // -> 1
The unary plus operator attempts to convert its value into a number. It can convert string representations of integers and floats, as well as the non-string values true
, false
, and null
. If it cannot parse a particular value, it will evaluate to NaN
. That means we can coerce true
to 1
easier:
一元加运算符会尝试将值转换成数字。它可以将字符串转换整数和浮点数的形式,将非字符串转换为true
,false
和null
。 如果不能解析一个特定的值,它会被转换为NaN
。 这意味着我们可以非常容易地将true
转换为1
:
+true // -> 1
When you’re performing addition or multiplication, the ToNumber
method is invoked. According to the specification, this method returns:
每当你使用加法或乘法时,ToNumber
方法就会被调用,由于这个特性,方法将返回:
If
argument
is true, return 1. Ifargument
is false, return +0.
如果参数为真,返回1。如果参数为假,返回+0。
That’s why we can add boolean values as regular numbers and get correct results.
这就是为什么我们可以将布尔值与常规数字相加还可以获得正确的结果。
Corresponding sections:
相关部分:
16. HTML comments are valid in JavaScript
16. HTML的注释在JavaScript中也可用
You will be impressed, but <!--
(which is known as HTML comment) is a valid comment in JavaScript.
也许你会感到很惊讶,但是<!--
(一般我们都作为html的注释用)作为注释,在JavaScript中也是可用的。
// valid comment
<!-- valid comment too
Explanation:
说明:
Impressed? HTML-like comments were intended to allow browsers that didn’t understand the <script>
tag to degrade gracefully. These browsers, e.g. Netscape 1.x are no longer popular. So there is really no point in putting HTML comments in your script tags anymore.
你也感到很惊讶吧?类HTML的注释可以让读不懂<script>
标签的浏览器优雅降级。能够做到这样的浏览器,如Netscape 1.x他们不再受到大众欢迎。所以也没有必要将HTML的注释用到script标签中。
Since Node.js is based on the V8 engine, HTML-like comments are supported by the Node.js runtime too. Moreover, they’re a part of the specification:
由于Node.js基于V8引擎,Node.js运行时也支持类HTML的注释。 并且它们是规范的一部分:
这个特性最近正好在《DOM编程的艺术》中读到了,但需要注意的一点是:如果在HTML文档中需要用-->
来结束注释,但是JavaScript中不能这样做,因为它会把-->
视为注释内容的一部分。
另外,在HTML中这样的注释允许跨越多个行,但JavaScript脚本中要求这种注释每行都必须在前头加上<!--
作为标志。
17. NaN
is not(x) a number
17. NaN
不算是数字(才怪)
Type of NaN
is a 'number'
:
NaN
的类型是'number'
typeof NaN // -> 'number'
Explanation:
说明:
Explanations of how typeof
and instanceof
operators work:
了解typeof
和instanceof
是怎么工作的:
18. []
and null
are objects
18. []
和 null
也是对象
typeof [] // -> 'object'
typeof null // -> 'object'
// however
null instanceof Object // false
Explanation:
说明:
The behavior of typeof
operator is defined in this section of the specification:
typeof
的行为在规范中有定义:
According to the specification, the typeof
operator returns a string according to Table 35: typeof Operator Results. For null
, ordinary, standard exotic and non-standard exotic objects, which do not implement [[Call]]
, it returns the string "object"
.
根据规范与 Table 35: typeof Operator Results, typeof
返回一个字符串。对于null
这个不执行[[Call]]
的既普通又标准既异常又非标准的对象,它返回的是字符串"object"
。
However, you can check the type of an object by using the toString method.
总之,你可以用toString
方法来确认一个对象的类型。
Object.prototype.toString.call([])
// -> '[object Array]'
Object.prototype.toString.call(new Date)
// -> '[object Date]'
Object.prototype.toString.call(null)
// -> '[object Null]'
19. Magically increasing numbers
19. 会魔法般增长的数字
999999999999999 // -> 999999999999999
9999999999999999 // -> 10000000000000000
10000000000000000 // -> 10000000000000000
10000000000000000 + 1 // -> 10000000000000000
10000000000000000 + 1.1 // -> 10000000000000002
Explanation:
说明:
This is caused by IEEE 754-2008 standard for Binary Floating-Point Arithmetic. At this scale, it rounds to the nearest even number. Read more:
这是由IEEE 754-2008标准的二进制浮点运算标准引起的。在这个大小时,它会舍入到最接近的偶数。了解更多:
- 6.1.6 The Number Type
- IEEE 754 on Wikipedia
20. Precision of 0.1 + 0.2
20. 0.1 + 0.2 的精度
A well-known joke. An addition of 0.1 and 0.2 is deadly precise:
这是一个非著名笑话。0.1与0.2相加的结果准确度惊人:
0.1 + 0.2 // -> 0.30000000000000004
(0.1 + 0.2) === 0.3 // -> false
Explanation:
说明:
The answer for the ”Is floating point math broken?” question on StackOverflow:
在StackOverflow上关于”浮点数计算是不是瓦特了?“
The constants
0.2
and0.3
in your program will also be approximations to their true values. It happens that the closest double to0.2
is larger than the rational number0.2
but that the closest double to0.3
is smaller than the rational number0.3
. The sum of0.1
and0.2
winds up being larger than the rational number0.3
and hence disagreeing with the constant in your code.
程序中的常量0.2
和0.3
实际上是无限近似于他们的真值的。会有这样一种现象:最接近0.2
的 双精度浮点数比有理数0.2
要大,但是最接近0.3
的 双精度浮点数比有理数0.3
要小。结果导致0.1
和0.2
的和要比有理数0.3
要大一点,因此它的结果并不等于你代码中的常数。
This problem is so known that there is even a website called 0.30000000000000004.com. It occurs in every language that uses floating-point math, not just JavaScript.
这个问题太有名了,现在甚至有一个网站叫做0.30000000000000004.com。不仅仅是JavaScript,所有用到了浮点数的程序语言中都会有这个问题。
21. Patching numbers
21. Number补完计划
You can add your own methods to wrapper objects like Number or String.
你可以将自己的方法添加到像Number
或String
包装对象中
Number.prototype.isOne = function () {
return Number(this) === 1
}
1.0.isOne() // -> true
1..isOne() // -> true
2.0.isOne() // -> false
(7).isOne() // -> false
Explanation:
说明:
Obviously, you can extend Number
object like any other object in JavaScript. However, it’s not recommended if the behavior of defined method is not a part of the specification. Here is the list of Number
’s properties:
显然,在JavaScript中你可以像扩展其他对象一样扩展Number
对象。尽管如此,如果添加的方法行为没有存在于规范中的话,这种行为并不被推荐。以下是Number
的属性列表:
22. Comparison of three numbers
22. 比较三个数字
1 < 2 < 3 // -> true
3 > 2 > 1 // -> false
Explanation:
说明:
Why does this work that way? Well, the problem is in the first part of an expression. Here’s how it works:
为什么会发生这种情况呢?这个问题发生在表达式的开头。它是这样运作的:
1 < 2 < 3 // 1 < 2 -> true
true < 3 // true -> 1
1 < 3 // -> true
3 > 2 > 1 // 3 > 2 -> true
true > 1 // true -> 1
1 > 1 // -> false
We can fix this with Greater than or equal operator (>=):
通过设置为大于等于(>=)我们可以解决这个问题:
3 > 2 >= 1 // true
Read more about Relational operators in the specification:
详细了解关系运算符的规范:
- 12.10 Relational Operators
23. Funny math
23. 搞笑的math
Often the results of arithmetic operations in JavaScript might be quite unexpected. Consider these examples:
通常JavaScript中的算术运算结果可能是非常意想不到的。 思考以下例子:
3 - 1 // -> 2
3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'
'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'
'222' - -'111' // -> 333
[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN
Explanation:
说明:
What’s happening in the first four examples? Here’s a small table to understand addition in JavaScript:
前四个例子中发生了什么?来看看这个列表吧,能帮助我们理解JavaScript中的加法运算符:
Number + Number -> addition(相加)
Boolean + Number -> addition
Boolean + Boolean -> addition
Number + String -> concatenation(连接)
String + Boolean -> concatenation
String + String -> concatenation
What about other examples? A ToPrimitive
and ToString
methods are being implicitly called for []
and {}
before addition. Read more about evaluation process in the specification:
另外一些例子怎么解释?当[]
和 {}
在相加前被调用时,隐式调用了ToPrimitive
和 ToString
方法。了解更多规范中的分析过程:
24. Addition of RegExps
24. 正则表达式的补充
Did you know you can add numbers like this?
你知道可以这样把数字加起来吗?
// Patch a toString method
//添加一个toString方法
RegExp.prototype.toString = function() {
return this.source
}
/7/ - /5/ // -> 2
Explanation:
说明:
25. Strings aren’t instances of String
25. 字符串不是 String
的实例
'str' // -> 'str'
typeof 'str' // -> 'string'
'str' instanceof String // -> false
Explanation:
说明:
The String
construnctor returns a string:
String
构造函数返回一个字符串:
typeof String('str') // -> 'string'
String('str') // -> 'str'
String('str') == 'str' // -> true
Let’s try with a new:
我们换一个试试:
new String('str') == 'str' // -> true
typeof new String('str') // -> 'object'
Object? What’s that?
对象?啥玩意?
new String('str') // -> [String: 'str']
More information about the String
constructor in the specification:
获取规范中更多有关String
构造函数的信息:
26. Calling functions with backticks
26. 用反引号调用函数
Let’s declare a function which logs all params into the console:
我们来声明一个将所有参数记录到控制台中的函数:
function f(...args) {
return args
}
No doubt, you know you can call this function like this:
毫无疑问地,你知道可以这样调用这个函数:
f(1, 2, 3) // -> [ 1, 2, 3 ]
But did you know you can call any function with backticks?
但你知道你可以用反引号调用任何函数吗?
f`true is ${true}, false is ${false}, array is ${[1,2,3]}`
// -> [ [ 'true is ', ', false is ', ', array is ', '' ],
// -> true,
// -> false,
// -> [ 1, 2, 3 ] ]
Explanation:
说明:
Well, this is not magic at all if you’re familiar with Tagged template literals. In the example above, f
function is a tag for template literal. Tags before template literal allow you to parse template literals with a function. The first argument of a tag function contains an array of string values. The remaining arguments are related to the expressions. Example:
其实,如果你熟悉Tagged模板文字,就会知道这根本就不是魔术。在上边的例子中,f
函数是一个模板文字的标签。模板文字之前的标签允许您使用函数解析模板文字。标签函数的第一个参数包含字符串值的数组。 其余的参数与表达式有关。 例:
function template(strings, ...keys) {
// do something with strings and keys…
}
This is the magic behind famous library called styled-components, which is popular in the React community.
这是magic behind ,它来自于React社区里非常流行的一个叫做styled-components的程序库。
Link to the specification:
规范的链接:
- 12.3.7 Tagged Templates
27. Call call call
27. 连环call
Found by @cramforce
console.log.call.call.call.call.call.apply(a => a, [1, 2])
Explanation:
说明:
Attention, it could break your mind! Try to reproduce this code in your head: we’re applying the call
method using the apply
method. Read more:
注意,可能会伤了你的心! 尝试在您的头脑中重现此代码:我们在apply方法中应用“call”方法。 了解更多:
- 19.2.3.3 Function.prototype.call(thisArg, …args)
- 19.2.3.1 Function.prototype.apply(thisArg, argArray)
28. A constructor
property
28. 一个构造函数属性
const c = 'constructor'
c[c][c]('console.log("WTF?")')() // > WTF?
Explanation:
说明:
Let’s consider this example step-by-step:
我们来一步一步地剖析这个例子:
// Declare a new constant which is a string
// 声明一个字符串常量
'constructor'
const c = 'constructor'
// c is a string
// c是一个字符串
c // -> 'constructor'
// Getting a constructor of string
// 获取一个字符串的构造函数
c[c] // -> [Function: String]
// Getting a constructor of constructor
// 获取一个构造函数的构造函数
c[c][c] // -> [Function: Function]
// Call the Function constructor and pass
// the body of new function as an argument
// 调用Function构造函数并传递新函数的主体作为参数
c[c][c]('console.log("WTF?")') // -> [Function: anonymous]
// And then call this anonymous function
// The result is console-logging a string 'WTF?'
// 然后调用这个匿名函数
// 结果是在控制台打印出一个字符串'WTF?'
c[c][c]('console.log("WTF?")')() // > WTF?
An Object.prototype.constructor
returns a reference to the Object
constructor function that created the instance object. In case with strings it is String
, in case with numbers it is Number
and so on.
一个Object.prototype.constructor
返回一个引用对象的构造函数创建的实例对象。在字符串的情况下,它是String
,在数字的情况下它是Number
等等。
29. Object as a key of object’s property
29. 对象可以作为对象属性的键值
{ [{}]: {} } // -> { '[object Object]': {} }
Explanation:
说明:
Why does this work so? Here we’re using a Computed property name. When you pass an object between those brackets, it coerces object to a string, so we get the property key '[object Object]'
and the value {}
.
为什么会变成这样婶儿呢?这里我们使用一个计算属性名。当您在这些方括号之间传递对象时,会将对象强制转换为字符串,因此我们可以获取属性键“[object Object]”和值“{}”。
We can make “brackets hell” like this:
我们可以让括号这样:
({[{}]:{[{}]:{}}})[{}][{}] // -> {}
// structure:
// {
// '[object Object]': {
// '[object Object]': {}
// }
// }
Read more about object literals here:
了解更多和对象文本有关的:
30. Accessing prototypes with __proto__
30. 用 __proto__
访问原型
As we know, primitives don’t have prototypes. However, if we try to get a value of __proto__
for primitives, we would get this:
正如我们所知,原始类型没有原型。但是,如果我们尝试从原始类型获取一个“proto”值,我们会得到这样的结果:
(1).__proto__.__proto__.__proto__ // -> null
Explanation:
说明:
This happens because when something doesn’t have a prototype, it will be wrapped into a wrapper object using the ToObject
method. So, step-by-step:
这是因为当某些东西没有原型时,将使用ToObject
方法将其包装到包装对象中。所以,分解来看:
(1).__proto__ // -> [Number: 0]
(1).__proto__.__proto__ // -> {}
(1).__proto__.__proto__.__proto__ // -> null
Here is more information about __proto__
:
获取有关 __proto__
的更多信息:
31. `${{Object}}
`(最后一个反引号在代码块内)
What is the result of the expression below?
下边表达式的结果是啥?
`${{Object}}`
The answer is:
答案是:
// -> '[object Object]'
Explanation:
说明:
We defined an object with a property Object using Shorthand property notation:
我们用对象字面量简写的方法创建一个对象:
{ Object: Object }
Then we’ve passed this object to the template literal, so the toString
method calls for that object. That’s why we get the string '[object Object]'
.
然后我们把这个对象传入模板字符串,这样就可以为这个对象调用toString
方法了。这就是为什么我们得到了字符串'[object Object]'
32. Destructuring with default values
32. 解构时赋默认值
Consider this example:
思考这个例子:
let x, { x: y = 1 } = { x }; y;
The example above is a great task for an interview. What the value of y
? The answer is:
上边这个例子可厉害了。所以y
的值是多少?结果是:
// -> 1
Explanation:
说明:
let x, { x: y = 1 } = { x }; y;
// ↑ ↑ ↑ ↑
// 1 3 2 4
With the example above:
解释一下上边的例子:
- We declare
x
with no value, so it’sundefined
.
我们声明了x
,并且没有设定初始值,所以是undefined
- Then we pack the value of
x
into the object propertyx
.
然后我们打包x
的值进了对象的属性x
- Then we extract the value of x using destructuring and want to assign it to
y
. If the value is not defined, then we’re going to use1
as the default value.
然后我们想把解构后的x
值赋给y
。如果这个值没有定义,那么我们就用1
来当做默认值 Return the value of y.
返回y的值Object initializer at MDN
33. Dots and spreading
33. 点点和扩展
Interesting examples could be composed with spreading of arrays. Consider this:
数组的扩展有一个很有意思的例子
[...[...'...']].length // -> 3
Explanation:
说明:
Why 3
? When we use the spread operator TODO(link to spec), the @@iterator
method is called, and the returned iterator is used to obtain the values to be iterated. The default iterator for string spreads a string into characters. After spreading, we pack these characters into an array. Then we spread this array again and pack it back to an array.
为什么等于3
?当我们用扩展操作时,会调用@@iterator
方法,并且返回的迭代器是用来获取被迭代的值的。字符串默认的迭代器会把字符串展开成一个个字符。展开后,我们把这些字符打包进一个数组。然后我们再次展开这个数组然后再打包进一个数组。
A '...'
string consists with three .
characters, so the length of resulting array is 3
.
一个'...'
字符串包含三个.
字符,所以数组的长度值为3
Now, step-by-step:
现在来做分解动作:
[...'...'] // -> [ '.', '.', '.' ]
[...[...'...']] // -> [ '.', '.', '.' ]
[...[...'...']].length // -> 3
Obviously, we can spread and wrap the elements of an array as many times as we want:
很显然,我们想这样重复多少次都可以:
[...'...'] // -> [ '.', '.', '.' ]
[...[...'...']] // -> [ '.', '.', '.' ]
[...[...[...'...']]] // -> [ '.', '.', '.' ]
[...[...[...[...'...']]]] // -> [ '.', '.', '.' ]
// and so on …
34. Labels
34. 标记
Not so many programmers know about labels in JavaScript. They are kind of interesting:
不是很经常有程序员知道JavaScript中有标记这个东西。他还真有点意思:
foo: {
console.log('first');
break foo;
console.log('second');
}
// > first
// -> undefined
Explanation:
说明:
The labeled statement is used with break
or continue
statements. You can use a label to identify a loop, and then use the break
or continue
statements to indicate whether a program should interrupt the loop or continue its execution.
标记过的语句与break
或 continue
一起使用,你可以用标记去识别一个循环,然后用break
或 continue
来识别一个程序是否该中断这个循环或者继续执行。
In the example above, we identify a label foo
. After that console.log('first');
executes and then we interrupt the execution.
在上边的例子中,我们识别了标签foo
。在 console.log('first');
之后执行了它,然后我们中断了循环
Read more about labels in JavaScript:
了解更多JavaScript中关于labels的知识:
35. Nested labels
35. 嵌套的标签
a: b: c: d: e: f: g: 1, 2, 3, 4, 5; // -> 5
Explanation:
说明:
Similar to previous examples, follow these links:
和之前的一些例子相同:阅读下边这些链接:
36. Insidious try..catch
36. 阴险的try..catch
What will this expression return? 2
or 3
?
这个表达式会返回什么?2
还是 3
?
(() => {
try {
return 2;
} finally {
return 3;
}
})()
The answer is 3
. Surprised?
答案是3
。惊讶不?
Explanation:
说明:
37. Is this multiple inheritance?
37. 这是多重继承吗?
Take a look at the example below:
看一下下边的例子:
new (class F extends (String, Array) { }) // -> F []
Is this a multiple inheritance? Nope.
这是一个多重继承么?不是。
Explanation:
说明:
The interesting part is the value of the extends
clause ((String, Array))
. The grouping operator always returns its last argument, so (String, Array)
is actually just Array
. That means we’ve just created a class which extends Array
.
有趣的是extends
的值延伸自((String, Array))
。组合操作符总是返回他的最后一个参数,所以(String, Array)
其实只是 Array
。这意味着我们只是创建了一个扩展自Array
的类。
38. A generator which yields itself
38. yield自己的生成器
Consider this example of a generator which yields itself:
思考这样一个例子,一个生成器yield自己:
(function* f() { yield f })().next()
// -> { value: [GeneratorFunction: f], done: false }
As you can see, the returned value is an object with its value
equal to f
. In that case, we can do something like this:
就像看到的一样,返回值是一个对象,这个对象的值和f
相等。在这种情况下,我们可以这样做:
(function* f() { yield f })().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }
// and again
(function* f() { yield f })().next().value().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }
// and again
(function* f() { yield f })().next().value().next().value().next().value().next()
// -> { value: [GeneratorFunction: f], done: false }
// and so on
// …
Explanation:
说明:
To understand why this works that way, read these sections of the specification:
理解为什么会有这样的结果,阅读规范中的这些部分:
39. A class of class
39. 一类的类
Consider this obfuscated syntax playing:
思考一下这个混乱的语法游戏:
(typeof (new (class { class () {} }))) // -> 'object'
It seems like we’re declaring a class inside of class. Should be and error, however, we get the string 'object'
.
看起来像是我们在一个类中声明了一个类。应该会得到一个错误,但是并没有,我们得到一个字符串object
Explanation:
说明:
Since ECMAScript 5 era, keywords are allowed as property names. So think about it as about this simple object example:
ECMAScript 5时代以来,关键词可以是属性名称。所以考虑一下这个简单的对象示例:
const foo = {
class: function() {}
};
And ES6 standardized shorthand method definitions. Also, classes can be anonymous. So if we drop : function
part, we’re going to get:
这是ES6标准的字面量简写方法定义的。并且,类可以是匿名的。因此,如果我们放弃 : function
部分,我们将得到:
class {
class() {}
}
The result of a default class is always a simple object. And its typeof should return 'object'
.
默认类的结果通常都是一个简单的对象。并且它的类型应该返回 'object'
。
Read more here:
了解更多:
40. Non-coercible objects
40. 非强制转换对象
With well-known symbols, there’s a way to get rid of type coercion. Take a look:
有了众所周知的符号,就有一种摆脱类型强制的方法。看一下吧。
function nonCoercible(val) {
if (val == null) {
throw TypeError('nonCoercible should not be called with null or undefined')
}
const res = Object(val)
res[Symbol.toPrimitive] = () => {
throw TypeError('Trying to coerce non-coercible object')
}
return res
}
Now we can use this like this:
现在我们可以这样用:
// objects
const foo = nonCoercible({foo: 'foo'})
foo * 10 // -> TypeError: Trying to coerce non-coercible object
foo + 'evil' // -> TypeError: Trying to coerce non-coercible object
// strings
const bar = nonCoercible('bar')
bar + '1' // -> TypeError: Trying to coerce non-coercible object
bar.toString() + 1 // -> bar1
bar === 'bar' // -> false
bar.toString() === 'bar' // -> true
bar == 'bar' // -> TypeError: Trying to coerce non-coercible object
// numbers
const baz = nonCoercible(1)
baz == 1 // -> TypeError: Trying to coerce non-coercible object
baz === 1 // -> false
baz.valueOf() === 1 // -> true
Explanation:
说明:
41. Tricky arrow functions
41. 搞怪的箭头功能
Consider the example below:
思考一下这个例子:
let f = () => 10
f() // -> 10
Okay, fine, but what about this:
let f = () => {}
f() // -> undefined
Explanation:
说明:
You might expect {}
instead of undefined
. This is because the curly braces are part of the syntax of the arrow functions, so f
will return undefined.
比起undefined
你也许更想见到{}
。这是因为大括号是箭头函数语法的一部分,所以f
会返回undefined。
42. Tricky return
42. 狡猾的return
return
statement is also tricky. Consider this:
return
同样很狡猾。思考这段代码:
(function () {
return
{
b : 10
}
})() // -> undefined
Explanation:
说明:
return
and the returned expression must be in the same line:
return
和返回的表达式必须在同一行:
(function () {
return {
b : 10
}
})() // -> { b: 10 }
43. Accessing object properties with arrays
43. 使用数组访问对象属性
var obj = { property: 1 }
var array = ['property']
obj[array] // -> 1
What about pseudo-multidimensional arrays?
换做伪多维数组呢?
var map = {}
var x = 1
var y = 2
var z = 3
map[[x, y, z]] = true
map[[x + 10, y, z]] = true
map["1,2,3"] // -> true
map["11,2,3"] // -> true
Explanation:
说明:
The brackets []
operator converts the expression passed toString
. Converting an one-element array to string it’s like converting the element to the string:
方括号将表达式通过toString进行转换
。将一个只有一个元素的数组转换为字符串,就像把元素转换为字符串一样。
['property'].toString() // -> 'property'
Other resources
其他资源
- wtfjs.com — a collection of those very special irregularities, inconsistencies and just plain painfully unintuitive moments for the language of the web.
- Wat — A lightning talk by Gary Bernhardt from CodeMash 2012
- What the… JavaScript? — Kyle Simpsons talk for Forward 2 attempts to “pull out the crazy” from JavaScript. He wants to help you produce cleaner, more elegant, more readable code, then inspire people to contribute to the open source community.
历经四天翻译完了,宝宝有一些累,需要一杯奶茶zZZ