From ecac645b5c5b38a8fbfa626f2118d2effb9f14b9 Mon Sep 17 00:00:00 2001 From: Gzlegendns Date: Tue, 29 Nov 2022 00:04:53 +0800 Subject: [PATCH 1/3] update READVE-zh-tw (zh-tw -> traditional Chinese) --- README-zh-tw.md | 2265 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2265 insertions(+) create mode 100644 README-zh-tw.md diff --git a/README-zh-tw.md b/README-zh-tw.md new file mode 100644 index 00000000..7425d44f --- /dev/null +++ b/README-zh-tw.md @@ -0,0 +1,2265 @@ +# What the f\*ck JavaScript? + +[![WTFPL 2.0][license-image]][license-url] +[![NPM version][npm-image]][npm-url] +[![Patreon][patreon-image]][patreon-url] +[![Buy Me A Coffee][bmc-image]][bmc-url] + +> 一個有趣和棘手的 JavaScript 示例列表。 + +JavaScript 是一個不錯的語言。它的語法簡單,生態系統也很龐大,最重要的是,它擁有最偉大的社區力量。 + +我們知道,JavaScript 是一個非常有趣的語言,但同時也充滿了各種奇怪的行為。這些奇怪的行為有時會搞砸我們的日常工作,有時則會讓我們忍俊不禁。 + +WTFJS 的靈感源於 [Brian Leroux](https://twitter.com/brianleroux)。這個列表受到他 [在 2012 年的 dotJS 上的演講 **“WTFJS”**](https://www.youtube.com/watch?v=et8xNAc2ic8) 的高度啟發: + +[![dotJS 2012 - Brian Leroux - WTFJS](https://img.youtube.com/vi/et8xNAc2ic8/0.jpg)](https://www.youtube.com/watch?v=et8xNAc2ic8) + +# 適用於 NodeJS 的指南手冊 + +你可以通過 `npm` 安裝該項目的指南手冊。只需運行: + +``` +$ npm install -g wtfjs +``` + +然後在命令行中運行 `wtfjs`,將會在命令行中打開手冊並跳轉至你選擇的頁數 `$PAGER`。這不是必需的步驟,你也可以繼續在這裡閱讀。 + +源碼在此處: + +# 翻譯 + +如今,**wtfjs** 已被翻譯成多種語言: + +- [中文](./README-zh-cn.md) +- [हिंदी](./README-hi.md) +- [Français](./README-fr-fr.md) +- [Português do Brasil](./README-pt-br.md) +- [Polski](./README-pl-pl.md) +- [Italiano](./README-it-it.md) +- [Russian](https://habr.com/ru/company/mailru/blog/335292/) (on Habr.com) +- [한국어](./README-kr.md) + +[**點此添加新的語言翻譯**][tr-request] + +[tr-request]: https://github.com/denysdovhan/wtfjs/blob/master/CONTRIBUTING.md#translations + +**注意:** 翻譯由該語言的譯者維護,因此可能缺失部分例子,或存在過時的例子等。 + + + + +# Table of Contents + +- [💪🏻 初衷](#-%E5%88%9D%E8%A1%B7) +- [✍🏻 符號](#-%E7%AC%A6%E5%8F%B7) +- [👀 例子](#-%E4%BE%8B%E5%AD%90) + - [`[]` 等於 `![]`](#-%E7%AD%89%E4%BA%8E-) + - [`true` 不等於 `![]`,也不等於 `[]`](#true-%E4%B8%8D%E7%AD%89%E4%BA%8E-%E4%B9%9F%E4%B8%8D%E7%AD%89%E4%BA%8E-) + - [true 是 false](#true-%E6%98%AF-false) + - [baNaNa](#banana) + - [`NaN` 不是 `NaN`](#nan-%E4%B8%8D%E6%98%AF-nan) + - [奇怪的 `Object.is()` 和 `===`](#%E5%A5%87%E6%80%AA%E7%9A%84-objectis-%E5%92%8C-) + - [它是 fail](#%E5%AE%83%E6%98%AF-fail) + - [`[]` 是真值,但不等於 `true`](#-%E6%98%AF%E7%9C%9F%E5%80%BC%E4%BD%86%E4%B8%8D%E7%AD%89%E4%BA%8E-true) + - [`null` 是假值,但又不等於 `false`](#null-%E6%98%AF%E5%81%87%E5%80%BC%E4%BD%86%E5%8F%88%E4%B8%8D%E7%AD%89%E4%BA%8E-false) + - [`document.all` 是一个 object,但又同時是 undefined](#documentall-%E6%98%AF%E4%B8%80%E4%B8%AA-object%E4%BD%86%E5%8F%88%E5%90%8C%E6%97%B6%E6%98%AF-undefined) + - [最小值大於零](#%E6%9C%80%E5%B0%8F%E5%80%BC%E5%A4%A7%E4%BA%8E%E9%9B%B6) + - [函數不是函數](#%E5%87%BD%E6%95%B0%E4%B8%8D%E6%98%AF%E5%87%BD%E6%95%B0) + - [數組相加](#%E6%95%B0%E7%BB%84%E7%9B%B8%E5%8A%A0) +- [數組中的尾逗號](#%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%B0%BE%E9%80%97%E5%8F%B7) + - [數組的相等性是深水猛獸](#%E6%95%B0%E7%BB%84%E7%9A%84%E7%9B%B8%E7%AD%89%E6%80%A7%E6%98%AF%E6%B7%B1%E6%B0%B4%E7%8C%9B%E5%85%BD) + - [`undefined` 和 `Number`](#undefined-%E5%92%8C-number) + - [`parseInt` 是一个壞蛋](#parseint-%E6%98%AF%E4%B8%80%E4%B8%AA%E5%9D%8F%E8%9B%8B) + - [`true` 和 `false` 的數字運算](#true-%E5%92%8C-false-%E7%9A%84%E6%95%B0%E5%AD%A6%E8%BF%90%E7%AE%97) + - [HTML 註釋在 JavaScript 中有效](#html-%E6%B3%A8%E9%87%8A%E5%9C%A8-javascript-%E4%B8%AD%E6%9C%89%E6%95%88) + - [`NaN` ~~不是~~一个數值](#nan-%E4%B8%8D%E6%98%AF%E4%B8%80%E4%B8%AA%E6%95%B0%E5%80%BC) + - [`[]` 和 `null` 是對象](#-%E5%92%8C-null-%E6%98%AF%E5%AF%B9%E8%B1%A1) + - [神奇的數字增長](#%E7%A5%9E%E5%A5%87%E7%9A%84%E6%95%B0%E5%AD%97%E5%A2%9E%E9%95%BF) + - [`0.1 + 0.2` 精度計算](#01--02-%E7%B2%BE%E5%BA%A6%E8%AE%A1%E7%AE%97) + - [擴展數字的方法](#%E6%89%A9%E5%B1%95%E6%95%B0%E5%AD%97%E7%9A%84%E6%96%B9%E6%B3%95) + - [三個數字的比較](#%E4%B8%89%E4%B8%AA%E6%95%B0%E5%AD%97%E7%9A%84%E6%AF%94%E8%BE%83) + - [有趣的數學](#%E6%9C%89%E8%B6%A3%E7%9A%84%E6%95%B0%E5%AD%A6) + - [正則表達式的加法](#%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E5%8A%A0%E6%B3%95) + - [字符串不是 `String` 的實例](#%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%8D%E6%98%AF-string-%E7%9A%84%E5%AE%9E%E4%BE%8B) + - [用反引號調用函數](#%E7%94%A8%E5%8F%8D%E5%BC%95%E5%8F%B7%E8%B0%83%E7%94%A8%E5%87%BD%E6%95%B0) + - [到底 call 了誰](#%E5%88%B0%E5%BA%95-call-%E4%BA%86%E8%B0%81) + - [`constructor` 屬性](#constructor-%E5%B1%9E%E6%80%A7) + - [將對象做為另一個對象的 key](#%E5%B0%86%E5%AF%B9%E8%B1%A1%E5%81%9A%E4%B8%BA%E5%8F%A6%E4%B8%80%E4%B8%AA%E5%AF%B9%E8%B1%A1%E7%9A%84-key) + - [訪問原形 `__proto__`](#%E8%AE%BF%E9%97%AE%E5%8E%9F%E5%9E%8B-__proto__) + - [`` `${{Object}}` ``](#-object-) + - [使用默認值解構](#%E4%BD%BF%E7%94%A8%E9%BB%98%E8%AE%A4%E5%80%BC%E8%A7%A3%E6%9E%84) + - [點和擴展運算符](#%E7%82%B9%E5%92%8C%E6%89%A9%E5%B1%95%E8%BF%90%E7%AE%97%E7%AC%A6) + - [標籤](#%E6%A0%87%E7%AD%BE) + - [嵌套標籤](#%E5%B5%8C%E5%A5%97%E6%A0%87%E7%AD%BE) + - [陰險的 `try..catch`](#%E9%98%B4%E9%99%A9%E7%9A%84-trycatch) + - [這是多重繼承嗎?](#%E8%BF%99%E6%98%AF%E5%A4%9A%E9%87%8D%E7%BB%A7%E6%89%BF%E5%90%97) + - [yield 返回自身的生成器](#yield-%E8%BF%94%E5%9B%9E%E8%87%AA%E8%BA%AB%E7%9A%84%E7%94%9F%E6%88%90%E5%99%A8) + - [類的類](#%E7%B1%BB%E7%9A%84%E7%B1%BB) + - [不可轉換類型的對象](#%E4%B8%8D%E5%8F%AF%E8%BD%AC%E6%8D%A2%E7%B1%BB%E5%9E%8B%E7%9A%84%E5%AF%B9%E8%B1%A1) + - [棘手的箭頭函數](#%E6%A3%98%E6%89%8B%E7%9A%84%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0) + - [箭頭函數不能作為建構函數](#%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0%E4%B8%8D%E8%83%BD%E4%BD%9C%E4%B8%BA%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0) + - [`arguments` 和箭头函數](#arguments-%E5%92%8C%E7%AE%AD%E5%A4%B4%E5%87%BD%E6%95%B0) + - [棘手的返回](#%E6%A3%98%E6%89%8B%E7%9A%84%E8%BF%94%E5%9B%9E) + - [對象的鏈式賦值](#%E5%AF%B9%E8%B1%A1%E7%9A%84%E9%93%BE%E5%BC%8F%E8%B5%8B%E5%80%BC) + - [使用數組訪問對象屬性](#%E4%BD%BF%E7%94%A8%E6%95%B0%E7%BB%84%E8%AE%BF%E9%97%AE%E5%AF%B9%E8%B1%A1%E5%B1%9E%E6%80%A7) + - [`Number.toFixed()` 顯示不同的數字](#numbertofixed-%E6%98%BE%E7%A4%BA%E4%B8%8D%E5%90%8C%E7%9A%84%E6%95%B0%E5%AD%97) + - [`min` 大於 `max`](#min-%E5%A4%A7%E4%BA%8E-max) + - [比較 `null` 和 `0`](#%E6%AF%94%E8%BE%83-null-%E5%92%8C-0) + - [相同變量重複聲明](#%E7%9B%B8%E5%90%8C%E5%8F%98%E9%87%8F%E9%87%8D%E5%A4%8D%E5%A3%B0%E6%98%8E) + - [Array.prototype.sort() 的默認行為](#arrayprototypesort-%E7%9A%84%E9%BB%98%E8%AE%A4%E8%A1%8C%E4%B8%BA) + - [resolve() 不會返回 Promise 實例](#resolve-%E4%B8%8D%E4%BC%9A%E8%BF%94%E5%9B%9E-promise-%E5%AE%9E%E4%BE%8B) + - [`{}{}` 是 undefined](#-%E6%98%AF-undefined) + - [`arguments` 綁定](#arguments-%E7%BB%91%E5%AE%9A) + - [來自地域的 `alert`](#%E6%9D%A5%E8%87%AA%E5%9C%B0%E7%8B%B1%E7%9A%84-alert) + - [沒有盡頭的計時](#%E6%B2%A1%E6%9C%89%E5%B0%BD%E5%A4%B4%E7%9A%84%E8%AE%A1%E6%97%B6) + - [`setTimeout` 對象](#settimeout-%E5%AF%B9%E8%B1%A1) + - [點點運算符](#%E7%82%B9%E7%82%B9%E8%BF%90%E7%AE%97%E7%AC%A6) + - [再 new 一次](#%E5%86%8D-new-%E4%B8%80%E6%AC%A1) + - [你應該用上分號](#%E4%BD%A0%E5%BA%94%E8%AF%A5%E7%94%A8%E4%B8%8A%E5%88%86%E5%8F%B7) + - [用空格分割(split)字符串](#%E7%94%A8%E7%A9%BA%E6%A0%BC%E5%88%86%E5%89%B2split%E5%AD%97%E7%AC%A6%E4%B8%B2) + - [對字符串 stringify](#%E5%AF%B9%E5%AD%97%E7%AC%A6%E4%B8%B2-stringify) + - [對數字和 `true` 的非嚴格相等比较](#%E5%AF%B9%E6%95%B0%E5%AD%97%E5%92%8C-true-%E7%9A%84%E9%9D%9E%E4%B8%A5%E6%A0%BC%E7%9B%B8%E7%AD%89%E6%AF%94%E8%BE%83) +- [其他資源](#%E5%85%B6%E4%BB%96%E8%B5%84%E6%BA%90) +- [🤝 捐贈支持](#-%E6%8D%90%E8%B5%A0%E6%94%AF%E6%8C%81) +- [🎓 許可證](#-%E8%AE%B8%E5%8F%AF%E8%AF%81) + + + + +# 💪🏻 初衷 + +> 只是因為好玩 +> +> — _[**“只是為了好玩:一个意外革命的故事”**](https://en.m.wikipedia.org/wiki/Just_for_Fun), Linus Torvalds_ + +這個列表的主要目的是收集一些瘋狂的例子,並儘可能解釋它們的原理。我很喜歡學習以前不了解的東西。 + +如果您是初學者,您可以根據此筆記深入了解 JavaScript。我希望它會激勵你在閱讀規範上投入更多時間和精力。 + +如果您是專業開發人員,您將從這些例子中看到人見人愛的 JavaScript 也充滿了非預期的邊界行為。 + +總之,古人云:三人行,必有我師焉。我相信這些例子總能讓你學習到新的知識。 + +> **⚠️ Note:** 如果這些例子幫助到你,請[務必贊助收集了這些例子的作者](#-supporting). + +# ✍🏻 符号 + +**`// ->`** 表示表達式的結果。例如: + +```js +1 + 1; // -> 2 +``` + +**`// >`** 表示 `console.log` 等输出的結果。例如: + +```js +console.log("hello, world!"); // > hello, world! +``` + +**`//`** 則是用於解釋的註釋。例如: + +```js +// 將一個函數賦值給 foo 常量 +const foo = function() {}; +``` + +# 👀 例子 + +## `[]` 等於 `![]` + +數組等於一個數組取反: + +```js +[] == ![]; // -> true +``` + +### 💡 說明: + +抽象相等運算符會將其兩端的表達式轉換為數字值進行比較,儘管這個例子中,左右兩端均被轉換為 `0`,但原因各不相同。數組總是真值(truthy),因此右值的數組取反後總是為 `false`,然後在抽象相等比較中被被類型轉換為 `0`。而左值則是另一種情形,空數組沒有被轉換為布爾值的話,儘管在邏輯上是真值(truthy),但在抽象相等比較中,會被類型轉換為數字 `0`。 + +該表達式的運算步驟如下: + +```js ++[] == +![]; +0 == +false; +0 == 0; +true; +``` + +了解更多:[`[]` 是真值,但並非 `true`](#-is-truthy-but-not-true). + +- [**12.5.9** 邏輯非運算符 (`!`)](https://www.ecma-international.org/ecma-262/#sec-logical-not-operator) +- [**7.2.13** 抽象相等比較 ](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) + +## `true` 不等於 `![]`,也不等於 `[]` + +數組不等於 `true`,但數組取反也不等於 `true`; +數組等於 `false`數組取反也等於 `false`: + +```js +true == []; // -> false +true == ![]; // -> false + +false == []; // -> true +false == ![]; // -> true +``` + +### 💡 說明: + +```js +true == []; // -> false +true == ![]; // -> false + +// 根據規範 + +true == []; // -> false + +toNumber(true); // -> 1 +toNumber([]); // -> 0 + +1 == 0; // -> false + +true == ![]; // -> false + +![]; // -> false + +true == false; // -> false +``` + +```js +false == []; // -> true +false == ![]; // -> true + +// 規具規範 + +false == []; // -> true + +toNumber(false); // -> 0 +toNumber([]); // -> 0 + +0 == 0; // -> true + +false == ![]; // -> true + +![]; // -> false + +false == false; // -> true +``` + +- [**7.2.15** 抽象相等比較](https://262.ecma-international.org/11.0/index.html#sec-abstract-equality-comparison) + +## true 是 false + +```js +!!"false" == !!"true"; // -> true +!!"false" === !!"true"; // -> true +``` + +### 💡 說明: + +考慮以下步驟: + +```js +// true 是真值(truthy),並且隱式轉換為數字1,而字符串 'true' 會被轉換為 NaN。 +true == "true"; // -> false +false == "false"; // -> false + +// 'false' 不是空字符串,所以它的值是 true +!!"false"; // -> true +!!"true"; // -> true +``` + +- [**7.2.13** 抽象相等比較](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) + +## baNaNa + +```js +"b" + "a" + +"a" + "a"; +``` + +這是用 JavaScript 寫的老派笑話,原版如下: + +```js +"foo" + +"bar"; // -> 'fooNaN' +``` + +### 💡 說明: + +這個表達式可以轉化成 `'foo' + (+'bar')`,但無法將`'bar'`強制轉化成數值。 + +- [**12.8.3** 加法運算符 (`+`)](https://www.ecma-international.org/ecma-262/#sec-addition-operator-plus) +- [12.5.6 一元 + 運算符](https://www.ecma-international.org/ecma-262/#sec-unary-plus-operator) + +## `NaN` 不是 `NaN` + +```js +NaN === NaN; // -> false +``` + +### 💡 說明: + +規範嚴格定義了這種行為背後的邏輯: + +> 1. 如果 `Type(x)` 不同於 `Type(y)`,返回 **false**。 +> 2. 如果 `Type(x)` 數值, 然後 +> 1. 如果 `x` 是 **NaN**,返回 **false**。 +> 2. 如果 `y` 是 **NaN**,返回 **false**。 +> 3. …… +> +> — [**7.2.14** 嚴格模式相等比較 ](https://www.ecma-international.org/ecma-262/#sec-strict-equality-comparison) + +根據 IEEE 對 NaN 的定義: + +> 有四種可能的相互排斥的關係:小於、等於、大於和無序。當比較操作中至少一個操作數是 NaN 時,便是無序的關係。換句話說,NaN 對任何事物包括其本身比較都應當是無序關係。 +> +> — StackOverflow 上的 [“為什麼對於 IEEE754 NaN 值的所有比較返回 false?”](https://stackoverflow.com/questions/1565164/1573715#1573715) + +## 奇怪的 `Object.is()` 和 `===` + +`Object.is()` 用於判斷兩個值是否相同。和 `===` 操作符像作用類似,但它也有一些奇怪的行為: + +```javascript +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 的規範](https://tc39.es/ecma262/#sec-object.is) +- MDN 上的[相等比較與相同值比較](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness) + +## 它是 fail + +你可能不會相信,但…… + +```js +(![] + [])[+[]] + + (![] + [])[+!+[]] + + ([![]] + [][[]])[+!+[] + [+[]]] + + (![] + [])[!+[] + !+[]]; +// -> 'fail' +``` + +### 💡 說明: + +將大量的符號分解成片段,我們注意到,以下表达式经常出现: + +```js +![] + []; // -> 'false' +![]; // -> false +``` + +所以我們嘗試將 `[]` 和 `false` 加起來。但是因為一些內部函數調用(`binary + Operator` - >`ToPrimitive` - >`[[DefaultValue]` ]),我們最終將右邊的操作數轉換為一個字符串: + +```js +![] + [].toString(); // 'false' +``` + +將字符串作為數組,我們可以通過`[0]`來訪問它的第一個字符: + +```js +"false"[0]; // -> 'f' +``` + +剩下的部分以此類推,不過此處的 `i` 字符是比較討巧的。 `fail` 中的 `i` 來自於生成的字符串 `falseundefined`,通過指定序號 `['10']` 取得的。 + +更多的例子: + +```js ++![] // -> 0 ++!![] // -> 1 +!![] // -> true +![] // -> false +[][[]] // -> undefined ++!![] / +![] // -> Infinity +[] + {} // -> "[object Object]" ++{} // -> NaN +``` + +- [燒腦預警:瘋狂的 JavaScript](http://patriciopalladino.com/blog/2012/08/09/non-alphanumeric-javascript.html) +- [寫個句子幹嘛要用字母](https://bluewings.github.io/en/writing-a-sentence-without-using-the-alphabet/#weird-javascript-generator) — 用 JavaScript 生成任意短語 + +## `[]` 是真值,但不等於 `true` + +數組是一个真值,但卻不等於 `true`。 + +```js +!![] // -> true +[] == true // -> false +``` + +### 💡 說明: + +以下是 ECMA-262 規範中相應部分的鏈接: + +- [**12.5.9** 邏輯非運算符 (`!`)](https://www.ecma-international.org/ecma-262/#sec-logical-not-operator) +- [**7.2.13** 抽象相等比較](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) + +## `null` 是假值,但又不等於 `false` + +僅管 `null` 是假值,但它不等於 `false`。 + +```js +!!null; // -> false +null == false; // -> false +``` + +但是,别的被當作假值的卻等於 `false`,如 `0` 或 `''`。 + +```js +0 == false; // -> true +"" == false; // -> true +``` + +### 💡 說明: + +跟前面的例子相同。這是一个相應的鏈接: + +- [**7.2.13** 抽象相等比较](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) + +## `document.all` 是一个 object,但又同時是 undefined + +> ⚠️ 這是瀏覽器 API 的一部分,對於 Node.js 環境無敵 ⚠️ + +僅管 document.all 是一個類數組對象(array-like object),並且通過它可以訪問頁面中的 DOM 節點,但在通過 `typeof` 的檢測結果是 `undefined`。 + +```js +document.all instanceof Object; // -> true +typeof document.all; // -> 'undefined' +``` + +同時,`document.all` 不等於 `undefined`。 + +```js +document.all === undefined; // -> false +typeof document.all; // -> 'undefined' +``` + +但是同時,`document.all` 不等於 `undefined`: + +```js +document.all === undefined; // -> false +document.all == null; // -> true +``` + +不過: + +```js +document.all == null; // -> true +``` + +### 💡 說明: + +> `document.all` 作為訪問頁面 DOM 節點的一種方式,在早期版本的 IE 瀏覽器中較為流行。儘管這一 API 從未成為標準,但被廣泛使用在早期的 JS 代碼中。當標準演變出新的 API(例如 `document.getElementById`)時,這個 API 調用就被廢棄了。因為這個 API 的使用範圍較為廣泛,標準委員會決定保留這個 API,但有意地引入一個違反 JavaScript 標準的規範。 +> 這個有意的對違反標準的規范明確地允許該 API 與 `undefined` 使用[嚴格相等比較](https://www.ecma-international.org/ecma-262/#sec-strict-equality-comparison)得出 `false` 而使用[抽象相等比較](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) 得出 `true`。 +> +> — [“廢棄功能 - document.all”](https://html.spec.whatwg.org/multipage/obsolete.html#dom-document-all) at WhatWG - HTML spec +> — YDKJS(你不懂 JS) - 類型與語法 中的 [“第 4 章 - ToBoolean - 假值](https://github.com/getify/You-Dont-Know-JS/blob/0d79079b61dad953bbfde817a5893a49f7e889fb/types%20%26%20grammar/ch4.md#falsy-objects) + +## 最小值大於零 + +`Number.MIN_VALUE` 是最小的數字,大於零: + +```js +Number.MIN_VALUE > 0; // -> true +``` + +### 💡 說明: + +> `Number.MIN_VALUE` 是 `5e-324`,即可以在浮點精度内表示的最小正数,也是在該精度内無限接近零的數字。它定義了浮點數的最高精度。 + +> 現在,整體最小的值是 `Number.NEGATIVE_INFINITY`,儘管這在嚴格意義上並不是真正的數字。 +> +> — StackOverflow 上的[“為甚麼在 JavaScript 中 `0` 小於 `Number.MIN_VALUE`?”](https://stackoverflow.com/questions/26614728/why-is-0-less-than-number-min-value-in-javascript) + +- [**20.1.2.9** Number.MIN_VALUE](https://www.ecma-international.org/ecma-262/#sec-well-known-symbols) + +## 函數不是函數 + +> ⚠️ V8 v5.5 或更低版本中出現的 Bug(Node.js <= 7) ⚠️ + +大家都知道 _undefined 不是 function_ 對吧?但是你知道這個嗎? + +```js +// 聲明一個繼承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 測試成功)。 + +```js +class Foo extends null {} +new Foo() instanceof null; +// > TypeError: Super constructor null of Foo is not a constructor +``` + +### 💡 說明: + +這並不是缺陷,因為: + +```js +Object.getPrototypeOf(Foo.prototype); // -> null +``` + +若當前類沒有構造函數,則在構造該類時會順次調用其原型鏈上的構造函數,而本例中其父類沒有構造函數。補充一下,`null` 也是一个 `object`: + +```js +typeof null === "object"; +``` + +因此,你可以繼承 `null`(僅管在面向對象编程的世界里這是不允許的),但是卻不能調用 `null` 的構造函數。若你把代碼改成這樣: + +```js +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` 時: + +```js +class Foo extends null { + constructor() { + console.log(111); + super(); + } +} +``` + +JS 抛出錯誤: + +``` +TypeError: Super constructor null of Foo is not a constructor +// 類型錯誤:Foo的超類的構造函數null不是構造函數 +``` + +- [@geekjob](https://github.com/geekjob) 發布的 [對問题的解釋](https://github.com/denysdovhan/wtfjs/pull/102#discussion_r259143582) + +## 數組相加 + +如果你嘗試將兩個數組相加: + +```js +[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6' +``` + +### 💡 說明: + +數據之間會發生串聯。步驟如下: + +```js +[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 个空元素的數組。如下所示,最終只能得到一个包含三個元素的數組,原因在於尾逗號: + +```js +let a = [, , ,]; +a.length; // -> 3 +a.toString(); // -> ',,' +``` + +### 💡 說明: + +> **尾逗號** (trailing commas,有時也稱為“最后逗號”(final commas)) 在向 JavaScript 代碼中添加新元素、参數或屬性時非常有用。如果您想添加一个新屬性,若前一行已經有尾逗號,你无需修改前一行,只要添加一個新一行並加上尾逗號即可。這使得版本控制歷史較為乾淨,編輯代碼也很簡單。 +> +> — MDN 上的 [尾逗號](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas) + +## 數組的相等性是深水猛獸 + +數組之間進行相等比較是 JS 中的深水猛獸,看看這些例子: + +```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** 抽象相等比較](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) 一節描述了這些行為。 + +## `undefined` 和 `Number` + +無參數調用 `Number` 構造函數會返回 `0`。我們知道,當函數沒有接受到指定位置的實際參數時,該處的形式參數的值會是 `undefined`。因此,你可能覺得當我們傳入 `undefined` 時應當同樣返回 `0`。然而實際上傳入 `undefined` 返回的是 `NaN`。 + +```js +Number(); // -> 0 +Number(undefined); // -> NaN +``` + +### 💡 說明: + +根據規範: + +1. 若無參數調用該函數,`n` 將為 `+0`。 +2. 否則,`n` 將為? `ToNumber(value)`。 +3. 如果值為 `undefined`,`ToNumber(undefined)` 應該返回 `NaN`。 + +這是相應的部分: + +- [**20.1.1** Number 構造函數 ](https://www.ecma-international.org/ecma-262/#sec-number-constructor) +- [**7.1.3** ToNumber(`argument`)](https://www.ecma-international.org/ecma-262/#sec-tonumber) + +## `parseInt` 是一個壞蛋 + +`parseInt` 以它的怪異而出名。 + +```js +parseInt("f*ck"); // -> NaN +parseInt("f*ck", 16); // -> 15 +``` + +**💡 說明:** +這是因為 `parseInt` 會持續解析直到它解析到一個不識別的字符,`'f*ck'` 中的 `f` 是 16 進制下的 `15`。 + +解析 `Infinity` 到整數也很有意思…… + +```js +// +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 +``` + +也要小心解析 `null`: + +```js +parseInt(null, 24); // -> 23 +``` + +**💡 說明:** + +> 它將 `null` 轉換成字符串 `'null'`,並嘗試轉換它。對於基數 0 到 23,沒有可以轉換的數字,因此返回 NaN。而當基數為 24 時,第 14 個字母`“n”`也可以作數字用。當基數為 31 時,第 21 個字母`“u”`進入數字的行列,此時整個字符串都可以解析了。而當基數增加到 37 以上,已經超出了數字和字母所能表達的數字範圍,因此一律返回 `NaN`。 +> +> — StackOverflow 上的 [“parseInt(null, 24) === 23 什么鬼”](https://stackoverflow.com/questions/6459758/parseintnull-24-23-wait-what) + +不要忘记八進制: + +```js +parseInt("06"); // 6 +parseInt("08"); // 8 如果支持 ECMAScript 5 +parseInt("08"); // 0 如果不支持 ECMAScript 5 +``` + +**💡 說明:** +當輸入的字符串以“0”開始時,根據實現的不同,會被解釋為八進製或十進制。 ECMAScript 5 明確表示應當使用十進制,但有部分瀏覽器仍不支持。因此推薦在調用 `parseInt` 函數時總是傳入表示基數的第二個參數。 + +`parseInt` 會先將參數值轉換為字符串: + +```js +parseInt({ toString: () => 2, valueOf: () => 1 }); // -> 2 +Number({ toString: () => 2, valueOf: () => 1 }); // -> 1 +``` + +解析浮點數的時候要注意 + +```js +parseInt(0.000001); // -> 0 +parseInt(0.0000001); // -> 1 +parseInt(1 / 1999999); // -> 5 +``` + +**💡 說明:** `parseInt` 接受字符串參數並返回一個指定基數下的整數。 `parseInt` 會將字符串中首個非數字字符(字符集由基數決定)及其後的內容全部截斷。如 `0.000001` 被轉換為 `"0.000001"`,因此 `parseInt` 返回 `0`。而 `0.0000001` 轉換為字符串會變成 `"1e-7"`,因此 `parseInt` 返回 `1`。 `1/1999999` 被轉換為 `5.00000250000125e-7`,所以 `parseInt` 返回 `5`。 +## `true` 和 `false` 的數學運算 + +做一下數學計算: + +```js +true + true; // -> 2 +(true + true) * (true + true) - true; // -> 3 +``` + +嗯……🤔 + +### 💡 說明: + +我們可以用 `Number` 構造函數將值強制轉化成數值。很明顯,`true` 將被強制轉換為 `1` : + +```js +Number(true); // -> 1 +``` + +一元加運算符會嘗試將其值轉換成數字。它可以轉換字符串形式表達的整數和浮點數,以及非字符串值 `true`、`false` 和 `null`。如果它不能解析特定的值,它將轉化為 `NaN`。這意味著我們可以有更簡便的方式將 `true` 轉換成 `1`: + +```js ++true; // -> 1 +``` + +當你執行加法或乘法時,將會 `ToNumber` 方法。根據規範,該方法的返回值為: + +> 如果`參數`是 **true**,返回 **1**。如果`參數`是 **false**,則返回 **+0**。 + +因此我們可以將布爾值相加並得到正確的結果 + +相應章節: + +- [**12.5.6** 一元 `+` 運算符 ](https://www.ecma-international.org/ecma-262/#sec-unary-plus-operator) +- [**12.8.3** 加法運算符(`+`) ](https://www.ecma-international.org/ecma-262/#sec-addition-operator-plus) +- [**7.1.3** ToNumber(`argument`)](https://www.ecma-international.org/ecma-262/#sec-tonumber) + +## HTML 註釋在 JavaScript 中有效 + +你可能會感到震驚,` +```js +(function() { + return + { + b: 10; + } +})(); // -> undefined +``` + + +### 💡 說明: + +`return` 和返回的表達式必須在同一行: + +```js +(function() { + return { + b: 10 + }; +})(); // -> { b: 10 } +``` + +這是因為一個叫自動分號插入的概念,它會在大部分換行處插入分號。第一個例子裡,`return` 語句和對象字面量中間被插入了一個分號。所以函數返回 `undefined`,其後的對象字面量永遠不會被求值。 + +- [**11.9.1** 自動分號插入的規則](https://www.ecma-international.org/ecma-262/#sec-rules-of-automatic-semicolon-insertion) +- [**13.10** `return` 語句](https://www.ecma-international.org/ecma-262/#sec-return-statement) + +## 對象的鍊式賦值 + +```js +var foo = { n: 1 }; +var bar = foo; + +foo.x = foo = { n: 2 }; + +foo.x; // -> undefined +foo; // -> {n: 2} +bar; // -> {n: 1, x: {n: 2}} +``` + +從右到左,`{n: 2}` 被賦值給 `foo`,而此賦值的結果 `{n: 2}` 被賦值給 `foo.x`,因此 `bar` 是 `{n: 1, x: {n: 2}}`,畢竟 `bar` 是 `foo` 的一個引用。但為什麼 `foo.x` 是 `undefined` 而 `bar.x` 不是呢? + +### 💡 說明: + +`foo` 和 `bar` 引用同一個對象 `{n: 1}`,而左值在賦值前解析。 `foo = {n: 2}` 是創建一個新對象,所以 `foo` 被更新為引用那個新的對象。因為 `foo.x = ...` 中的 `foo` 作為左值在賦值前就被解析並依然引用舊的 `foo = {n: 1}` 對象並為其添加了 `x` 值。在鍊式賦值之後,`bar` 依然引用舊的 `foo` 對象,但 `foo` 更新為沒有 `x` 屬性的 `{n: 2}` 對象。 + +它等價於: + +```js +var foo = { n: 1 }; +var bar = foo; + +foo = { n: 2 }; // -> {n: 2} +bar.x = foo; // -> {n: 1, x: {n: 2}} +// bar.x 指向新的 foo 對象的地址 +// 這不等價於:bar.x = {n: 2} +``` + +## 使用數組訪問對象屬性 + +```js +var obj = { property: 1 }; +var array = ["property"]; + +obj[array]; // -> 1 +``` + +那關於偽多維數組創建對象呢? + +```js +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 +``` + +### 💡 說明: + +`[]` 操作符會使用 `toString` 將傳遞的表達式轉換為字符串。將單元素數組轉換為字符串,相當於將這個元素轉換為字符串: + +```js +["property"].toString(); // -> 'property'` +``` + +## `Number.toFixed()` 顯示不同的數字 + +`Number.toFixed()` 在不同的瀏覽器中會表現得有點奇怪。看看這個例子: + +```js +(0.7875).toFixed(3); +// Firefox: -> 0.787 +// Chrome: -> 0.787 +// IE11: -> 0.788 +(0.7876).toFixed(3); +// Firefox: -> 0.788 +// Chrome: -> 0.788 +// IE11: -> 0.788 +``` + +### 💡 說明: + +儘管你的第一直覺可能是 IE11 是正確的而 Firefox/Chrome 錯了,事實是 Firefox/Chrome 更直接地遵循數字運算的標準(IEEE-754 Floating Point),而 IE11 經常違反它們(可能)去努力得出更清晰的結果。 + +你可以通過一些快速的測試來了解為什麼它們發生: + +```js +// 確認 5 向下取整的奇怪結果 +(0.7875).toFixed(3); // -> 0.787 +// 當你展開到 64 位(雙精度)浮點數準確度限制時看起來就是一個 5 +(0.7875).toFixed(14); // -> 0.78750000000000 +// 但如果你超越這個限制呢? +(0.7875).toFixed(20); // -> 0.78749999999999997780 +``` + +浮點數在計算機內部不是以一系列十進制數字的形式存儲的,而是通過一個可以產生一點點通常會被 toString 或者其他調用取整的不准確性的更複雜的方法,但它實際上在內部會被表示。 + +在這裡,那個結尾的 "5" 實際上是一個極其小的略小於 5 的分數。將其以任何常理的長度取整它都會被看作一個 5,但它在內部通常不是 5。 + +然而 IE11 會直接在這個數字後面補 0,甚至在 toFixed(20) 的時候也是這樣,因為它看起來強制取整了值來減少硬件限制帶來的問題。 + +詳見 ECMA-262 中 `NOTE 2` 的 `toFixed` 的定義。 + +- [**20.1.3.3** Number.prototype.toFixed (`fractionDigits`)](https://www.ecma-international.org/ecma-262//#sec-number.prototype.tofixed) + +## `min` 大於 `max` + +我發現一個神奇的例子: + +```js +Math.min() > Math.max(); // -> true +Math.min() < Math.max(); // -> false +``` + +### 💡 說明: + +這是一個簡單的例子。我們一步一步來: + +```js +Math.min(); // -> Infinity +Math.max(); // -> -Infinity +Infinity > -Infinity; // -> true +``` + +為什麼是這樣呢?其實 `Math.max()` 並不會返回最大的正數,即 `Number.MAX_VALUE`。 + +`Math.max` 接受兩個參數,將它們轉換到數字,比較之後返回最大的那個。若沒有傳入參數,結果將是 -∞。若參數中存在 `NaN`,則返回 `NaN`。 + +反過來,當 `Math.min` 沒有傳入參數,會返回 ∞。 + +- [**15.8.2.11** Math.max](https://262.ecma-international.org/5.1/#sec-15.8.2.11) +- [**15.8.2.11** Math.min](https://262.ecma-international.org/5.1/#sec-15.8.2.12) +- [為什麼 `Math.max()` 小於 `Math.min()`? ](https://charlieharvey.org.uk/page/why_math_max_is_less_than_math_min)## `Math.max()` 小於 `Math.min()` + +```js +Math.min(1, 4, 7, 2); // -> 1 +Math.max(1, 4, 7, 2); // -> 7 +Math.min(); // -> Infinity +Math.max(); // -> -Infinity +Math.min() > Math.max(); // -> true +``` + +### 💡 說明: + +- Charlie Harvey 的 [Why is Math.max() less than Math.min()?](https://charlieharvey.org.uk/page/why_math_max_is_less_than_math_min) + +## 比較 `null` 和 `0` + +下面的表達式似乎有點矛盾: + +```js +null == 0; // -> false +null > 0; // -> false +null >= 0; // -> true +``` + +既然 `null >= 0` 返回 `true`,為什麼 `null` 既不等於也不大於 `0`? (對於小於比較也可以得出相似的結果。) + +### 💡 說明: + +這三個表達式的求值方式各不相同,因此產生了非預期的結果。 +首先,對於 `null == 0` 這個抽象相等比較操作,通常當該運算符不能正確地比較兩邊的值,則它會將兩邊的值都轉換為數字,再對數字進行比較。那麼,您可能會期望以下行為: + +```js +// 事實並非如此 +(null == 0 + null) == +0; +0 == 0; +true; +``` + +然而,仔細閱讀規範就會發現,數字轉換實際上並沒有發生在 `null` 或 `undefined` 的一側。也就是說,如果在等號的一側有 `null`,則當另一側的表達式為 `null` 或 `undefined`就返回 `true`;反之則返回 `false`。 + +接下來,對於 `null > 0` 這個比較關係。與抽象相等運算符的算法不同,它 _會_ 先將 `null` 轉換為一個數字。因此,我們得到這樣的行為: + +```js +null > 0 ++null = +0 +0 > 0 +false +``` + +最後一個,對於 `null >= 0` 的比較關係。你可能認為這個表達式應該等同於 `null > 0 || null == 0` 的結果;如果真是這樣,那麼基於上述的討論,這裡的結果也應當是 `false` 才對。然而,`>=` 操作符的工作方式實際上是 `<` 操作符的取反。在我們上述的討論中,關於大於運算符的論述也適用於小於運算符,也就是說這個表達式的值是這樣出來的: + +```js +null >= 0; +!(null < 0); +!(+null < +0); +!(0 < 0); +!false; +true; +``` + +- [**7.2.12** 抽象關係比較](https://www.ecma-international.org/ecma-262/#sec-abstract-relational-comparison) +- [**7.2.13** 抽象相等比較](https://www.ecma-international.org/ecma-262/#sec-abstract-equality-comparison) +- [一篇深入淺出的說明](https://blog.campvanilla.com/javascript-the-curious-case-of-null-0-7b131644e274) + +## 相同變量重複聲明 + +JS 允許重複聲明變量: + +```js +a; +a; +// 這也是有效的 +a, a; +``` + +嚴格模式也可以運行: + +```js +var a, a, a; +var a; +var a; +``` + +### 💡 解釋: + +所有的定義都被合併成一條定義。 + +- [**13.3.2** 變量表達式](https://www.ecma-international.org/ecma-262/#sec-variable-statement) + +## Array.prototype.sort() 的默認行為 + +假設你需要對數組排序。 + +``` +[ 10, 1, 3 ].sort() // -> [ 1, 10, 3 ] +``` + +### 💡 說明: + +默認的排序算法基於將給定元素轉換為字符串,然後比較它們的 UTF-16 序列中的值。 + +- [**22.1.3.25** Array.prototype.sort ( comparefn )](https://www.ecma-international.org/ecma-262/#sec-array.prototype.sort) + +### 提示 + +傳入一個 `compareFn` 比較函數,對非字符串的其他值排序。 + +``` +[ 10, 1, 3 ].sort((a, b) => a - b) // -> [ 1, 3, 10 ] +``` + +## resolve() 不會返回 Promise 實例 + +```javascript +const theObject = { + a: 7 +}; +const thePromise = new Promise((resolve, reject) => { + resolve(theObject); +}); // -> Promise 實例對象 + +thePromise.then(value => { + console.log(value === theObject); // -> true + console.log(value); // -> { a: 7 } +}); +``` + +從 `thePromise` 接收到的 `value` 值確實是 `theObject`。 + +那麼,如果向 `resolve` 傳入另外一個 `Promise` 會怎樣? + +```javascript +const theObject = new Promise((resolve, reject) => { + resolve(7); +}); // -> Promise 實例對象 +const thePromise = new Promise((resolve, reject) => { + resolve(theObject); +}); // -> Promise 實例對象 + +thePromise.then(value => { + console.log(value === theObject); // -> false + console.log(value); // -> 7 +}); +``` + +### 💡 說明: + +> 此函數將類 promise 對象的多層嵌套平鋪到單層嵌套。 (例如上述的 promise 函數 resolve 了另一個會 resolve 出其他對象的 promise 函數) + +– [MDN 上的 Promise.resolve()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) + +官方規範是 [ECMAScript 25.6.1.3.2 Promise 的 Resolve 函數](https://tc39.es/ecma262/#sec-promise-resolve-functions),但是這一章節對人類非常不友好。 + +## `{}{}` 是 undefined + +你可以在終端測試一下。類似這樣的結構會返回最後定義的對像中的值。 + +```js +{}{}; // -> undefined +{}{}{}; // -> undefined +{}{}{}{}; // -> undefined +{foo: 'bar'}{}; // -> 'bar' +{}{foo: 'bar'}; // -> 'bar' +{}{foo: 'bar'}{}; // -> 'bar' +{a: 'b'}{c:' d'}{}; // -> 'd' +{a: 'b', c: 'd'}{}; // > SyntaxError: Unexpected token ':' +({}{}); // > SyntaxError: Unexpected token '{' +``` + +### 💡 说明: + +解析到 `{}` 會返回 `undefined`,而解析 `{foo: 'bar'}{}`時,表達式 `{foo: 'bar'}` 返回 `'bar'`。 + +`{}` 有兩重含義:表示對象,或表示代碼塊。例如,在 `() => {}` 中的 `{}` 表示代碼塊。所以我們必須加上括號:`() => ({})` 才能讓它正確地返回一個對象。 + +因此,我們現在將 `{foo: 'bar'}` 當作代碼塊使用,則可以在終端中這樣寫: + +```js +if (true) { + foo: "bar"; +} // -> 'bar' +``` + +啊哈,一樣的結果!所以 `{foo: 'bar'}{}` 中的花括號就是表示代碼塊。 + +## `arguments` 綁定 + +考慮以下函數: + +```js +function a(x) { + arguments[0] = "hello"; + console.log(x); +} + +a(); // > undefined +a(1); // > "hello" +``` + +### 💡 說明 + +`arguments` 是一個類數組對象,包含了所有傳入當前函數的參數。當沒有傳入參數時,該對像中就不存在 `x` 屬性,也就無法覆蓋。 + +- [arguments 對象](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments) on MDN + +## 來自地獄的 `alert` + +如題,從地獄而來的代碼: + +```js +[666]["\155\141\160"]["\143\157\156\163\164\162\165\143\164\157\162"]( + "\141\154\145\162\164(666)" +)(666); // alert(666) +``` + +### 💡 說明 + +這一串代碼是基於多個採用了八進制轉義序列的字符串構造的。 + +任何碼值小於 256 的字符(又稱擴展 ASCII 碼表域)都可以用 `\` 加上其八進制代碼的轉義方式寫出來。上面這個簡單的例子就是將 `alert` 編碼到八進制轉義序列。 + +- [Martin Kleppe 的推特](https://twitter.com/aemkei/status/897172907222237185) +- [JavaScript 字符轉義序列](https://mathiasbynens.be/notes/javascript-escapes#octal) +- [多行 JavaScript 字符串](https://davidwalsh.name/multiline-javascript-strings) + +## 沒有盡頭的計時 + +如果我們對 `setTimeout` 賦予無限大會如何? + +```js +setTimeout(() => console.log("called"), Infinity); // -> +// > 'called' +``` + +結果是,它會立即運行,並沒有等待無限長的時間。 + +### 💡 說明: + +通常運行時內部會將延時存儲為一個 32 位的有符號整數,而上述代碼會導致運行時在解析延時參數時發生整數溢出,從而使函數立即執行而不等待。 + +例如,在 Node.js 中我們可以看到這樣的警告信息: + +``` +(node:1731) TimeoutOverflowWarning: Infinity does not fit into a 32-bit signed integer. +Timeout duration was set to 1. +(Use `node --trace-warnings ...` to show where the warning was created) +``` + +- [WindowOrWorkerGlobalScope.setTimeout()](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) on MDN +- [Node.js 文檔中關於計時器的章節](https://nodejs.org/api/timers.html#timers_settimeout_callback_delay_args) +- W3C 上的 [計時器]](https://www.w3.org/TR/2011/WD-html5-20110525/timers.html) + +## `setTimeout` 對象 + +如果我們給 `setTimeout` 的回調函數參數傳非函數值會發生什麼? + +```js +setTimeout(123, 100); // -> +// > 'called' +``` + +没問題。 + +```js +setTimeout('{a: 1}', 100); // -> +// > 'called' +``` + +這個也沒問題。 + +```js +setTimeout({a: 1}, 100); // -> +// > 'Uncaught SyntaxError: Unexpected identifier setTimeout (async) (anonymous) @ VM__:1' +// 未捕獲的語法錯誤:非預期的標識符 +``` + +拋出了一個 **SyntaxError**(語法錯誤)。 + +這種錯誤很容易發生,尤其是當你有個函數返回一個對象,但是你忘了將其傳進函數,直接就在這裡調用了!不過,如果 `content-policy` 設置為 `self` 會怎麼樣呢? + +```js +setTimeout(123, 100); // -> +// > console.error("[Report Only] Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'report-sample' 'self' ") +// [僅報告] 拒絕將字符串當作JavaScript求值,因為內容安全策略(CSP,Content Security Policy)指令被設置為 "script-src 'report-sample' 'self'",在該指令模式下不允許 'unsafe-eval' 的腳本源。 +``` + +終端會拒絕執行! + +### 💡 說明: + +`WindowOrWorkerGlobalScope.setTimeout()` 的第一個參數可以是代碼(`code`),代碼會被傳遞到 `eval` 函數,這是不好的。 `eval` 會把所有輸入強制轉換為字符串,然後進行求值,那麼對象會變成 `'[object Object]'`;嗯,你也看到了,這裡確實有一個非法標識符 `'Unexpected identifier'`。 + +- [eval()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval) on MDN (don't use this) +- [WindowOrWorkerGlobalScope.setTimeout()](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) on MDN +- [內容安全策略](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) +- W3C 上的 [計時器](https://www.w3.org/TR/2011/WD-html5-20110525/timers.html) + +## 點點運算符 + +現在嘗試把一個數字轉換到字符串: + +```js +27.toString() // > Uncaught SyntaxError: Invalid or unexpected token +// 未捕獲的語法錯誤:非法或非預期的詞元(token) +``` + +如果我們再加上一個點呢? + +```js +27..toString(); // -> '27' +``` + +那為什麼第一個例子錯了呢? + +### 💡 說明: + +這是文法的限制。 + +`.` 運算符存在歧義,它既可以當屬性訪問符,也可以是小數點,這取決於它在代碼中的位置。 + +規範中定義了 `.` 運算符僅在特定的位置使用時會被當作小數點,這個定義寫在 ECMAScript 的數字字面量語法一節中。 + +所以,當你想要在數字後加屬性訪問器的點號時,應當加上括號,或再加上一個點,以使該表達式合法。 + +```js +(27).toString(); // -> '27' +// or +27..toString(); // -> '27' +``` + +- [JavaScript 中 toString 的用法](https://stackoverflow.com/questions/6853865/usage-of-tostring-in-javascript/6853910#6853910) on StackOverflow +- [為什麼 10..toString() 可行,而 10.toString() 卻不行? ](https://stackoverflow.com/questions/13149282/why-does-10-tostring-work-but-10-tostring-does-not/13149301#13149301) + +## 再 new 一次 + +這僅僅是一個用於娛樂的例子。 + +```js +class Foo extends Function { + constructor(val) { + super(); + this.prototype.val = val; + } +} + +new new Foo(":D")().val; // -> ':D' +``` + +### 💡 說明: + +JavaScript 與其他面向對象語言不同,它的構造函數僅是一個比較特殊的函數。雖然 class 語法糖讓你可以創建一個字面上的類,但實例化後它就變成了函數,因此它可以再次實例化。 + +雖然我沒有測試過,但我覺得最後的那個表達式應該是這樣分析的: + +```js +new new Foo(":D")().val(new newFooInstance()).val; +veryNewFooInstance.val; +// -> ':D' +``` + +再補充一下,運行 `new Function('return "bar";')` 必然會創建一個內容為 `return "bar";` 的函數對象。而`Foo`類的構造函數中的 `super()` 調用的是 `Function` 的構造函數,所以自然而然我們可以在它上面添加更多的操作。 + +```js +class Foo extends Function { + constructor(val) { + super(` + this.val = arguments[0]; + `); + this.prototype.val = val; + } +} + +var foo = new new Foo(":D")("D:"); +foo.val; // -> 'D:' +delete foo.val; // 移除這個實例的“val”屬性,讓它退回(defer back)到他的原型的“val”屬性 +foo.val; // -> ':D' +``` + +- [擴展 Function 的類:再 new 一次](https://github.com/denysdovhan/wtfjs/issues/78) + +## 你應該用上分號 + +下面這個應該是標準的 JavaScript……吧?不,它炸了! + +```js +class SomeClass { + ["array"] = [] + ["string"] = "str" +} + +new SomeClass().array; // -> 'str' +``` + +woc……? + +### 💡 說明: + +嗯,你沒猜錯,這又是自動分號插入的功勞。 + +上面這個例子實際上會被轉換為: + +```js +class SomeClass { + ["array"] = ([]["string"] = "str"); +} +``` + +看到了吧,`str` 這個字符串被賦值到屬性 `array` 上。 + +- Ryan Cavanaugh 發布的 [關於這個例子的原創推特](https://twitter.com/SeaRyanC/status/1148726605222535168) +- [TC39 會議中關於它的討論](https://github.com/tc39/notes/blob/master/meetings/2017-09/sept-26.md) + +## 用空格分割(split)字符串 + +你試過用空格分割字符串嗎? + +```js +"".split(""); // -> [] +// 但是…… +"".split(" "); // -> [""] +``` + +### 💡 說明: + +這是預期行為。它會在輸入的字符串中遍歷,一旦發現分隔符,就在此處分割。但若你傳入的是空字符串,它找不到分隔符,因此返回該字符串。 + +規範引用如下: + +> 它會從左向右搜索字符串,並根據 `separator`(分隔符)決定子字符串的分割位置;分割位置的字符僅用於分割,不會包含在返回的數組中。 + +- [**22.1.3.21** String.prototype.split](https://tc39.es/ecma262/#sec-string.prototype.split) +- Ryan Cavanaugh 發布的 [關於這個例子的原創推特](https://twitter.com/SeaRyanC/status/1331656278104440833) +- Nabil Tharwat 發布的 [包含解釋的推特](https://twitter.com/kl13nt/status/1331742810932916227?s=20) + +## 對字符串 stringify + +這會導致一個缺陷,我曾經修了好幾天: + +```js +JSON.stringify("production") === "production"; // -> false +``` + +### 💡 說明: + +先看看 `JSON.stringify` 的返回值: + +```js +JSON.stringify("production"); // -> '"production"' +``` + +原来是被“字串化”了,所以這也難怪: + +```js +'"production"' === "production"; // -> false +``` + +- [ECMA-404 JSON 數據內部變動標準](https://www.json.org/json-en.html) + +## 對數字和 `true` 的非嚴格相等比較 + +```js +1 == true; // -> true +// 但是…… +Boolean(1.1); // -> true +1.1 == true; // -> false +``` + +### 💡 說明: + +根據規範: + +> 比較 x == y 時,當 x 和 y 都有值,會返回 true 或 false。比較過程如下所述: +> +> 4. 若 `Type(x)` 是數字且 `Type(y)` 是字符串,則會返回 `x == ! ToNumber(y)` 的結果。 + +所以比較過程是這樣的: + +```js +1 == true; +1 == Number(true); +1 == 1; // -> true +// 但是…… +1.1 == true; +1.1 == Number(true); +1.1 == 1; // -> false +``` + +- [**7.2.15** 抽象相等比較](https://262.ecma-international.org/11.0/index.html#sec-abstract-equality-comparison) + +# 其他資源 + +- [wtfjs.com](http://wtfjs.com/) — 一些非常特別的不規範與不一致的集合,以及對於 web 編程語言來說非常痛苦的時光。 +- [Wat](https://www.destroyallsoftware.com/talks/wat) — CodeMash 2012 中 Gary Bernhardt 的演講 +- [What the... JavaScript?](https://www.youtube.com/watch?v=2pL28CcEijU) — Kyle Simpsons 在 Forward 2 的演講,描述了“瘋狂的 JavaScript”。他希望幫助你寫出更乾淨、更優雅、更易讀的代碼,鼓勵人們為開源社區做出貢獻。 +- [Zeros in JavaScript](http://zero.milosz.ca/) — 針對 JavaScript 中的 `==`、`===`、`+` 和 `*` 的真值表。 + +# 🤝 捐贈支持 + +你好!這個項目是我在空閒時間做的,作為我的主要工作的補充。我希望你在閱讀這篇文章時保持愉快的心情。請考慮支持我 🙏。 + +每一次捐贈對我來說意義重大。你的捐贈是對我的工作的肯定:我的工作有價值。 + +**🙏 感謝您的支持! 🙏** + +| 服务 | 链接 | 动作 | +| ---------------- | :------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | +| **Patreon** | [Become a patron][patreon-url] | | +| **BuyMeACoffee** | [Buy me a cup of ☕️ or 🥤][bmc-url] | | +| **Bitcoin** | `1EJsKs6rPsqa7QLoVLpe3wgcdL9Q8WmDxE` | | +| **Ethereum** | `0x6aF39C917359897ae6969Ad682C14110afe1a0a1` | | + +> **⚠️ 提示:** 我现居乌克兰,乌克兰的银行账户没办法绑定 PayPal 或 Stripe 之类的账户。所以我没法开启 Github Sponsors、OpenCollective 和其他依赖于这些服务的捐赠渠道。对不起,目前您只能通过这些方式支持我。 + +# 🎓 許可證 + +[![CC 4.0][license-image]][license-url] + +© [Denys Dovhan](http://denysdovhan.com) + +[license-url]: http://www.wtfpl.net +[license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square +[npm-url]: https://npmjs.org/package/wtfjs +[npm-image]: https://img.shields.io/npm/v/wtfjs.svg?style=flat-square +[patreon-url]: https://patreon.com/denysdovhan +[patreon-image]: https://img.shields.io/badge/support-patreon-F96854.svg?style=flat-square +[bmc-url]: https://patreon.com/denysdovhan +[bmc-image]: https://img.shields.io/badge/support-buymeacoffee-222222.svg?style=flat-square From 93256c0426daafa70746d9b2a1180571a03f3181 Mon Sep 17 00:00:00 2001 From: Gzlegendns Date: Tue, 29 Nov 2022 10:31:22 +0800 Subject: [PATCH 2/3] update README-zh-tw.md --- README-zh-tw.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README-zh-tw.md b/README-zh-tw.md index 7425d44f..faf22d3d 100644 --- a/README-zh-tw.md +++ b/README-zh-tw.md @@ -31,7 +31,8 @@ $ npm install -g wtfjs 如今,**wtfjs** 已被翻譯成多種語言: -- [中文](./README-zh-cn.md) +- [简体中文](./README-zh-cn.md) +- [繁體中文](./README-zh-tw.md) - [हिंदी](./README-hi.md) - [Français](./README-fr-fr.md) - [Português do Brasil](./README-pt-br.md) From 11e1316c4d9a96ba89bfde62a25426099e6c52d1 Mon Sep 17 00:00:00 2001 From: Gzlegendns Date: Tue, 29 Nov 2022 10:34:59 +0800 Subject: [PATCH 3/3] =?UTF-8?q?update=20README-zh-tw.md=20add=20-=20[?= =?UTF-8?q?=E7=B9=81=E9=AB=94=E4=B8=AD=E6=96=87](./README-zh-tw.md)=20in?= =?UTF-8?q?=20line=2035?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-zh-tw.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README-zh-tw.md b/README-zh-tw.md index faf22d3d..2a643b44 100644 --- a/README-zh-tw.md +++ b/README-zh-tw.md @@ -32,7 +32,7 @@ $ npm install -g wtfjs 如今,**wtfjs** 已被翻譯成多種語言: - [简体中文](./README-zh-cn.md) -- [繁體中文](./README-zh-tw.md) +- [繁體中文](./README-zh-tw.md) - [हिंदी](./README-hi.md) - [Français](./README-fr-fr.md) - [Português do Brasil](./README-pt-br.md)