diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..490051876 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/.gitignore b/.gitignore index 6f90fd190..1a71fb7c8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ sftp-config.json Thumbs.db +/svgs \ No newline at end of file diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index fe15e24e8..93bf2eb1b 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -1,10 +1,18 @@ # 介紹 JavaScript +<<<<<<< HEAD 一起來看看 JavaScript 有什麼特別的,我們能做些什麼,與哪些配合不錯的技術。 +======= +Let's see what's so special about JavaScript, what we can achieve with it, and what other technologies play well with it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 什麼是 JavaScript? +<<<<<<< HEAD *JavaScript* 最初是為了 *"賦予網頁活力"* 而創造的。 +======= +*JavaScript* was initially created to "make web pages alive". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 這種程式語言我們稱之為 *腳本*,它們可以被寫入網頁 HTML 中,並在頁面讀取時自動執行。 @@ -24,26 +32,44 @@ 不同的引擎有不同的 "代號(codenames)"。例如: +<<<<<<< HEAD - [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- Chrome 和 Opera 內的引擎。 - [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- Firefox 內的引擎。 - ... 還有其他代號,像是不同版本 IE 使用的 "Trident" 與 "Chakra"、微軟 Edge 的 "ChakraCore"、Safari 的 "Nitro" 與 "SquirrelFish",等等。 上面提到的代號最好可以記住,因為這些代號常被用於網路上的開發者文章,就像我們一樣。如:"V8 支援某個 X 功能",代表該功能在 Chrome 和 Opera 上應該可以運作。 +======= +- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome, Opera and Edge. +- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- in Firefox. +- ...There are other codenames like "Chakra" for IE, "JavaScriptCore", "Nitro" and "SquirrelFish" for Safari, etc. + +The terms above are good to remember because they are used in developer articles on the internet. We'll use them too. For instance, if "a feature X is supported by V8", then it probably works in Chrome, Opera and Edge. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="引擎怎麼運作的?" 引擎很複雜,但概念很簡單。 +<<<<<<< HEAD 1. 引擎(瀏覽器內建)讀取("解析")腳本 2. 接著轉換("編譯")腳本為機器語言 3. 然後機器語言極快地執行 +======= +1. The engine (embedded if it's a browser) reads ("parses") the script. +2. Then it converts ("compiles") the script to machine code. +3. And then the machine code runs, pretty fast. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 引擎會對流程中每個階段進行優化。甚至會在執行時監看編譯好的腳本,分析其資料流,並以此再優化機器碼。 ``` ## 瀏覽器中的 JavaScript 可以做什麼? +<<<<<<< HEAD 現代化 JavaScript 是個 "安全" 的程式語言。它不提供對記憶體或 CPU 的低階存取,因為它原生是為了瀏覽器而建立,所以不需要。 +======= +Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or the CPU, because it was initially created for browsers which do not require it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 JavaScript 的能力很大一部分取決於執行它的環境。例如 [Node.js](https://wikipedia.org/wiki/Node.js) 提供 JavaScript 可以讀寫任意檔案與發送網路請求(network requests)等功能。 @@ -59,14 +85,23 @@ JavaScript 的能力很大一部分取決於執行它的環境。例如 [Node.js ## 瀏覽器中的 JavaScript **不能**做什麼? +<<<<<<< HEAD 為了使用者的資訊安全,JavaScript 在瀏覽器內的功能被限制。此為防範惡意網頁獲取私人資訊或損害使用者資料。 +======= +JavaScript's abilities in the browser are limited to protect the user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 這些限制範例如下: +<<<<<<< HEAD - 網頁上的 JavaScript 無法讀寫、複製和執行硬碟內任意檔案。它也沒有直接存取作業系統的功能。 +======= +- JavaScript on a webpage may not read/write arbitrary files on the hard disk, copy them or execute programs. It has no direct access to OS functions. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 現代化瀏覽器允許 JavaScript 有限制地操作檔案,且只有在使用者做特定動作時提供,像是:"拖曳" 一個檔案至瀏覽器視窗,或經由 `` 標籤選取。 +<<<<<<< HEAD JavaScript 有些方法可與相機、麥克風或其他裝置互動,但都需要使用者明確地授權。所以啟用 JavaScript 的網頁不會偷偷開啟相機觀察周遭並傳資料給 [美國國家安全局(NSA)](https://en.wikipedia.org/wiki/National_Security_Agency)。 - 不同的瀏覽器分頁/視窗基本上不知道彼此,但有時例外,例如:當一個視窗使用 JavaScript 開啟另一個視窗時。但就算如此,開啟不同網站(不同域名、通訊協定或埠)的頁面,其中的 JavaScript 亦無法溝通。 @@ -78,21 +113,44 @@ JavaScript 的能力很大一部分取決於執行它的環境。例如 [Node.js ![](limitations.svg) 在瀏覽器以外的 JavaScript 就沒有這些限制,如:伺服器上的 JavaScript。現代化瀏覽器也允許插件/擴充套件要求額外權限。 +======= + There are ways to interact with the camera/microphone and other devices, but they require a user's explicit permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the [NSA](https://en.wikipedia.org/wiki/National_Security_Agency). +- Different tabs/windows generally do not know about each other. Sometimes they do, for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other page if they come from different sites (from a different domain, protocol or port). + + This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and must contain special JavaScript code that handles it. We'll cover that in the tutorial. + + This limitation is, again, for the user's safety. A page from `http://anysite.com` which a user has opened must not be able to access another browser tab with the URL `http://gmail.com`, for example, and steal information from there. +- JavaScript can easily communicate over the net to the server where the current page came from. But its ability to receive data from other sites/domains is crippled. Though possible, it requires explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's a safety limitation. + +![](limitations.svg) + +Such limitations do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow plugins/extensions which may ask for extended permissions. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 是什麼讓 JavaScript 如此獨特? 至少有 *三項* JavaScript 很棒的事: ```compare +<<<<<<< HEAD + 與 HTML/CSS 完整整合 + 簡單的事能夠簡單地完成 + 所有主要瀏覽器支援且預設開啟 +======= ++ Full integration with HTML/CSS. ++ Simple things are done simply. ++ Supported by all major browsers and enabled by default. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 在瀏覽器技術中,只有 JavaScript 能唯一滿足這三項。 這造就 JavaScript 如此獨特。這也是為什麼它是建立瀏覽器介面最為廣泛的工具。 +<<<<<<< HEAD 此外,JavaScript 也可以建立伺服器和手機應用程式等等。 +======= +That said, JavaScript can be used to create servers, mobile applications, etc. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## JavaScript "之上" 的語言 @@ -100,18 +158,33 @@ JavaScript 的語法並不符合每個人的要求,不同人想要不同功能 這是預期中的,因為每個人的計畫和需求都不一樣。 +<<<<<<< HEAD 所以最近有大量新語言出現,它們在被瀏覽器執行前,都被 *轉譯*(transpiled)成 JavaScript。 +======= +So, recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run in the browser. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 現代化的工具讓轉譯迅速且透明,且實際上使開發者用另一種語言寫程式,再被自行轉換成 JavaScript。 例如這些語言: +<<<<<<< HEAD - [CoffeeScript](http://coffeescript.org/) 是組 JavaScript 的 "語法糖"。它有更簡短的語法,可以讓我們寫出更清楚且精確的程式碼,通常 Ruby 開發者愛用。 - [TypeScript](http://www.typescriptlang.org/) 致力於增加 "強型態確認(strict data typing)" 來簡化開發與支援複雜的系統,為微軟所開發。 - [Flow](http://flow.org/) 同樣增加型態確認但使用不同方式,為臉書所開發。 - [Dart](https://www.dartlang.org/) 是一種擁有自己引擎的獨立語言,它執行在非瀏覽器環境上(像是手機應用程式),但也可以轉譯成 JavaScript,為谷歌所開發。 還有更多其他語言。當然,就算我們使用某種轉譯式語言,我們應該也要了解 JavaScript 來真正知道我們在做什麼。 +======= +- [CoffeeScript](https://coffeescript.org/) is "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it. +- [TypeScript](https://www.typescriptlang.org/) is concentrated on adding "strict data typing" to simplify the development and support of complex systems. It is developed by Microsoft. +- [Flow](https://flow.org/) also adds data typing, but in a different way. Developed by Facebook. +- [Dart](https://www.dartlang.org/) is a standalone language that has its own engine that runs in non-browser environments (like mobile apps), but also can be transpiled to JavaScript. Developed by Google. +- [Brython](https://brython.info/) is a Python transpiler to JavaScript that enables the writing of applications in pure Python without JavaScript. +- [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) is a modern, concise and safe programming language that can target the browser or Node. + +There are more. Of course, even if we use one of these transpiled languages, we should also know JavaScript to really understand what we're doing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 總結 @@ -119,3 +192,9 @@ JavaScript 的語法並不符合每個人的要求,不同人想要不同功能 - 至今 JavaScript 有著獨特的地位,它是在瀏覽器上最被廣泛採用的語言且與 HTML/CSS 完整整合。 - 有許多語言可被 "轉譯" 成 JavaScript 並提供特定的功能。建議在掌握 JavaScript 後可以稍微看看。 +<<<<<<< HEAD +======= +- JavaScript was initially created as a browser-only language, but it is now used in many other environments as well. +- Today, JavaScript has a unique position as the most widely-adopted browser language, fully integrated with HTML/CSS. +- There are many languages that get "transpiled" to JavaScript and provide certain features. It is recommended to take a look at them, at least briefly, after mastering JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md index e3ff0d495..cda5a783c 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -2,7 +2,11 @@ 這本書是個 *教程*,主要是幫助你漸漸地學會這門語言,一旦你熟悉了基礎,你將會需要更多其他的資源。 +<<<<<<< HEAD ## 規格書 +======= +This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other resources. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [The ECMA-262 specification](https://www.ecma-international.org/publications/standards/Ecma-262.htm) 規格書包含了 JavaScript 最深入、詳細以及形式化的資訊。它定義了這門語言。 @@ -10,14 +14,23 @@ 每年都會有新規格版本釋出,這些釋出版本之間的最新規格草案在這 。 +<<<<<<< HEAD 如果你想知道最尖端的功能,包含那些 "即將成為標準"(所謂 "stage 3")的功能,可以看一下 。 +======= +A new specification version is released every year. Between these releases, the latest specification draft is at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 同樣地,假如你在開發瀏覽器相關的功能,我們也有在此教程的 [第二部分](info:browser-environment) 介紹相關的規格書。 +<<<<<<< HEAD ## 操作手冊 +======= +Also, if you're developing for the browser, then there are other specifications covered in the [second part](info:browser-environment) of the tutorial. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - **MDN (Mozilla) JavaScript Reference** 是一個包含了範例以及其他資訊的操作手冊,很適合拿來獲取個別函式或是方法的深入資訊。 +<<<<<<< HEAD 你可以在這裡找到 。 不過,透過網路搜尋通常會是較好的選擇。使用在搜尋列打入 "MDN [關鍵字]" 的方式,比如你要搜尋 `parseInt` 這個函式,可以用 。 @@ -29,6 +42,13 @@ ## 兼容表 JavaScript 是一個持續開發中的語言,它定期會被加入一些新功能。 +======= +- **MDN (Mozilla) JavaScript Reference** is the main manual with examples and other information. It's great to get in-depth information about individual language functions, methods etc. + + You can find it at . + +Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for the `parseInt` function. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 想知道瀏覽器或是其他引擎的支援程度,可以參考: @@ -37,5 +57,14 @@ JavaScript 是一個持續開發中的語言,它定期會被加入一些新功 所有這些資訊對於實務開發都是很有用的,因為他們包含了非常有價值的資訊,比如支援程度跟語言的細節等。 +<<<<<<< HEAD 當你需要比較深入的資訊或是需要了解一些特定的功能時,請記得使用這些資訊(或是此頁)。 +======= +- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: . +- - a table with language features and engines that support those or don't support. + +All these resources are useful in real-life development, as they contain valuable information about language details, their support, etc. + +Please remember them (or this page) for the cases when you need in-depth information about a particular feature. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index 5c0a2352c..13f82bb2c 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -12,8 +12,13 @@ 如果你還沒選擇使用一個 IDE,可以考慮以下選擇: +<<<<<<< HEAD - [Visual Studio Code](https://code.visualstudio.com/)(跨平台,免費)。 - [WebStorm](http://www.jetbrains.com/webstorm/)(跨平台,付費)。 +======= +- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). +- [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, paid). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 若使用 Windows 系統,也可選用 "Visual Studio",但別跟 "Visual Studio Code" 搞混了。"Visual Studio" 是一個需付費且強大的 Windows 專屬編輯器,特別適合開發 .NET 平台,用來開發 JavaScript 也不錯。它有個免費的版本 [Visual Studio Community](https://www.visualstudio.com/vs/community/)。 @@ -29,6 +34,7 @@ 實際上,輕量化編輯器可能有各式各樣的插件,包含目錄語法層級分析與自動程式補完,所以兩者間並沒有明顯的界線。 +<<<<<<< HEAD 以下是一些值得考慮的選擇: - [Atom](https://atom.io/)(跨平台,免費)。 @@ -36,6 +42,13 @@ - [Sublime Text](http://www.sublimetext.com)(跨平台,共享軟體)。 - [Notepad++](https://notepad-plus-plus.org/)(Windows,免費)。 - [Vim](http://www.vim.org/) 和 [Emacs](https://www.gnu.org/software/emacs/) 也很不錯,前提是你要知道怎麼使用。 +======= +There are many options, for instance: + +- [Sublime Text](https://www.sublimetext.com/) (cross-platform, shareware). +- [Notepad++](https://notepad-plus-plus.org/) (Windows, free). +- [Vim](https://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 不要起爭議 @@ -45,3 +58,12 @@ 選擇編輯器就像選其他工具一樣,需要依照你的專案、習慣和個人喜好選擇。 +<<<<<<< HEAD +======= +The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. + +The author's personal opinion: + +- I'd use [Visual Studio Code](https://code.visualstudio.com/) if I develop mostly frontend. +- Otherwise, if it's mostly another language/platform and partially frontend, then consider other editors, such as XCode (Mac), Visual Studio (Windows) or Jetbrains family (Webstorm, PHPStorm, RubyMine etc, depending on the language). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index 9165d8608..e70dff042 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -29,11 +29,23 @@ - 在這裡我們可以看到紅色標註的錯誤訊息,這個例子中,腳本內有一個未知的 "lalala" 指令。 - 在右邊有一個可以點擊連至原始碼的連結 `bug.html:12`,伴隨著產生錯誤的程式行數。 +<<<<<<< HEAD 錯誤訊息底下,有一個藍色 `>` 符號,它代表著我們可以輸入 JavaScript 的 "命令行"。按下 `key:Enter` 來執行它們(用 `key:Shift+Enter` 來輸入多行指令)。 +======= +Below the error message, there is a blue `>` symbol. It marks a "command line" where we can type JavaScript commands. Press `key:Enter` to run them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 現在我們可以看到錯誤就夠了,晚點會於章節 再回到開發者工具並深入探討除錯這件事。 +<<<<<<< HEAD ## Firefox、Edge 與其他 +======= +```smart header="Multi-line input" +Usually, when we put a line of code into the console, and then press `key:Enter`, it executes. + +To insert multiple lines, press `key:Shift+Enter`. This way one can enter long fragments of JavaScript code. +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 多數其他瀏覽器使用 `key:F12` 打開開發者工具。 @@ -49,6 +61,7 @@ Safari(Mac 瀏覽器,Windows/Linux 不支援)有點不太一樣,我們 現在 `key:Cmd+Opt+C` 可以開啟控制台。同樣地,注意最上方出現一個新的選單名為 "開發(Develop)",有著許多指令與選項。 +<<<<<<< HEAD ```smart header="輸入多行" 通常當我們輸入一行程式碼到控制台並按下 `key:Enter`,它就執行了。 @@ -56,6 +69,9 @@ Safari(Mac 瀏覽器,Windows/Linux 不支援)有點不太一樣,我們 ``` ## 總結 +======= +## Summary +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 開發者工具允許我們看到錯誤、執行指令、查看變數(variables)與更多其它事。 - 大多數 Windows 的瀏覽器可以經由按下 `key:F12` 開啟。Mac 上的 Chrome 需要 `key:Cmd+Opt+J`;Safari 要用 `key:Cmd+Opt+C`(需先啟用)。 diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md index 12f269b51..9527b57e1 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -9,7 +9,11 @@ ## "script" 標籤 +<<<<<<< HEAD JavaScript 程式可以使用 ` ``` +<<<<<<< HEAD 這邊的 `/path/to/script.js` 代表從網站根目錄開始,腳本檔案的絕對路徑,我們也可以提供相對於當前頁面的相對路徑。舉例來說,`src="script.js"` 指的是目前資料夾中的一個 `"script.js"` 檔案。 +======= +Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="script.js"`, just like `src="./script.js"`, would mean a file `"script.js"` in the current folder. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 我們也可以提供完整的 URL,例如: ```html - + ``` 如果要添加多個腳本,請使用多個標籤: diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 06b6f72ef..60554c9a8 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -46,7 +46,11 @@ alert(3 + + 2); ``` +<<<<<<< HEAD 此程式碼輸出 `6`,因為 JavaScript 沒有在內插入分號。直觀上,當某行程式以 `"+"` 結束時,它是個 "不完整的表達式(incomplete expression)" 而不需要分號,如此一來這個例子才會以我們所想像的方式運作。 +======= +The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so a semicolon there would be incorrect. And in this case, that works as intended. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **但有些情況下 JavaScript 對於分號是否真的被需要會假設 "失敗"** @@ -56,44 +60,71 @@ alert(3 + 如果你想看個這種錯誤的例子,來看這段程式碼: ```js run -[1, 2].forEach(alert) +alert("Hello"); + +[1, 2].forEach(alert); ``` +<<<<<<< HEAD 先不用思考方括號 `[]` 跟 `forEach` 的意義,我們晚點會介紹。現在只要記得這段程式碼的執行結果:先顯示 `1` 接著是 `2`。 現在來加入一個 `alert` 在這段程式碼之前且 *不要* 以分號做結。 +======= +No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of running the code: it shows `Hello`, then `1`, then `2`. + +Now let's remove the semicolon after the `alert`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify -alert("There will be an error") +alert("Hello") -[1, 2].forEach(alert) +[1, 2].forEach(alert); ``` +<<<<<<< HEAD 若我們執行這段程式碼,只有第一個 `alert` 會被顯示出來並有錯誤產生! 但若我們在 `alert` 之後加入分號,一切又恢復正常: ```js run alert("All fine now"); +======= +The difference compared to the code above is only one character: the semicolon at the end of the first line is gone. -[1, 2].forEach(alert) -``` +If we run this code, only the first `Hello` shows (and there's an error, you may need to open the console to see it). There are no numbers any more. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +That's because JavaScript does not assume a semicolon before square brackets `[...]`. So, the code in the last example is treated as a single statement. + +<<<<<<< HEAD 現在我們有 "All fine now" 的訊息並伴隨著 `1` 和 `2` 顯示。 在沒有分號時會有錯誤,是因為 JavaScript 不假設方括號 `[...]` 之前要有分號。 因為分號沒有被自動插入,所以第一個例子內的程式碼被視為單獨一行述句。這是引擎是怎麼看它的樣子: +======= +Here's how the engine sees it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run no-beautify -alert("There will be an error")[1, 2].forEach(alert) +alert("Hello")[1, 2].forEach(alert); ``` +<<<<<<< HEAD 但程式應該要有兩個分開的述句而非單獨一個,本例中的合併是錯誤的所以導致錯誤,這在很多其他情況下也可能發生。 +======= +Looks weird, right? Such merging in this case is just wrong. We need to put a semicolon after `alert` for the code to work correctly. + +This can happen in other situations also. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` 我們建議即使是依據換行分開的述句也要標上分號,這個規則被社群廣為採納。再次強調 -- 大多時間 *可能* 可以省略分號,但加上分號會更安全,尤其對新手而言。 +<<<<<<< HEAD ## 註解 [#code-comments] +======= +## Comments [#code-comments] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 程式碼會隨著時間變得越來越複雜,有其必要加入 *註解* 來解釋程式在做什麼且為什麼這麼做。 @@ -134,8 +165,13 @@ alert('Hello'); alert('World'); ``` +<<<<<<< HEAD ```smart header="用熱鍵!" 大多數編輯器中,可以按下 `key:Ctrl+/` 這個熱鍵來註解掉單行程式碼,而 `key:Ctrl+Shift+/` 可以註解多行(選取一段程式碼後按下熱鍵)。Mac 則使用 `key:Cmd` 取代 `key:Ctrl`;`key:Option` 取代 `key:Shift`。 +======= +```smart header="Use hotkeys!" +In most editors, a line of code can be commented out by pressing the `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac, try `key:Cmd` instead of `key:Ctrl` and `key:Option` instead of `key:Shift`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ````warn header="不支援巢狀註解!" diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index 05ad2f77b..9ba313f3d 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -19,10 +19,14 @@ ... ``` +<<<<<<< HEAD 我們很快會學到函式(functions)(一種組合命令的方式),所以先說一下,注意 `"use strict"` 可以被放在函式本體的最前面,而不用是整個腳本的最前方。這麼做可以使嚴格模式(strict mode)只作用在函式之中,但通常大家都會用於整個腳本上。 ````warn header="確保 \"use strict\" 至於最頂端" 請確保 `"use strict"` 至於你腳本的最頂端,否則嚴格模式可能不會被啟用。 +======= +Quite soon we're going to learn functions (a way to group commands), so let's note in advance that `"use strict"` can be put at the beginning of a function. Doing that enables strict mode in that function only. But usually people use it for the whole script. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 這裡嚴格模式就沒有被啟用: @@ -41,16 +45,30 @@ alert("some code"); ```warn header="沒有取消 `use strict` 的方式" 沒有像是 `"no use strict"` 之類使得引擎回復舊行為的指令 +<<<<<<< HEAD 一旦我們開啟嚴格模式,就不能回頭。 +======= +Once we enter strict mode, there's no going back. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 瀏覽器主控台 +<<<<<<< HEAD 未來當你使用瀏覽器主控台來測試功能時,請注意它並沒有預設開啟 `use strict`。 +======= +When you use a [developer console](info:devtools) to run code, please note that it doesn't `use strict` by default. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 有時候 `use strict` 造成的不同會讓你得到不正確的結果。 +<<<<<<< HEAD 你可以試著按下 `key:Shift+Enter` 來輸入多行,然後把 `use strict` 放在在最上方,像這樣: +======= +So, how to actually `use strict` in the console? + +First, you can try to press `key:Shift+Enter` to input multiple lines, and put `use strict` on top, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js 'use strict'; @@ -60,12 +78,17 @@ alert("some code"); 這在大多數瀏覽器,Firefox 和 Chrome,都可以運作。 +<<<<<<< HEAD 如果不行,開啟 `use strict` 最有效的方式是輸入像下面這樣的程式碼到主控台內: +======= +If it doesn't, e.g. in an old browser, there's an ugly, but reliable way to ensure `use strict`. Put it inside this kind of wrapper: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js (function() { 'use strict'; +<<<<<<< HEAD // ...你的程式碼... })() ``` @@ -82,3 +105,24 @@ alert("some code"); 2. 嚴格模式要在腳本或函式的最頂端放置 `"use strict"` 才會被開啟。有些語言功能,像是 "類別(classes)" 和 "模組(modules)" 會自動開啟嚴格模式。 3. 所有現代化瀏覽器都支援嚴格模式。 4. 我們建議腳本最好總是以 `"use strict"` 起始。除了非常少的指定情境以外,本教程中所有範例都預設使用嚴格模式。 +======= + // ...your code here... +})() +``` + +## Should we "use strict"? + +The question may sound obvious, but it's not so. + +One could recommend to start scripts with `"use strict"`... But you know what's cool? + +Modern JavaScript supports "classes" and "modules" - advanced language structures (we'll surely get to them), that enable `use strict` automatically. So we don't need to add the `"use strict"` directive, if we use them. + +**So, for now `"use strict";` is a welcome guest at the top of your scripts. Later, when your code is all in classes and modules, you may omit it.** + +As of now, we've got to know about `use strict` in general. + +In the next chapters, as we learn language features, we'll see the differences between the strict and old modes. Luckily, there aren't many and they actually make our lives better. + +All examples in this tutorial assume strict mode unless (very rarely) specified otherwise. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md index d51fb4a18..a466a9321 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md @@ -6,7 +6,11 @@ let ourPlanetName = "Earth"; ``` +<<<<<<< HEAD 注意我們也可用更短的名字 `planet`,但它可能不夠清楚說明代表是哪個星球。有著更多細節是好的,至少在變數 `沒變得太長` 之前。 +======= +Note, we could use a shorter name `planet`, but it might not be obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 現在使用者的名字 diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index 5aecfee62..ae7376f17 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -12,13 +12,24 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` +<<<<<<< HEAD 在這裡我們有個日期常數 `birthday`,和經由 `birthday` 與其他程式碼(為了保持簡化這邊不提供,因為也不重要)計算出的 `age` 常數。 +======= +Here we have a constant `birthday` for the date, and also the `age` constant. + +The `age` is calculated from `birthday` using `someCode()`, which means a function call that we didn't explain yet (we will soon!), but the details don't matter here, the point is that `age` is calculated somehow based on the `birthday`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 對於 `birthday` 使用大寫命名是正確的嗎?對於 `age` 呢?或者乾脆兩者都用? ```js +<<<<<<< HEAD const BIRTHDAY = '18.04.1982'; // 用大寫命名? const AGE = someCode(BIRTHDAY); // 用大寫命名? -``` +======= +const BIRTHDAY = '18.04.1982'; // make birthday uppercase? +const AGE = someCode(BIRTHDAY); // make age uppercase? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +``` diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index ea5009189..917ce0904 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -24,7 +24,11 @@ let message; let message; *!* +<<<<<<< HEAD message = 'Hello'; // 儲存該字串 +======= +message = 'Hello'; // store the string 'Hello' in the variable named message +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` @@ -63,7 +67,11 @@ let age = 25; let message = 'Hello'; ``` +<<<<<<< HEAD 有些人也會這樣子來宣告多行變數: +======= +Some people also define multiple variables in this multiline style: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify let user = 'John', @@ -81,23 +89,38 @@ let user = 'John' 技術上來說,這些變化在都做一樣的事,所以這只是個人的喜好與美學而已。 +<<<<<<< HEAD ````smart header="用 `var` 而非 `let`" 在一些較舊的腳本中,你也許會發現另一個關鍵字:`var` 而非 `let`: +======= +````smart header="`var` instead of `let`" +In older scripts, you may also find another keyword: `var` instead of `let`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js *!*var*/!* message = 'Hello'; ``` +<<<<<<< HEAD `var` 關鍵字 *幾乎* 等同於 `let`,它一樣宣告變數,但有一點點的那麼 "老派" 的差異。 `let` 和 `var` 之間有著微妙的差異,但現在對我們來說還沒什麼,我們將會在 這個章節內涵蓋這些細節。 +======= +The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way. + +There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 一個現實世界的比喻 若把它想像成一個上面貼有獨特命名貼紙的資料 "盒",我們可以更簡單地掌握 "變數" 這個概念。 +<<<<<<< HEAD 例如,變數 `message` 可以被想像成一個被標註上 `"message"` 的盒子,裡面裝著 `"Hello!"` 的值: +======= +For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](variable.svg) @@ -105,6 +128,11 @@ let user = 'John' 我們也可以隨意改變放入的值: +<<<<<<< HEAD +======= +We can also change it as many times as we want: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let message; @@ -136,12 +164,35 @@ alert(hello); // Hello world! alert(message); // Hello world! ``` +<<<<<<< HEAD ```smart header="函數式語言(Functional languages)" 值得注意的是,有種叫 [函式編程(functional programming)](https://en.wikipedia.org/wiki/Functional_programming) 的語言,像是 [Scala](http://www.scala-lang.org/) 或 [Erlang](http://www.erlang.org/),禁止更改變數值。 +======= +````warn header="Declaring twice triggers an error" +A variable should be declared only once. + +A repeated declaration of the same variable is an error: + +```js run +let message = "This"; + +// repeated 'let' leads to an error +let message = "That"; // SyntaxError: 'message' has already been declared +``` +So, we should declare a variable once and then refer to it without `let`. +```` + +```smart header="Functional languages" +It's interesting to note that there exist so-called [pure functional](https://en.wikipedia.org/wiki/Purely_functional_programming) programming languages, such as [Haskell](https://en.wikipedia.org/wiki/Haskell), that forbid changing variable values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 在這些語言中,一旦值被 "裝箱" 就永遠在那了,如果我們需要存到別的地方,這種語言強制我們建立一個新的箱子(宣告一個新變數),我們不能再使用舊的。 +<<<<<<< HEAD 雖然第一次看起來有點怪,但這些語言更有足夠能力勝任正規的開發,甚至在像是平行計算的領域內,有這些限制反而更好。建議可以研讀一門這樣的語言(即使你近期還沒打算開始用),有助於增廣見聞。 +======= +Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 變數命名 [#variable-naming] @@ -179,19 +230,32 @@ let 1a; // 不能以數字開頭 let my-name; // 連字號 '-' 不能用於命名 ``` +<<<<<<< HEAD ```smart header="大小寫有差" 用 `apple` 和 `AppLE` 命名的變數是不同的。 ``` ````smart header="可以使用非拉丁字母,但並不建議" 可以使用任意語言,包括西里爾(cyrillic)字母甚至象形文字,像這樣: +======= +```smart header="Case matters" +Variables named `apple` and `APPLE` are two different variables. +``` + +````smart header="Non-Latin letters are allowed, but not recommended" +It is possible to use any language, including Cyrillic letters, Chinese logograms and so on, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let имя = '...'; let 我 = '...'; ``` +<<<<<<< HEAD 技術上這樣不會有問題,這些名稱都是被允許的,但國際傳統是使用英文來命名。就算我們只是寫個很小的腳本,它可能也會存活很久,到時其他國家的人也許會需要閱讀它。 +======= +Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ````warn header="保留字(Reserved names)" @@ -244,13 +308,21 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // 錯誤,不能對常數重新賦值! ``` +<<<<<<< HEAD 當程式設計師確認一個變數將不會再被更動,就可以用 `const` 宣告它以確保並清楚地告知他人這件事。 ### 大寫常數 +======= +When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 有個普遍的做法是,使用常數作為那些執行程式前就已知有夠難記的值的別名。 +<<<<<<< HEAD 這些常數使用大寫字母與底線來命名。 +======= +There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 例如,使用常數來代表那些被稱為 "web"(十六進位)格式的顏色: @@ -273,16 +345,29 @@ alert(color); // #FF7F00 什麼時候我們該使用大寫命名常數,什麼時候正常命名就好?讓我們弄更清楚吧。 +<<<<<<< HEAD 作為一個 "常數" 就代表變數的值永不再變動,但有些常數是早在執行程式前就已知的(像代表紅色的十六進位數值),而有些要在執行時期才內被 *計算* 出來,但在賦予值之後就不會被更動。 例如: +======= +Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment. + +For instance: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js const pageLoadTime = /* 載入網頁時間 */; ``` +<<<<<<< HEAD `pageLoadTime` 的值在載入頁面之前是未知的,所以用正常命名就好。但它依然是一個常數,因為被給定值後就不會再變動。 換句話說,大寫命名的常數只作為那些 "被寫死" 的值的別名。 +======= +The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment. + +In other words, capital-named constants are only used as aliases for "hard-coded" values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 正確命名變數 @@ -290,18 +375,31 @@ const pageLoadTime = /* 載入網頁時間 */; 一個變數名稱應該要有著清楚且明確的含義,用以描述儲存在內的資料。 +<<<<<<< HEAD 變數名稱是學習寫程式的過程中,最重要且困難的技巧之一。一眼掃過變數名稱就可以區分出程式碼是新手還是有經驗的老手寫的。 在真實專案中,大部分時間都花在修改且擴展一套現存的程式碼,而非從頭開始寫完全不相干的東西。當我們做了些其他事情後再回來看某段程式碼時,被良好標示過的資訊會是更易於閱讀的,也就是變數有被很好地命名時。 +======= +Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer. + +In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 請在宣告變數前花點時間思考一個變數的正確名字,這麼做將會有很好地回報。 一些最好要遵守的規則是: +<<<<<<< HEAD - 使用人類易讀的名字,像是 `userName` 或 `shoppingCart`。 - 避免使用縮寫或短名稱,像是 `a`、`b` 和 `c`,除非你真的知道你在做什麼。 - 讓名字盡量具有描述性且精簡。`data` 和 `value` 是不良的例子,這些名字沒有含義,只有在程式碼上下文可以特別清楚知道 `data` 或 `value` 是從哪來時才會較適合使用。 - 腦中的字彙應該要與團隊保持一致,若一個網站訪客稱為 "user" 那我們就該命名相關的變數為 `currentUser` 或 `newUser` 而非 `currentVisitor` 或 `newManInTown`。 +======= +- Use human-readable names like `userName` or `shoppingCart`. +- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing. +- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing. +- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 聽起來很簡單?用聽的當然簡單,但要實際建立一個具描述性且精簡的變數名字就沒那麼簡單,試試看吧。 diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 35f7cd842..dfe865e3a 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -1,6 +1,14 @@ # 資料類型 +<<<<<<< HEAD JavaScript 中的變數可包含任意資料,一個變數可以在某時間點是字串然後在另一個時間點是數值: +======= +A value in JavaScript is always of a certain type. For example, a string or a number. + +There are eight basic data types in JavaScript. Here, we'll cover them in general and in the next chapters we'll talk about each of them in detail. + +We can put any type in a variable. For example, a variable can at one moment be a string and then store a number: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // 沒有出錯 @@ -8,11 +16,17 @@ let message = "hello"; message = 123456; ``` +<<<<<<< HEAD 若允許這麼做的程式語言,稱其具有 "動態類型(dynamically typed)",意思是變數不會綁定任一種資料類型。 JavaScript 中有八種基礎的資料類型,在此我們會稍微介紹下它們,然後在接下來的章節中我們會逐一介紹其細節: ## 數值(A number) +======= +Programming languages that allow such things, such as JavaScript, are called "dynamically typed", meaning that there exist data types, but variables are not bound to any of them. + +## Number +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let n = 123; @@ -45,13 +59,23 @@ n = 12.345; alert( "not a number" / 2 ); // NaN,這種除法是錯誤的 ``` +<<<<<<< HEAD `NaN` 具有黏性。任何對 `NaN` 的進一步運算都會回傳 `NaN`: +======= + `NaN` is sticky. Any further mathematical operation on `NaN` returns `NaN`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run - alert( "not a number" / 2 + 5 ); // NaN + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "not a number" / 2 - 1 ); // NaN ``` +<<<<<<< HEAD 所以如果數學表示式中有個 `NaN`,就會傳播至整個運算結果上。 +======= + So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result (there's only one exception to that: `NaN ** 0` is `1`). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="數學運算很安全" 操作數學運算在 JavaScript 是 "安全的"。我們可以做任何事情:除以零、把不是數值的字串當作數值等等。 @@ -63,7 +87,39 @@ n = 12.345; 我們會在章節 中看到更多使用數值的方式。 +<<<<<<< HEAD ## BigInt 類型 +======= +## BigInt [#bigint-type] + +In JavaScript, the "number" type cannot safely represent integer values larger than (253-1) (that's `9007199254740991`), or less than -(253-1) for negatives. + +To be really precise, the "number" type can store larger integers (up to 1.7976931348623157 * 10308), but outside of the safe integer range ±(253-1) there'll be a precision error, because not all digits fit into the fixed 64-bit storage. So an "approximate" value may be stored. + +For example, these two numbers (right above the safe range) are the same: + +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 +``` + +So to say, all odd integers greater than (253-1) can't be stored at all in the "number" type. + +For most purposes ±(253-1) range is quite enough, but sometimes we need the entire range of really big integers, e.g. for cryptography or microsecond-precision timestamps. + +`BigInt` type was recently added to the language to represent integers of arbitrary length. + +A `BigInt` value is created by appending `n` to the end of an integer: + +```js +// the "n" at the end means it's a BigInt +const bigInt = 1234567890123456789012345678901234567890n; +``` + +As `BigInt` numbers are rarely needed, we don't cover them here, but devoted them a separate chapter . Read it when you need such big numbers. + +## String +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 在 JavaScript 中,"number" 類型沒辦法代表大於 253 (或小於 -253 )的整數,這是其內部表示系統上的技術限制。這大約是 16 位十進位數字,在大多數的情況下,這個限制都不是問題,但有時我們真的需要很大的數字,例如用於密碼學或是精準度到 microsecond 的時間戳記。 @@ -88,8 +144,13 @@ JavaScript 中的字串(string)必須被引號(quotes)所環繞。 ```js let str = "Hello"; +<<<<<<< HEAD let str2 = 'Single quotes are ok too(單引號也可以)'; let phrase = `can embed ${str}(字串可被 \${內嵌})`; +======= +let str2 = 'Single quotes are ok too'; +let phrase = `can embed another ${str}`; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 在 JavaScript 中,有三種引號。 @@ -98,7 +159,11 @@ let phrase = `can embed ${str}(字串可被 \${內嵌})`; 2. 單引號:`'Hello'`。 3. 反引號:`Hello`。 +<<<<<<< HEAD 單雙引號都是 "簡易的" 引號,在 JavaScript 中它們沒有差異。 +======= +Double and single quotes are "simple" quotes. There's practically no difference between them in JavaScript. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 反引號是 "功能擴展" 引號,讓我們可以在字串中利用 `${...}` 嵌入變數或是表達式,例如: @@ -121,6 +186,7 @@ alert( "the result is ${1 + 2}" ); // the result is ${1 + 2}(雙引號什麼 我們會在章節 中介紹更多細節。 +<<<<<<< HEAD ```smart header="不存在 *字元(character)* 類型" 在一些語言中,單一字元有個特殊的 "字元" 類型,像是在 C 語言或是 Java 中的 `char`。 @@ -128,6 +194,15 @@ alert( "the result is ${1 + 2}" ); // the result is ${1 + 2}(雙引號什麼 ``` ## 布林(Boolean)(邏輯類型) +======= +```smart header="There is no *character* type." +In some languages, there is a special "character" type for a single character. For example, in the C language and in Java it is called "char". + +In JavaScript, there is no such type. There's only one type: `string`. A string may consist of zero characters (be empty), one character or many of them. +``` + +## Boolean (logical type) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 布林(boolean)類型只有兩種值:`true` 和 `false`。 @@ -164,7 +239,11 @@ let age = null; 它就只是個代表著 "沒東西"、"空白" 或 "未知值" 的特殊值。 +<<<<<<< HEAD 上面那段程式碼中的 `age`,就表示因為某些因素而未知或空白。 +======= +The code above states that `age` is unknown. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## "undefined" 值 @@ -175,33 +254,54 @@ let age = null; 如果一個變數被宣告了但還沒被賦予值,那它的值就是 `undefined`: ```js run -let x; +let age; +<<<<<<< HEAD alert(x); // 顯示 "undefined" ``` 技術上來說,可以把 `undefined` 指定給任何變數: +======= +alert(age); // shows "undefined" +``` + +Technically, it is possible to explicitly assign `undefined` to a variable: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run -let x = 123; +let age = 100; -x = undefined; +// change the value to undefined +age = undefined; -alert(x); // "undefined" +alert(age); // "undefined" ``` +<<<<<<< HEAD ...但我們不建議這麼做。通常我們使用 `null` 來給一個變數 "空白" 或是 "未知" 的值,然後使用 `undefined` 來查看變數是否有被給過值。 +======= +...But we don't recommend doing that. Normally, one uses `null` to assign an "empty" or "unknown" value to a variable, while `undefined` is reserved as a default initial value for unassigned things. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 物件(Objects)和符號(Symbols) `物件(object)` 這個類型很特殊。 +<<<<<<< HEAD 其它類型都稱為 "原生(primitive)" 類型,因為它們的值只可包含一種東西(一個字串或一個數值或一個其它東西),相對地,物件被用來儲存資料群集和更複雜的東西。再更充分了解原生類型後,我們晚點會在 這個章節中介紹它。 `符號(symbol)` 類型被用在為物件建立一個獨特的識別符(identifiers),為了完整性我們在此提到它,但會在介紹完物件後再來學習。 +======= +All other types are called "primitive" because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities. + +Being that important, objects deserve a special treatment. We'll deal with them later in the chapter , after we learn more about primitives. + +The `symbol` type is used to create unique identifiers for objects. We have to mention it here for the sake of completeness, but also postpone the details till we know objects. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## typeof 運算子(typeof operator)[#type-typeof] +<<<<<<< HEAD `typeof` 運算子會回傳其引數(argument)的類型。這在當我們想要分開處理不同類型的值,或只是想要快速檢查類型時,就會很有用。 它支援兩種語法格式: @@ -212,6 +312,11 @@ alert(x); // "undefined" 換句話說,有沒有括號都可以正常運作,結果也都一樣。 呼叫 `typeof x` 會回傳一個該類型名稱的字串: +======= +The `typeof` operator returns the type of the operand. It's useful when we want to process values of different types differently or just want to do a quick check. + +A call to `typeof x` returns a string with the type name: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js typeof undefined // "undefined" @@ -241,13 +346,29 @@ typeof alert // "function"(3) 最後三行可能需要額外解釋: +<<<<<<< HEAD 1. `Math` 是個用來提供數學運算的內建物件,我們會在 這個章節中介紹它,在這裡只是把它視為一個物件的範例。 2. `typeof null` 的結果是 `object`,這是錯誤的。這是個官方承認在 `typeof` 中的錯誤,保留它只是為了兼容性。`null` 當然不是一個物件,它是個有著自己類型的特殊值。再次強調,這是語言中的一個錯誤。 3. `typeof alert` 的結果是 `function`,因為 `alert` 是個函式。我們會在接下來的章節中學到函式,並了解 JavaScript 中沒有 "function" 這個特殊的類型。函式屬於物件類型的一種,但 `typeof` 視它們為不同兩者並回傳 `function`。這不那麼正確,但在實作中卻很方便。 +======= +1. `Math` is a built-in object that provides mathematical operations. We will learn it in the chapter . Here, it serves just as an example of an object. +2. The result of `typeof null` is `"object"`. That's an officially recognized error in `typeof`, coming from very early days of JavaScript and kept for compatibility. Definitely, `null` is not an object. It is a special value with a separate type of its own. The behavior of `typeof` is wrong here. +3. The result of `typeof alert` is `"function"`, because `alert` is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently, returning `"function"`. That also comes from the early days of JavaScript. Technically, such behavior isn't correct, but can be convenient in practice. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +```smart header="The `typeof(x)` syntax" +You may also come across another syntax: `typeof(x)`. It's the same as `typeof x`. +To put it clear: `typeof` is an operator, not a function. The parentheses here aren't a part of `typeof`. It's the kind of parentheses used for mathematical grouping. + +Usually, such parentheses contain a mathematical expression, such as `(2 + 2)`, but here they contain only one argument `(x)`. Syntactically, they allow to avoid a space between the `typeof` operator and its argument, and some people like it. + +Some people prefer `typeof(x)`, although the `typeof x` syntax is much more common. +``` ## 總結 +<<<<<<< HEAD 在 JavaScript 中有 8 種基礎資料類型: - `number` 用於任何類型的數值:整數或浮點數。 @@ -258,12 +379,32 @@ typeof alert // "function"(3) - `undefined` 用於尚未指定值 -- 只有一個值 `undefined` 的獨立類型。 - `object` 用於更為複雜的資料結構。 - `symbol` 用於獨特的識別符。 +======= +There are 8 basic data types in JavaScript. + +- Seven primitive data types: + - `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253-1). + - `bigint` for integer numbers of arbitrary length. + - `string` for strings. A string may have zero or more characters, there's no separate single-character type. + - `boolean` for `true`/`false`. + - `null` for unknown values -- a standalone type that has a single value `null`. + - `undefined` for unassigned values -- a standalone type that has a single value `undefined`. + - `symbol` for unique identifiers. +- And one non-primitive data type: + - `object` for more complex data structures. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `typeof` 運算子讓我們確認變數中儲存的類型。 +<<<<<<< HEAD - 兩種格式:`typeof x` 或 `typeof(x)`。 - 回傳一個類型名稱的字串,像是 `"string"`。 - 對於 `null` 回傳 `"object"` -- 這是語言中的錯誤,它實際上不是個物件。 接下來的章節中,我們將更專注於介紹原生類型,一旦對它們更熟悉了,就會開始來介紹物件。 +======= +- Usually used as `typeof x`, but `typeof(x)` is also possible. +- Returns a string with the name of the type, like `"string"`. +- For `null` returns `"object"` -- this is an error in the language, it's not actually an object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md similarity index 100% rename from 1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md rename to 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md similarity index 100% rename from 1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md rename to 1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md similarity index 68% rename from 1-js/02-first-steps/09-alert-prompt-confirm/article.md rename to 1-js/02-first-steps/06-alert-prompt-confirm/article.md index 19dc9ad91..863066954 100644 --- a/1-js/02-first-steps/09-alert-prompt-confirm/article.md +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -1,5 +1,6 @@ # 互動: alert, prompt, confirm +<<<<<<< HEAD:1-js/02-first-steps/09-alert-prompt-confirm/article.md 此部分的教程涵蓋 "原本" 的 JavaScript,不需對環境做特別調整。 但因為我們將使用瀏覽器做為演示環境,所以我們應該至少知道一些跟使用者互動的函式。在本章,我們將會對 `alert`、`prompt` 和 `confirm` 瀏覽器函式更為熟悉。 @@ -13,6 +14,13 @@ alert(message); ``` 會顯示一段訊息並中斷腳本執行直到使用者按下 "OK"。 +======= +As we'll be using the browser as our demo environment, let's see a couple of functions to interact with the user: `alert`, `prompt` and `confirm`. + +## alert + +This one we've seen already. It shows a message and waits for the user to press "OK". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/06-alert-prompt-confirm/article.md 例如: @@ -20,7 +28,11 @@ alert(message); alert("Hello"); ``` +<<<<<<< HEAD:1-js/02-first-steps/09-alert-prompt-confirm/article.md 該顯示訊息的迷你窗口稱之為 *模態視窗 (modal window)*。"模態 (modal)" 這個字代表使用者不能與頁面其他部分互動或按其他按鈕等,直到他們處理了這個視窗為止,在此處為 -- 直到它們按下 "OK"。 +======= +The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc, until they have dealt with the window. In this case -- until they press "OK". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/06-alert-prompt-confirm/article.md ## prompt @@ -38,7 +50,15 @@ result = prompt(title, [default]); `default` : 可選的第二個參數,為輸入欄位的初始值。 +<<<<<<< HEAD:1-js/02-first-steps/09-alert-prompt-confirm/article.md 訪問者可在 prompt 輸入欄位寫些東西然後按下 OK。或者可藉由按下 Cancel 或 `key:Esc` 按鍵取消輸入。 +======= +```smart header="The square brackets in syntax `[...]`" +The square brackets around `default` in the syntax above denote that the parameter is optional, not required. +``` + +The visitor can type something in the prompt input field and press OK. Then we get that text in the `result`. Or they can cancel the input by pressing Cancel or hitting the `key:Esc` key, then we get `null` as the `result`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/06-alert-prompt-confirm/article.md 呼叫 `prompt` 後會回傳輸入欄位的文字,或輸入被取消時則回傳 `null`。 diff --git a/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md index 26348cecb..d56fc6187 100644 --- a/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md @@ -16,7 +16,6 @@ true + false "$" + 4 + 5 "4" - 2 "4px" - 2 -7 / 0 " -9 " + 5 " -9 " - 5 null + 1 diff --git a/1-js/02-first-steps/06-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md similarity index 71% rename from 1-js/02-first-steps/06-type-conversions/article.md rename to 1-js/02-first-steps/07-type-conversions/article.md index 039cd128c..a8d75223b 100644 --- a/1-js/02-first-steps/06-type-conversions/article.md +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -1,13 +1,24 @@ # 類型轉換 +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md 大多時候,運算子和函式會自動轉換給予它們的值為正確類型。 +======= +Most of the time, operators and functions automatically convert the values given to them to the right type. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/07-type-conversions/article.md 例如,`alert` 自動將任何值轉換成字串並顯示,數學運算子會把值轉換成數值。 當然某些情況我們得明確地轉換一個值至預期的類型。 +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md ```smart header="還沒開始談到物件類型" 在這章節,我們將不會轉換物件類型,我們會從原生類型開始說起。等晚點我們學完物件後,會在章節 中談到物件是如何被轉換的。 +======= +```smart header="Not talking about objects yet" +In this chapter, we won't cover objects. For now, we'll just be talking about primitives. + +Later, after we learn about objects, in the chapter we'll see how objects fit in. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/07-type-conversions/article.md ``` ## String Conversion @@ -32,7 +43,11 @@ alert(typeof value); // string ## Numeric Conversion +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md 數值轉換自動發生在數學運算函式和表達式。 +======= +Numeric conversion in mathematical functions and expressions happens automatically. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/07-type-conversions/article.md 例如,當除法 `/` 被運用在非數值上時: @@ -67,8 +82,13 @@ alert(age); // NaN,轉換失敗 |-------|-------------| |`undefined`|`NaN`| |`null`|`0`| +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md |true 和 false | `1` 和 `0` | | `string` | 先去除頭尾空白,如果剩下的字串是空字串則結果是 `0`,否則數值將被從剩下的字串中被 "讀取",此時若有錯誤將產生 `NaN`。 | +======= +|true and false | `1` and `0` | +| `string` | Whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/07-type-conversions/article.md 例子: @@ -81,6 +101,7 @@ alert( Number(false) ); // 0 請注意 `null` 和 `undefined` 的行為在此處不同:`null` 會變成零,而 `undefined` 會變成 `NaN`。 +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md ````smart header="加法 '+' 連接字串們" 幾乎所有數學運算子都會將值轉成數值,而加法 `+` 是個值得一提的例外。如果加法某側運算元是個字串,另一側將也會被轉換成字串。 @@ -93,6 +114,9 @@ alert( '1' + 2 ); // '12'(左側為字串) 這只發生在當至少一側的引數是字串時,否則值都會被轉為數值。 ```` +======= +Most mathematical operators also perform such conversion, we'll see that in the next chapter. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/07-type-conversions/article.md # Boolean Conversion @@ -139,7 +163,11 @@ alert( Boolean(" ") ); // 空白,也是 true(任何非空字串都是 true |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md | `string` | 字串 "依原樣" 讀取並忽略頭尾空白,若為空字串則為 `0`,有錯誤則成為 `NaN`。 | +======= +| `string` | The string is read "as is", whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/07-type-conversions/article.md **`布林轉換`** -- 用於邏輯運算,可使用 `Boolean(value)` 來轉換。 diff --git a/1-js/02-first-steps/07-operators/1-increment-order/solution.md b/1-js/02-first-steps/08-operators/1-increment-order/solution.md similarity index 100% rename from 1-js/02-first-steps/07-operators/1-increment-order/solution.md rename to 1-js/02-first-steps/08-operators/1-increment-order/solution.md diff --git a/1-js/02-first-steps/07-operators/1-increment-order/task.md b/1-js/02-first-steps/08-operators/1-increment-order/task.md similarity index 100% rename from 1-js/02-first-steps/07-operators/1-increment-order/task.md rename to 1-js/02-first-steps/08-operators/1-increment-order/task.md diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/solution.md b/1-js/02-first-steps/08-operators/2-assignment-result/solution.md similarity index 100% rename from 1-js/02-first-steps/07-operators/2-assignment-result/solution.md rename to 1-js/02-first-steps/08-operators/2-assignment-result/solution.md diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/task.md b/1-js/02-first-steps/08-operators/2-assignment-result/task.md similarity index 100% rename from 1-js/02-first-steps/07-operators/2-assignment-result/task.md rename to 1-js/02-first-steps/08-operators/2-assignment-result/task.md diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md new file mode 100644 index 000000000..7370b66af --- /dev/null +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -0,0 +1,25 @@ + +```js no-beautify +"" + 1 + 0 = "10" // (1) +"" - 1 + 0 = -1 // (2) +true + false = 1 +6 / "3" = 2 +"2" * "3" = 6 +4 + 5 + "px" = "9px" +"$" + 4 + 5 = "$45" +"4" - 2 = 2 +"4px" - 2 = NaN +" -9 " + 5 = " -9 5" // (3) +" -9 " - 5 = -14 // (4) +null + 1 = 1 // (5) +undefined + 1 = NaN // (6) +" \t \n" - 2 = -2 // (7) +``` + +1. The addition with a string `"" + 1` converts `1` to a string: `"" + 1 = "1"`, and then we have `"1" + 0`, the same rule is applied. +2. The subtraction `-` (like most math operations) only works with numbers, it converts an empty string `""` to `0`. +3. The addition with a string appends the number `5` to the string. +4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it). +5. `null` becomes `0` after the numeric conversion. +6. `undefined` becomes `NaN` after the numeric conversion. +7. Space characters are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`. diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md new file mode 100644 index 000000000..d56fc6187 --- /dev/null +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md @@ -0,0 +1,27 @@ +importance: 5 + +--- + +# 類型轉換 + +這些表達式的結果會是什麼? + +```js no-beautify +"" + 1 + 0 +"" - 1 + 0 +true + false +6 / "3" +"2" * "3" +4 + 5 + "px" +"$" + 4 + 5 +"4" - 2 +"4px" - 2 +" -9 " + 5 +" -9 " - 5 +null + 1 +undefined + 1 +" \t \n" - 2 +``` + +好好想一下,把結果寫下來並和答案對照看看。 + diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md new file mode 100644 index 000000000..209a0702c --- /dev/null +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md @@ -0,0 +1,32 @@ +The reason is that prompt returns user input as a string. + +So variables have values `"1"` and `"2"` respectively. + +```js run +let a = "1"; // prompt("First number?", 1); +let b = "2"; // prompt("Second number?", 2); + +alert(a + b); // 12 +``` + +What we should do is to convert strings to numbers before `+`. For example, using `Number()` or prepending them with `+`. + +For example, right before `prompt`: + +```js run +let a = +prompt("First number?", 1); +let b = +prompt("Second number?", 2); + +alert(a + b); // 3 +``` + +Or in the `alert`: + +```js run +let a = prompt("First number?", 1); +let b = prompt("Second number?", 2); + +alert(+a + +b); // 3 +``` + +Using both unary and binary `+` in the latest code. Looks funny, doesn't it? diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/task.md b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md new file mode 100644 index 000000000..b3ea4a3a3 --- /dev/null +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md @@ -0,0 +1,18 @@ +importance: 5 + +--- + +# Fix the addition + +Here's a code that asks the user for two numbers and shows their sum. + +It works incorrectly. The output in the example below is `12` (for default prompt values). + +Why? Fix it. The result should be `3`. + +```js run +let a = prompt("First number?", 1); +let b = prompt("Second number?", 2); + +alert(a + b); // 12 +``` diff --git a/1-js/02-first-steps/07-operators/article.md b/1-js/02-first-steps/08-operators/article.md similarity index 63% rename from 1-js/02-first-steps/07-operators/article.md rename to 1-js/02-first-steps/08-operators/article.md index dd328a96f..fe73b1e80 100644 --- a/1-js/02-first-steps/07-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -1,8 +1,16 @@ +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md # 運算子(Operators) +======= +# Basic operators, maths +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md 我們從學校學到許多運算子,像是加法 `+`、乘法 `*`、減法 `-` 等等。 +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 在本章,我們會更專注有別於學校算術運算涵蓋的部分。 +======= +In this chapter, we’ll start with simple operators, then concentrate on JavaScript-specific aspects, not covered by school arithmetic. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md ## 術語:"一元運算子(unary)"、“二元運算子(binary)"、"運算元(operand)" @@ -28,9 +36,66 @@ 正式地說,上例中的兩種不同運算子使用同一個符號:一元負號運算子(單一個運算元,正負轉換)和二元減號運算子(兩個運算元,一對一的數值減法)。 +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md ## 字串連接,二元運算子 + 現在來看看學校沒教的 JavaScript 特殊功能運算子。 +======= +## Maths + +The following math operations are supported: + +- Addition `+`, +- Subtraction `-`, +- Multiplication `*`, +- Division `/`, +- Remainder `%`, +- Exponentiation `**`. + +The first four are straightforward, while `%` and `**` need a few words about them. + +### Remainder % + +The remainder operator `%`, despite its appearance, is not related to percents. + +The result of `a % b` is the [remainder](https://en.wikipedia.org/wiki/Remainder) of the integer division of `a` by `b`. + +For instance: + +```js run +alert( 5 % 2 ); // 1, the remainder of 5 divided by 2 +alert( 8 % 3 ); // 2, the remainder of 8 divided by 3 +alert( 8 % 4 ); // 0, the remainder of 8 divided by 4 +``` + +### Exponentiation ** + +The exponentiation operator `a ** b` raises `a` to the power of `b`. + +In school maths, we write that as ab. + +For instance: + +```js run +alert( 2 ** 2 ); // 2² = 4 +alert( 2 ** 3 ); // 2³ = 8 +alert( 2 ** 4 ); // 2⁴ = 16 +``` + +Just like in maths, the exponentiation operator is defined for non-integer numbers as well. + +For example, a square root is an exponentiation by ½: + +```js run +alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root) +alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root) +``` + + +## String concatenation with binary + + +Let's meet the features of JavaScript operators that are beyond school arithmetics. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md 通常加法運算子 `+` 用於加總數值。 @@ -41,7 +106,11 @@ let s = "my" + "string"; alert(s); // mystring ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 注意只要某一側運算元是字串,另一側將會被轉換成字串類型。 +======= +Note that if any of the operands is a string, then the other one is converted to a string too. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md 例如: @@ -50,21 +119,40 @@ alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 看吧,它並不在意字串是第一個還是第二個運算元,規則就是如此簡單:若一側運算元是字串,另一側就也會被轉成字串。 然而要注意由左到右一系列的運算。如果是兩個數值運算然後才接著字串,數值們會在被轉成字串前先被加總: +======= +See, it doesn't matter whether the first operand is a string or the second one. + +Here's a more complex example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md ```js run alert(2 + 2 + '1' ); // "41" 而非 "221" ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 字串連結與轉換是二元加法運算子的一個特殊功能。其他算術運算只用於數值上,且總是會將它們的運算元轉為數值。 例如,減法和除法: +======= +Here, operators work one after another. The first `+` sums two numbers, so it returns `4`, then the next `+` adds the string `1` to it, so it's like `4 + '1' = '41'`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md ```js run -alert( 2 - '1' ); // 1 -alert( '6' / '2' ); // 3 +alert('1' + 2 + 2); // "122" and not "14" +``` +Here, the first operand is a string, the compiler treats the other two operands as strings too. The `2` gets concatenated to `'1'`, so it's like `'1' + 2 = "12"` and `"12" + 2 = "122"`. + +The binary `+` is the only operator that supports strings in such a way. Other arithmetic operators work only with numbers and always convert their operands to numbers. + +Here's the demo for subtraction and division: + +```js run +alert( 6 - '2' ); // 4, converts '2' to a number +alert( '6' / '2' ); // 3, converts both operands to numbers ``` ## 數值轉換,一元運算子 + @@ -132,11 +220,16 @@ alert( +apples + +oranges ); // 5 JavaScript 中有許多運算子,每個運算子都有個對應的優先權號碼,更大號碼的運算子會更早執行。若優先權相同,則按照左至右的方式執行。 +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 這個表格來自於 [優先權列表](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence)(你不需要記憶這個表,只要注意一元運算子都比對應的二元運算子有更高權限就好): +======= +Here's an extract from the [precedence table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md | 優先權 | 名稱 | 符號 | |------------|------|------| | ... | ... | ... | +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md | 17 | 一元正號 | `+` | | 17 | 一元負號 | `-` | | 15 | 乘法 | `*` | @@ -148,10 +241,28 @@ JavaScript 中有許多運算子,每個運算子都有個對應的優先權號 | ... | ... | ... | 我們可以看到,"一元正號運算子" 有著優先權號碼 `17`,比 "加法(二元加法運算子)" 的 `13` 還高。這也是為什麼在表達式 `"+apples + +oranges"` 中,一元正號運算子比加法還要更早運作。 +======= +| 14 | unary plus | `+` | +| 14 | unary negation | `-` | +| 13 | exponentiation | `**` | +| 12 | multiplication | `*` | +| 12 | division | `/` | +| 11 | addition | `+` | +| 11 | subtraction | `-` | +| ... | ... | ... | +| 2 | assignment | `=` | +| ... | ... | ... | + +As we can see, the "unary plus" has a priority of `14` which is higher than the `11` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md ## 指定運算子(Assignment) +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 注意看指定符號 `=` 也是一個運算子,它也被列在優先權列表中,但只有非常低的優先權 `3`。 +======= +Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `2`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md 這就是為什麼當我們指定一個變數,像是 `x = 2 * 2 + 1` 時,計算會先完成然後才指定,把結果存入 `x` 中。 @@ -161,11 +272,15 @@ let x = 2 * 2 + 1; alert( x ); // 5 ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 鏈結指定是可行的: +======= +### Assignment = returns a value +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md -```js run -let a, b, c; +The fact of `=` being an operator, not a "magical" language construct has an interesting implication. +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md *!* a = b = c = 2 + 2; */!* @@ -179,6 +294,9 @@ alert( c ); // 4 ````smart header="指定運算子 `\"=\"` 會回傳一個值" 一個運算子總是會回傳一個值,這在大部分像是加法 `+` 或乘法 `*` 運算上都很明顯,而指定運算子也遵守這個規則。 +======= +All operators in JavaScript return a value. That's obvious for `+` and `-`, but also true for `=`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md 呼叫 `x = value` 把 `value` 寫進 `x` 內 *並且回傳 x*。 @@ -198,6 +316,7 @@ alert( c ); // 0 上面例子中,表達式 `(a = b + 1)` 的結果是 `a` 被指定的值 (也就是 `3`),接著被用於進一步的運算上。 +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 很有趣的程式碼對吧?我們應該了解它是如何運作的,因為有時候會在 JavaScript 函式庫中看到它。但我們不該自己寫成這樣,這種技巧真的不會讓程式碼變得更乾淨或易讀。 ```` @@ -222,13 +341,52 @@ alert( 6 % 3 ); // 0 是 6 除以 3 的餘數 對一個數 `b` 而言,`a ** b` 的結果是 `a` 與自己相乘共 `b` 次。 例如: +======= +Funny code, isn't it? We should understand how it works, because sometimes we see it in JavaScript libraries. + +Although, please don't write the code like that. Such tricks definitely don't make code clearer or readable. + +### Chaining assignments + +Another interesting feature is the ability to chain assignments: ```js run -alert( 2 ** 2 ); // 4 (2 * 2) -alert( 2 ** 3 ); // 8 (2 * 2 * 2) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) +let a, b, c; + +*!* +a = b = c = 2 + 2; +*/!* + +alert( a ); // 4 +alert( b ); // 4 +alert( c ); // 4 ``` +Chained assignments evaluate from right to left. First, the rightmost expression `2 + 2` is evaluated and then assigned to the variables on the left: `c`, `b` and `a`. At the end, all the variables share a single value. + +Once again, for the purposes of readability it's better to split such code into few lines: + +```js +c = 2 + 2; +b = c; +a = c; +``` +That's easier to read, especially when eye-scanning the code fast. + +## Modify-in-place +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md + +We often need to apply an operator to a variable and store the new result in that same variable. + +For example: + +```js +let n = 2; +n = n + 5; +n = n * 2; +``` + +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 該運算子也可用於非整數上。 如: @@ -236,11 +394,33 @@ alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) ```js run alert( 4 ** (1/2) ); // 2(數學上 1/2 次方代表著開平方根) alert( 8 ** (1/3) ); // 2(1/3 次方代表著開立方根) +======= +This notation can be shortened using the operators `+=` and `*=`: + +```js run +let n = 2; +n += 5; // now n = 7 (same as n = n + 5) +n *= 2; // now n = 14 (same as n = n * 2) + +alert( n ); // 14 +``` + +Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: `/=`, `-=`, etc. + +Such operators have the same precedence as a normal assignment, so they run after most other calculations: + +```js run +let n = 2; + +n *= 3 + 5; // right part evaluated first, same as n *= 8 + +alert( n ); // 16 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md ``` ## 遞增(Increment)/ 遞減(decrement) - + 一次遞增或遞減一個數值幾乎是最常見到的數值運算。 @@ -367,6 +547,7 @@ counter++; - RIGHT SHIFT ( `>>` ) - ZERO-FILL RIGHT SHIFT ( `>>>` ) +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md 這些運算子很少被使用,為了理解它們,我們必須得鑽研數值的底層表示方式,但現在不是個好時機,尤其是我們不會很快需要使用它們時。如果你有興趣,可以閱讀 MDN 上的這篇文章 [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators)。在有具體需求時再來查看比較實際。 ## 原地修改 (Modify-in-place) @@ -402,6 +583,9 @@ n *= 3 + 5; alert( n ); // 16(右側先被計算,與 n *= 8 相同) ``` +======= +These operators are used very rarely, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) chapter on MDN when a need arises. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/08-operators/article.md ## 逗號運算子 diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md similarity index 52% rename from 1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md rename to 1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md index ed93816e6..8b250b2fe 100644 --- a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md @@ -12,6 +12,7 @@ null === +"\n0\n" → false 答案解析: +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md 1. 數字間的大小比較,很顯然答案為 true。 2. 按字典順序比較,因此為 false。 3. 與上題相同,按字典順序比較,第一個字母 `"2"` 大於另一個字串的第一個字母 `"1"`。 @@ -19,3 +20,12 @@ null === +"\n0\n" → false 5. 嚴格比較下,比較不同類別的值會得到 false。 6. 與`(4)` 相仿,`null` 只與 `undefined` 相等。 7. 嚴格比較下,不同類別的值會直接回傳 false。 +======= +1. Obviously, true. +2. Dictionary comparison, hence false. `"a"` is smaller than `"p"`. +3. Again, dictionary comparison, first char `"2"` is greater than the first char `"1"`. +4. Values `null` and `undefined` equal each other only. +5. Strict equality is strict. Different types from both sides lead to false. +6. Similar to `(4)`, `null` only equals `undefined`. +7. Strict equality of different types. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md similarity index 100% rename from 1-js/02-first-steps/08-comparison/1-comparison-questions/task.md rename to 1-js/02-first-steps/09-comparison/1-comparison-questions/task.md diff --git a/1-js/02-first-steps/08-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md similarity index 79% rename from 1-js/02-first-steps/08-comparison/article.md rename to 1-js/02-first-steps/09-comparison/article.md index bf8c97d4c..c836ac879 100644 --- a/1-js/02-first-steps/08-comparison/article.md +++ b/1-js/02-first-steps/09-comparison/article.md @@ -1,15 +1,34 @@ # 值的比較 +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/article.md 我們知道許多數學上的比較運算子: - 大於/小於:a > b, a < b。 - 大於等於/小於等於:a >= b, a <= b。 - 等於:`a == b`(請注意這裡使用的是兩個等號 `=`。單一個等號 `a = b` 代表的是賦值。 - 不等於:在數學中我們使用 來表示,但在 JavaScript 中,是透過在單一個等號前方加上驚嘆號作為表示:a != b。 +======= +We know many comparison operators from maths. + +In JavaScript they are written like this: + +- Greater/less than: a > b, a < b. +- Greater/less than or equals: a >= b, a <= b. +- Equals: `a == b`, please note the double equality sign `==` means the equality test, while a single one `a = b` means an assignment. +- Not equals: In maths the notation is , but in JavaScript it's written as a != b. + +In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities. + +At the end you'll find a good recipe to avoid "JavaScript quirks"-related issues. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/09-comparison/article.md ## 比較結果為布林(Boolean)類型 +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/article.md 如同其他運算子,一個比較運算會回傳一個值。在這個例子中,其回傳值的類型為布林值(Boolean)。 +======= +All comparison operators return a boolean value: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/09-comparison/article.md - `true` -- 代表 "yes(是)", "correct(正確)" 或 "the truth(真理)"。 - `false` -- means "no(否)", "wrong(錯誤)" 或 "not the truth(非真理)"。 @@ -51,7 +70,13 @@ alert( 'Bee' > 'Be' ); // true 4. 重複上述步驟直到任一字串率先用完所有字元。 5. 如果兩個字串同時比完所有字元,意即擁有相同長度,則可判定雙方為相等。否則,具有尚未比完字元的字串(長度較長)者為大。 +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/article.md 在上面的範例中,`'Z' > 'A'` 在第一步驟就取得結果,而字串 `"Glow"` 和 `"Glee"` 則繼續按字元逐個比較: +======= +In the first example above, the comparison `'Z' > 'A'` gets to a result at the first step. + +The second comparison `'Glow'` and `'Glee'` needs more steps as strings are compared character-by-character: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/09-comparison/article.md 1. `G` 和 `G` 相等。 2. `l` 和 `l` 相等。 @@ -192,6 +217,7 @@ alert( undefined == 0 ); // false (3) - 比較式 `(1)`與`(2)`回傳 `false`,因為 `undefined` 在進行比較時,被轉換成了 `NaN`,而 `NaN` 是一個特殊的數字類型,它與任何值比較都會回傳 `false`。 - 相等性檢查 `(3)`回傳 `false`,因為在定義中, `undefined` 只與 `null` 和 `undefined` 相等,不會與其他值相等。 +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/article.md ### 避免錯誤 為何我們要研究上述範例呢?我們應該要時刻記住這些奇怪的規則嗎?其實並不需要。實際上,隨著時間,你會對這些奇怪的事情漸漸熟悉,但有個更為可靠的方式可以用來避免這方面的問題: @@ -199,6 +225,14 @@ alert( undefined == 0 ); // false (3) 除了嚴格相等 `===` 以外,其他凡是有 `undefined/null` 參與的比較運算,多放點心思注意一下。 除非你非常清楚自己在做什麼,否則不要使用 `>= > < <=` 去比較一個可能為 `null/undefined` 的變數。如果一個變數可能會是 `null/undefined`,將這兩者的可能情況分開檢查。 +======= +### Avoid problems + +Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to avoid problems with them: + +- Treat any comparison with `undefined/null` except the strict equality `===` with exceptional care. +- Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/09-comparison/article.md ## 總結 diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index 5068d41ea..0ba14d542 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md @@ -6,7 +6,11 @@ importance: 2 使用 `if..else` 結構,寫一段程式碼詢問:'What is the "official" name of JavaScript?' +<<<<<<< HEAD 如果訪問者輸入 "ECMAScript",則輸出 "Right!",否則 -- 輸出:"Didn't know? ECMAScript!" +======= +If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "You don't know? ECMAScript!" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](ifelse_task2.svg) diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 0d6293568..28f2148a5 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -1,4 +1,8 @@ +<<<<<<< HEAD # 條件運算子:if、'?' +======= +# Conditional branching: if, '?' +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 有時候我們需要根據不同條件執行不同操作。 @@ -68,9 +72,13 @@ if (cond) { ## "else" 語句 +<<<<<<< HEAD `if` 述語可以包含一個可選的 "else" 區塊,它會在條件為 false 時執行。 例如: +======= +The `if` statement may contain an optional `else` block. It executes when the condition is falsy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); @@ -183,10 +191,17 @@ alert( message ); 第一眼可能很難看出發生什麼事,但進一步細看,我們可以發現這只是普通的一連串檢查: +<<<<<<< HEAD 1. 第一個問號確認 `age < 3`。 2. 若為真 -- 回傳 `'Hi, baby!'`,否則進到冒號 `":"` 後的表達式,檢查 `age < 18`。 3. 若為真 -- 回傳 `'Hello!'`,否則進到下一個冒號 `":"` 後的表達式,檢查 `age < 100`。 4. 若為真 -- 回傳 `'Greetings!'`,否則進到最後一個冒號 `":"` 後的表達式,回傳 `'What an unusual age!'`。 +======= +1. The first question mark checks whether `age < 3`. +2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon ":", checking `age < 18`. +3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon ":", checking `age < 100`. +4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon ":", returning `'What an unusual age!'`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 使用 `if...else` 達到同樣效果的樣子: diff --git a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md index 953add9e1..ac57c9969 100644 --- a/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md +++ b/1-js/02-first-steps/11-logical-operators/2-alert-or/solution.md @@ -6,8 +6,14 @@ alert( alert(1) || 2 || alert(3) ); 呼叫 `alert` 沒有回傳值。或者,換句話說,它回傳 `undefined`。 +<<<<<<< HEAD 1. 第一個 OR `||` 核定它左側的運算元 `alert(1)`,它顯示第一段訊息 `1`。 2. 該 `alert` 回傳 `undefined`,接著 OR 來到第二個運算元尋找真值。 3. 第二個運算元 `2` 是真值,所以執行停止,`2` 被回傳並且被外部的 alert 顯示。 +======= +1. The first OR `||` evaluates its left operand `alert(1)`. That shows the first message with `1`. +2. The `alert` returns `undefined`, so OR goes on to the second operand searching for a truthy value. +3. The second operand `2` is truthy, so the execution is halted, `2` is returned and then shown by the outer alert. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 不會顯示 `3`,因為核定根本到不了 `alert(3)`。 diff --git a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md index ad005468e..8a12006ee 100644 --- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md +++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md @@ -1,6 +1,6 @@ 答案是:`null`,因為它是串列中的第一個虛值。 ```js run -alert( 1 && null && 2 ); +alert(1 && null && 2); ``` diff --git a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md index 51c19b413..7da17dfc4 100644 --- a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md +++ b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md @@ -4,7 +4,11 @@ importance: 3 # 檢查區間 +<<<<<<< HEAD 寫一個 "if" 條件來檢查 `age` 是否在包含 `14` 至 `90` 的閉區間內。 "包含" 代表著 `age` 可抵達 `14` 或 `90` 邊界。 +======= +Write an `if` condition to check that `age` is between `14` and `90` inclusively. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md index 11fa8373a..dd2b5d0fe 100644 --- a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md +++ b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md @@ -4,7 +4,11 @@ importance: 3 # 檢查出界 +<<<<<<< HEAD 寫一個 `if` 條件檢查 `age` **沒有** 在包含 14 到 90 的閉區間內。 寫出兩種變化:第一個使用 NOT `!`,第二個不使用。 +======= +Write an `if` condition to check that `age` is NOT between `14` and `90` inclusively. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md index 1eab2623c..10862c4c7 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md @@ -1,19 +1,19 @@ ```js run demo let userName = prompt("Who's there?", ''); -if (userName == 'Admin') { +if (userName === 'Admin') { let pass = prompt('Password?', ''); - if (pass == 'TheMaster') { + if (pass === 'TheMaster') { alert( 'Welcome!' ); - } else if (pass == '' || pass == null) { + } else if (pass === '' || pass === null) { alert( 'Canceled' ); } else { alert( 'Wrong password' ); } -} else if (userName == '' || userName == null) { +} else if (userName === '' || userName === null) { alert( 'Canceled' ); } else { alert( "I don't know you" ); diff --git a/1-js/02-first-steps/11-logical-operators/article.md b/1-js/02-first-steps/11-logical-operators/article.md index 64fd4a800..c87f2c218 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -1,6 +1,10 @@ # 邏輯運算子 +<<<<<<< HEAD JavaScript 中有三種邏輯運算子:`||`(OR)、`&&`(AND)和 `!`(NOT)。 +======= +There are four logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT), `??` (Nullish Coalescing). Here we cover the first three, the `??` operator is in the next article. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 雖然被稱為 "邏輯",這些運算子也可被用於不只是布林類型的任何值上,結果亦可能是任何類型。 @@ -64,7 +68,11 @@ if (hour < 10 || hour > 18 || isWeekend) { } ``` +<<<<<<< HEAD ## OR "||" 找出第一個真值 +======= +## OR "||" finds the first truthy value [#or-finds-the-first-truthy-value] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 前面提到的邏輯運算有點傳統,現在來看些 JavaScript 的 "額外" 特性。 @@ -84,34 +92,55 @@ OR `||` 運算子做以下的事: 回傳值是其原始型式,而非轉換的結果。 +<<<<<<< HEAD 也就是說,一連串 OR `||` 回傳第一個真值,或在都沒有找到真值時回傳最後一個值。 +======= +In other words, a chain of OR `||` returns the first truthy value or the last one if no truthy value is found. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉個例: ```js run +<<<<<<< HEAD alert( 1 || 0 ); // 1(1 是真值) alert( true || 'no matter what' ); //(true 是真值) alert( null || 1 ); // 1(1 是第一個真值) alert( null || 0 || 1 ); // 1(第一個真值) alert( undefined || null || 0 ); // 0(皆為虛值,回傳最後一個值) +======= +alert( 1 || 0 ); // 1 (1 is truthy) + +alert( null || 1 ); // 1 (1 is the first truthy value) +alert( null || 0 || 1 ); // 1 (the first truthy value) + +alert( undefined || null || 0 ); // 0 (all falsy, returns the last value) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 這導致一些跟 "純粹傳統布林限定 OR" 不太一樣的有趣用法。 1. **取得一串變數或表達式中的第一個真值。** +<<<<<<< HEAD 想像我們有一串變數,之中包含著資料或者 `null/undefined`。我們要如何找到第一筆資料? 我們可以使用 OR `||`: +======= + For instance, we have `firstName`, `lastName` and `nickName` variables, all optional (i.e. can be undefined or have falsy values). + + Let's use OR `||` to choose the one that has the data and show it (or `"Anonymous"` if nothing set): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run - let currentUser = null; - let defaultUser = "John"; + let firstName = ""; + let lastName = ""; + let nickName = "SuperCoder"; *!* - let name = currentUser || defaultUser || "unnamed"; + alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder */!* +<<<<<<< HEAD alert( name ); // 選出 "John" - 第一個真值 ``` @@ -149,6 +178,30 @@ alert( undefined || null || 0 ); // 0(皆為虛值,回傳最後一個值) 如果我們所見,這種用法是 "簡短版 `if`"。第一個運算元被轉換成布林值,如果其為假,第二個運算元才被接著核定。 大部分時間使用 "正規的" `if` 會比較好,能讓程式碼更容易被理解,但有時候這麼做滿方便的就是。 +======= + ``` + + If all variables were falsy, `"Anonymous"` would show up. + +2. **Short-circuit evaluation.** + + Another feature of OR `||` operator is the so-called "short-circuit" evaluation. + + It means that `||` processes its arguments until the first truthy value is reached, and then the value is returned immediately, without even touching the other argument. + + The importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call. + + In the example below, only the second message is printed: + + ```js run no-beautify + *!*true*/!* || alert("not printed"); + *!*false*/!* || alert("printed"); + ``` + + In the first line, the OR `||` operator stops the evaluation immediately upon seeing `true`, so the `alert` isn't run. + + Sometimes, people use this feature to execute commands only if the condition on the left part is falsy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## &&(AND) @@ -236,7 +289,12 @@ AND `&&` 運算子的優先權比 OR `||` 還高。 所以程式碼 `a && b || c && d` 本質上跟 `&&` 使用括號的表達式一樣:`(a && b) || (c && d)`。 ```` +<<<<<<< HEAD 跟 OR 一樣,AND `&&` 運算子有時可以取代 `if`。 +======= +````warn header="Don't replace `if` with `||` or `&&`" +Sometimes, people use the AND `&&` operator as a "shorter way to write `if`". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉個例: @@ -253,14 +311,18 @@ let x = 1; ```js run let x = 1; -if (x > 0) { - alert( 'Greater than zero!' ); -} +if (x > 0) alert( 'Greater than zero!' ); ``` +<<<<<<< HEAD 使用 `&&` 的方式看起來更簡短,但 `if` 更明顯易懂且多了那麼點可讀性。 所以我們建議根據用途使用不同的程式碼建構方式:要用條件式時選 `if`,要做 AND 運算時用 `&&`。 +======= +Although, the variant with `&&` appears shorter, `if` is more obvious and tends to be a little bit more readable. So we recommend using every construct for its purpose: use `if` if we want `if` and use `&&` if we want AND. +```` + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## !(NOT) diff --git a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md new file mode 100644 index 000000000..0b2f092ab --- /dev/null +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -0,0 +1,169 @@ +# Nullish coalescing operator '??' + +[recent browser="new"] + +The nullish coalescing operator is written as two question marks `??`. + +As it treats `null` and `undefined` similarly, we'll use a special term here, in this article. For brevity, we'll say that a value is "defined" when it's neither `null` nor `undefined`. + +The result of `a ?? b` is: +- if `a` is defined, then `a`, +- if `a` isn't defined, then `b`. + +In other words, `??` returns the first argument if it's not `null/undefined`. Otherwise, the second one. + +The nullish coalescing operator isn't anything completely new. It's just a nice syntax to get the first "defined" value of the two. + +We can rewrite `result = a ?? b` using the operators that we already know, like this: + +```js +result = (a !== null && a !== undefined) ? a : b; +``` + +Now it should be absolutely clear what `??` does. Let's see where it helps. + +The common use case for `??` is to provide a default value. + +For example, here we show `user` if its value isn't `null/undefined`, otherwise `Anonymous`: + +```js run +let user; + +alert(user ?? "Anonymous"); // Anonymous (user is undefined) +``` + +Here's the example with `user` assigned to a name: + +```js run +let user = "John"; + +alert(user ?? "Anonymous"); // John (user is not null/undefined) +``` + +We can also use a sequence of `??` to select the first value from a list that isn't `null/undefined`. + +Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be not defined, if the user decided not to fill in the corresponding values. + +We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are `null/undefined`. + +Let's use the `??` operator for that: + +```js run +let firstName = null; +let lastName = null; +let nickName = "Supercoder"; + +// shows the first defined value: +*!* +alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder +*/!* +``` + +## Comparison with || + +The OR `||` operator can be used in the same way as `??`, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value). + +For example, in the code above we could replace `??` with `||` and still get the same result: + +```js run +let firstName = null; +let lastName = null; +let nickName = "Supercoder"; + +// shows the first truthy value: +*!* +alert(firstName || lastName || nickName || "Anonymous"); // Supercoder +*/!* +``` + +Historically, the OR `||` operator was there first. It's been there since the beginning of JavaScript, so developers were using it for such purposes for a long time. + +On the other hand, the nullish coalescing operator `??` was added to JavaScript only recently, and the reason for that was that people weren't quite happy with `||`. + +The important difference between them is that: +- `||` returns the first *truthy* value. +- `??` returns the first *defined* value. + +In other words, `||` doesn't distinguish between `false`, `0`, an empty string `""` and `null/undefined`. They are all the same -- falsy values. If any of these is the first argument of `||`, then we'll get the second argument as the result. + +In practice though, we may want to use default value only when the variable is `null/undefined`. That is, when the value is really unknown/not set. + +For example, consider this: + +```js run +let height = 0; + +alert(height || 100); // 100 +alert(height ?? 100); // 0 +``` + +- The `height || 100` checks `height` for being a falsy value, and it's `0`, falsy indeed. + - so the result of `||` is the second argument, `100`. +- The `height ?? 100` checks `height` for being `null/undefined`, and it's not, + - so the result is `height` "as is", that is `0`. + +In practice, the zero height is often a valid value, that shouldn't be replaced with the default. So `??` does just the right thing. + +## Precedence + +The precedence of the `??` operator is the same as `||`. They both equal `3` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). + +That means that, just like `||`, the nullish coalescing operator `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`. + +So we may need to add parentheses in expressions like this: + +```js run +let height = null; +let width = null; + +// important: use parentheses +let area = (height ?? 100) * (width ?? 50); + +alert(area); // 5000 +``` + +Otherwise, if we omit parentheses, then as `*` has the higher precedence than `??`, it would execute first, leading to incorrect results. + +```js +// without parentheses +let area = height ?? 100 * width ?? 50; + +// ...works this way (not what we want): +let area = height ?? (100 * width) ?? 50; +``` + +### Using ?? with && or || + +Due to safety reasons, JavaScript forbids using `??` together with `&&` and `||` operators, unless the precedence is explicitly specified with parentheses. + +The code below triggers a syntax error: + +```js run +let x = 1 && 2 ?? 3; // Syntax error +``` + +The limitation is surely debatable, it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch from `||` to `??`. + +Use explicit parentheses to work around it: + +```js run +*!* +let x = (1 && 2) ?? 3; // Works +*/!* + +alert(x); // 2 +``` + +## Summary + +- The nullish coalescing operator `??` provides a short way to choose the first "defined" value from a list. + + It's used to assign default values to variables: + + ```js + // set height=100, if height is null or undefined + height = height ?? 100; + ``` + +- The operator `??` has a very low precedence, only a bit higher than `?` and `=`, so consider adding parentheses when using it in an expression. +- It's forbidden to use it with `||` or `&&` without explicit parentheses. diff --git a/1-js/02-first-steps/12-while-for/1-loop-last-value/solution.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/1-loop-last-value/solution.md rename to 1-js/02-first-steps/13-while-for/1-loop-last-value/solution.md diff --git a/1-js/02-first-steps/12-while-for/1-loop-last-value/task.md b/1-js/02-first-steps/13-while-for/1-loop-last-value/task.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/1-loop-last-value/task.md rename to 1-js/02-first-steps/13-while-for/1-loop-last-value/task.md diff --git a/1-js/02-first-steps/12-while-for/2-which-value-while/solution.md b/1-js/02-first-steps/13-while-for/2-which-value-while/solution.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/2-which-value-while/solution.md rename to 1-js/02-first-steps/13-while-for/2-which-value-while/solution.md diff --git a/1-js/02-first-steps/12-while-for/2-which-value-while/task.md b/1-js/02-first-steps/13-while-for/2-which-value-while/task.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/2-which-value-while/task.md rename to 1-js/02-first-steps/13-while-for/2-which-value-while/task.md diff --git a/1-js/02-first-steps/12-while-for/3-which-value-for/solution.md b/1-js/02-first-steps/13-while-for/3-which-value-for/solution.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/3-which-value-for/solution.md rename to 1-js/02-first-steps/13-while-for/3-which-value-for/solution.md diff --git a/1-js/02-first-steps/12-while-for/3-which-value-for/task.md b/1-js/02-first-steps/13-while-for/3-which-value-for/task.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/3-which-value-for/task.md rename to 1-js/02-first-steps/13-while-for/3-which-value-for/task.md diff --git a/1-js/02-first-steps/12-while-for/4-for-even/solution.md b/1-js/02-first-steps/13-while-for/4-for-even/solution.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/4-for-even/solution.md rename to 1-js/02-first-steps/13-while-for/4-for-even/solution.md diff --git a/1-js/02-first-steps/12-while-for/4-for-even/task.md b/1-js/02-first-steps/13-while-for/4-for-even/task.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/4-for-even/task.md rename to 1-js/02-first-steps/13-while-for/4-for-even/task.md diff --git a/1-js/02-first-steps/12-while-for/5-replace-for-while/solution.md b/1-js/02-first-steps/13-while-for/5-replace-for-while/solution.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/5-replace-for-while/solution.md rename to 1-js/02-first-steps/13-while-for/5-replace-for-while/solution.md diff --git a/1-js/02-first-steps/12-while-for/5-replace-for-while/task.md b/1-js/02-first-steps/13-while-for/5-replace-for-while/task.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/5-replace-for-while/task.md rename to 1-js/02-first-steps/13-while-for/5-replace-for-while/task.md diff --git a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md similarity index 57% rename from 1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md rename to 1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md index 3efe7cfb5..301431a52 100644 --- a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md +++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md @@ -9,8 +9,13 @@ do { 兩個檢查都是真值時,迴圈 `do..while` 將重複: +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md 1. 檢查 `num <= 100` -- 意即,輸入的值尚未大於 `100`。 2. 檢查 `&& num`,只有在 `num` 為 `null` 或空字串時才為假,此時 `while` 迴圈也會停止。 註:若 `num` 為 `null`,則 `num <= 100` 是 `true`。所以如果沒有第二層檢查,該迴圈在使用者點下 CANCEL 後將不會停止,故兩個檢查都是必要的。 +======= +1. The check for `num <= 100` -- that is, the entered value is still not greater than `100`. +2. The check `&& num` is false when `num` is `null` or an empty string. Then the `while` loop stops too. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md diff --git a/1-js/02-first-steps/12-while-for/6-repeat-until-correct/task.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/6-repeat-until-correct/task.md rename to 1-js/02-first-steps/13-while-for/6-repeat-until-correct/task.md diff --git a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md b/1-js/02-first-steps/13-while-for/7-list-primes/solution.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/7-list-primes/solution.md rename to 1-js/02-first-steps/13-while-for/7-list-primes/solution.md diff --git a/1-js/02-first-steps/12-while-for/7-list-primes/task.md b/1-js/02-first-steps/13-while-for/7-list-primes/task.md similarity index 100% rename from 1-js/02-first-steps/12-while-for/7-list-primes/task.md rename to 1-js/02-first-steps/13-while-for/7-list-primes/task.md diff --git a/1-js/02-first-steps/12-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md similarity index 77% rename from 1-js/02-first-steps/12-while-for/article.md rename to 1-js/02-first-steps/13-while-for/article.md index 7dccebe5a..9c087c098 100644 --- a/1-js/02-first-steps/12-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -6,7 +6,24 @@ *迴圈* 是種多次重複執行相同程式碼的方法。 +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md ## "while" 迴圈 +======= +```smart header="The for..of and for..in loops" +A small announcement for advanced readers. + +This article covers only basic loops: `while`, `do..while` and `for(..;..;..)`. + +If you came to this article searching for other types of loops, here are the pointers: + +- See [for..in](info:object#forin) to loop over object properties. +- See [for..of](info:array#loops) and [iterables](info:iterable) for looping over arrays and iterable objects. + +Otherwise, please read on. +``` + +## The "while" loop +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/article.md `while` 迴圈有著以下語法: @@ -106,10 +123,17 @@ for (let i = 0; i < 3; i++) { // 顯示 0,接著 1,然後 2 | 段落 | | | |-------|----------|----------------------------------------------------------------------------| +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md | begin | `i = 0` | 在進入迴圈時執行一次。 | | condition | `i < 3`| 每次迴圈迭代開始前檢查,若為假則迴圈停止。 | | body | `alert(i)`| 當條件是真值時,一直重複執行。 | | step| `i++` | 每次迭代時會在本體之後才執行。 | +======= +| begin | `let i = 0` | Executes once upon entering the loop. | +| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. | +| body | `alert(i)`| Runs again and again while the condition is truthy. | +| step| `i++` | Executes after the body on each iteration. | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/article.md 一般的迴圈演算法像這樣子運行: @@ -162,12 +186,15 @@ for (i = 0; i < 3; i++) { // 使用某個已存在的變數 alert(i); // 3,可被看見,因為是在迴圈之外被宣告的 ``` - ```` +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md ### 省略某幾段 `for` 的任何一段都可被省略。 +======= +### Skipping parts +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/article.md 例如,當我們不需要在迴圈開始時做任何事,就可以忽略 `begin`。 @@ -267,7 +294,11 @@ for (let i = 0; i < 10; i++) { 從技術角度上來看,這跟上面的例子是等效的。確實,我們可以將程式碼包裝在一個 `if` 區塊內,而不使用 `continue`。 +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md 但這麼做有副作用,它會建立多一層的巢狀結構(大括號內的 `alert` 呼叫)。若 `if` 內的程式碼不只短短幾行,就會降低整體的可讀性。 +======= +But as a side effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/article.md ```` ````warn header="不能在 '?' 右側使用 `break/continue`" @@ -283,7 +314,11 @@ if (i > 5) { } ``` +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md ...改寫成使用問號: +======= +...and rewrite it using a question mark: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/article.md ```js no-beautify (i > 5) ? alert(i) : *!*continue*/!*; // continue 不允許出現在這 @@ -316,10 +351,17 @@ alert('Done!'); 我們需要一個若使用者取消輸入時,用來結束運行的方式。 +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md 一般在 `input` 之後放入 `break` 只會中斷內部的迴圈,這還不夠 -- 可以用 "標籤" 來幫助我們! 一個 *標籤(label)* 是一個在迴圈前有著冒號的識別符: +======= +The ordinary `break` after `input` would only break the inner loop. That's not sufficient -- labels, come to the rescue! + +A *label* is an identifier with a colon before a loop: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/article.md ```js labelName: for (...) { ... @@ -341,6 +383,7 @@ labelName: for (...) { // 用輸入的值做些事情... } } + alert('Done!'); ``` @@ -362,13 +405,36 @@ for (let i = 0; i < 3; i++) { ... } 例如,不能這麼做: +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md ```js break label; // 不能跳到下面的標籤 +======= +For example, it is impossible to do this: + +```js +break label; // jump to the label below (doesn't work) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/article.md label: for (...) ``` +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md 只有在迴圈內部才可呼叫 `break/continue`,且標籤必須在該指令的上方某處。 +======= +A `break` directive must be inside a code block. Technically, any labelled code block will do, e.g.: + +```js +label: { + // ... + break label; // works + // ... +} +``` + +...Although, 99.9% of the time `break` is used inside loops, as we've seen in the examples above. + +A `continue` is only possible from inside a loop. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/13-while-for/article.md ```` ## 總結 diff --git a/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/solution.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md similarity index 100% rename from 1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/solution.md rename to 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/solution.md diff --git a/1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/task.md b/1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md similarity index 100% rename from 1-js/02-first-steps/13-switch/1-rewrite-switch-if-else/task.md rename to 1-js/02-first-steps/14-switch/1-rewrite-switch-if-else/task.md diff --git a/1-js/02-first-steps/13-switch/2-rewrite-if-switch/solution.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md similarity index 100% rename from 1-js/02-first-steps/13-switch/2-rewrite-if-switch/solution.md rename to 1-js/02-first-steps/14-switch/2-rewrite-if-switch/solution.md diff --git a/1-js/02-first-steps/13-switch/2-rewrite-if-switch/task.md b/1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md similarity index 100% rename from 1-js/02-first-steps/13-switch/2-rewrite-if-switch/task.md rename to 1-js/02-first-steps/14-switch/2-rewrite-if-switch/task.md diff --git a/1-js/02-first-steps/13-switch/article.md b/1-js/02-first-steps/14-switch/article.md similarity index 97% rename from 1-js/02-first-steps/13-switch/article.md rename to 1-js/02-first-steps/14-switch/article.md index 314c6cef8..d86babcec 100644 --- a/1-js/02-first-steps/13-switch/article.md +++ b/1-js/02-first-steps/14-switch/article.md @@ -47,7 +47,7 @@ switch (a) { break; */!* case 5: - alert( 'Too large' ); + alert( 'Too big' ); break; default: alert( "I don't know such values" ); @@ -139,7 +139,7 @@ switch (a) { Now both `3` and `5` show the same message. -The ability to "group" cases is a side-effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`. +The ability to "group" cases is a side effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`. ## Type matters diff --git a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md new file mode 100644 index 000000000..e3a0df77c --- /dev/null +++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md @@ -0,0 +1,3 @@ +No difference! + +In both cases, `return confirm('Did parents allow you?')` executes exactly when the `if` condition is falsy. \ No newline at end of file diff --git a/1-js/02-first-steps/14-function-basics/1-if-else-required/task.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/task.md similarity index 100% rename from 1-js/02-first-steps/14-function-basics/1-if-else-required/task.md rename to 1-js/02-first-steps/15-function-basics/1-if-else-required/task.md diff --git a/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/solution.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md similarity index 53% rename from 1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/solution.md rename to 1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md index 63f423dc8..4531a8d57 100644 --- a/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/solution.md +++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md @@ -14,5 +14,9 @@ function checkAge(age) { } ``` +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/solution.md 注意圍繞著 `age > 18` 的括號在此並非必要,它們存在的原因只是為了更佳的可讀性。 +======= +Note that the parentheses around `age > 18` are not required here. They exist for better readability. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md diff --git a/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md similarity index 100% rename from 1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md rename to 1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/task.md diff --git a/1-js/02-first-steps/14-function-basics/3-min/solution.md b/1-js/02-first-steps/15-function-basics/3-min/solution.md similarity index 100% rename from 1-js/02-first-steps/14-function-basics/3-min/solution.md rename to 1-js/02-first-steps/15-function-basics/3-min/solution.md diff --git a/1-js/02-first-steps/14-function-basics/3-min/task.md b/1-js/02-first-steps/15-function-basics/3-min/task.md similarity index 100% rename from 1-js/02-first-steps/14-function-basics/3-min/task.md rename to 1-js/02-first-steps/15-function-basics/3-min/task.md diff --git a/1-js/02-first-steps/14-function-basics/4-pow/solution.md b/1-js/02-first-steps/15-function-basics/4-pow/solution.md similarity index 100% rename from 1-js/02-first-steps/14-function-basics/4-pow/solution.md rename to 1-js/02-first-steps/15-function-basics/4-pow/solution.md diff --git a/1-js/02-first-steps/14-function-basics/4-pow/task.md b/1-js/02-first-steps/15-function-basics/4-pow/task.md similarity index 100% rename from 1-js/02-first-steps/14-function-basics/4-pow/task.md rename to 1-js/02-first-steps/15-function-basics/4-pow/task.md diff --git a/1-js/02-first-steps/14-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md similarity index 70% rename from 1-js/02-first-steps/14-function-basics/article.md rename to 1-js/02-first-steps/15-function-basics/article.md index 84e386c2a..ed38e611c 100644 --- a/1-js/02-first-steps/14-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -20,11 +20,15 @@ function showMessage() { } ``` +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md `function` 關鍵字寫在最前面,然後是 *函式的名字*,接著一串在小括號內的 *參數(parameters)*(用逗號分開,上面的例子中為空),最後在大括號之間的是函式的程式碼,也被稱為 "函式本體(function body)"。 +======= +The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above, we'll see examples later) and finally the code of the function, also named "the function body", between curly braces. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md ```js -function name(parameters) { - ...body... +function name(parameter1, parameter2, ... parameterN) { + // body } ``` @@ -137,25 +141,34 @@ alert( userName ); // *!*John*/!* 維持不變,函式並沒有存取外部變 ## 參數(Parameters) +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md 我們可以使用參數,或稱為 *函式引數(function arguments)*,來傳遞任意資料給函式。 +======= +We can pass arbitrary data to functions using parameters. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md 在上面的例子中,函式有兩個參數:`from` 和 `text`。 ```js run +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md function showMessage(*!*from, text*/!*) { // 引數:from、text +======= +function showMessage(*!*from, text*/!*) { // parameters: from, text +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md alert(from + ': ' + text); } -*!* -showMessage('Ann', 'Hello!'); // Ann: Hello! (*) -showMessage('Ann', "What's up?"); // Ann: What's up? (**) -*/!* +*!*showMessage('Ann', 'Hello!');*/!* // Ann: Hello! (*) +*!*showMessage('Ann', "What's up?");*/!* // Ann: What's up? (**) ``` 當函式在 `(*)` 和 `(**)` 被呼叫時,被給予的值將會被複製到區域變數 `from` 和 `text` 中,然後函式才使用它們。 +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md 這裡有另一個例子:我們把一個變數 `from` 傳遞給函式。請注意,此函式改變了 `from`,但這個改變在外部看不到,因為函式總是使用該值的複製品: +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md ```js run function showMessage(from, text) { @@ -174,9 +187,27 @@ showMessage(from, "Hello"); // *Ann*: Hello alert( from ); // Ann ``` +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md ## 預設值 若參數沒被提供,它的值會變成 `undefined`。 +======= +When a value is passed as a function parameter, it's also called an *argument*. + +In other words, to put these terms straight: + +- A parameter is the variable listed inside the parentheses in the function declaration (it's a declaration time term). +- An argument is the value that is passed to the function when it is called (it's a call time term). + +We declare functions listing their parameters, then call them passing arguments. + +In the example above, one might say: "the function `showMessage` is declared with two parameters, then called with two arguments: `from` and `"Hello"`". + + +## Default values + +If a function is called, but an argument is not provided, then the corresponding value becomes `undefined`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md 舉個例,前述的函式 `showMessage(from, text)` 可以使用單一個引數來呼叫: @@ -184,9 +215,15 @@ alert( from ); // Ann showMessage("Ann"); ``` +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md 那並非錯誤,這種呼叫方式會輸出 `"Ann: undefined"`。因為沒有 `text`,所以它預設成 `text === undefined`。 如果在這種情況想使用一個 "預設的" `text`,那我們可以加個 `=` 並在其後指定預設值: +======= +That's not an error. Such a call would output `"*Ann*: undefined"`. As the value for `text` isn't passed, it becomes `undefined`. + +We can specify the so-called "default" (to use if omitted) value for a parameter in the function declaration, using `=`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md ```js run function showMessage(from, *!*text = "no text given"*/!*) { @@ -196,7 +233,17 @@ function showMessage(from, *!*text = "no text given"*/!*) { showMessage("Ann"); // Ann: no text given ``` +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md 若現在沒有傳遞 `text` 參數,它將會拿到值 `"no text given"`。 +======= +Now if the `text` parameter is not passed, it will get the value `"no text given"`. + +The default value also jumps in if the parameter exists, but strictly equals `undefined`, like this: + +```js +showMessage("Ann", undefined); // Ann: no text given +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md 這邊的 `"no text given"` 是一個字串,但它可以是更為複雜的表達式,該表達式只在未給予參數時才會被計算和指定。所以這麼做是可以的: @@ -210,6 +257,7 @@ function showMessage(from, text = anotherFunction()) { ```smart header="預設參數的計算" 在 JavaScript 中,每次呼叫函式卻沒帶對應參數時,該預設參數才會被計算。 +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md 上面的例子中,在呼叫 `showMessage()` 卻沒帶 `text` 參數時,才會去呼叫 `anotherFunction()`。 ``` @@ -217,6 +265,19 @@ function showMessage(from, text = anotherFunction()) { 舊版本的 JavaScript 不支援預設參數,所以有幾種替代方案來支援,你可以在大多數老舊的腳本中找到。 舉個例,明確檢查 `undefined`: +======= +In the example above, `anotherFunction()` isn't called at all, if the `text` parameter is provided. + +On the other hand, it's independently called every time when `text` is missing. +``` + +````smart header="Default parameters in old JavaScript code" +Several years ago, JavaScript didn't support the syntax for default parameters. So people used other ways to specify them. + +Nowadays, we can come across them in old scripts. + +For example, an explicit check for `undefined`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md ```js function showMessage(from, text) { @@ -230,11 +291,20 @@ function showMessage(from, text) { } ``` +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md ...或使用 `||` 運算子: ```js function showMessage(from, text) { // 若 text 為虛值,則 text 會得到其 "預設" 值 +======= +...Or using the `||` operator: + +```js +function showMessage(from, text) { + // If the value of text is falsy, assign the default value + // this assumes that text == "" is the same as no text at all +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md text = text || 'no text given'; ... } @@ -243,7 +313,56 @@ function showMessage(from, text) { ## 回傳值 +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md 函式可以回傳一個值給呼叫它的程式碼作為結果。 +======= +### Alternative default parameters + +Sometimes it makes sense to assign default values for parameters at a later stage after the function declaration. + +We can check if the parameter is passed during the function execution, by comparing it with `undefined`: + +```js run +function showMessage(text) { + // ... + +*!* + if (text === undefined) { // if the parameter is missing + text = 'empty message'; + } +*/!* + + alert(text); +} + +showMessage(); // empty message +``` + +...Or we could use the `||` operator: + +```js +function showMessage(text) { + // if text is undefined or otherwise falsy, set it to 'empty' + text = text || 'empty'; + ... +} +``` + +Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when most falsy values, such as `0`, should be considered "normal": + +```js run +function showCount(count) { + // if count is undefined or null, show "unknown" + alert(count ?? "unknown"); +} + +showCount(0); // 0 +showCount(null); // unknown +showCount(); // unknown +``` + +## Returning a value +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md 把兩個值相加的最簡單做法是利用函式: @@ -395,9 +514,15 @@ checkPermission(..) // 確認權限,並回傳 true/false ```smart header="極短函式名稱" *很常* 被使用的函式有時有著極短的名稱。 +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md 例如,[jQuery](http://jquery.com) 框架使用 `$` 定義了一個函式。而 [Lodash](http://lodash.com/) 函式庫以 `_` 作為其核心函式的名稱。 這些都只是例外,一般的函式名稱應該要精簡易懂。 +======= +For example, the [jQuery](https://jquery.com/) framework defines a function with `$`. The [Lodash](https://lodash.com/) library has its core function named `_`. + +These are exceptions. Generally function names should be concise and descriptive. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md ``` ## 函式 == 註解 @@ -463,7 +588,11 @@ function name(parameters, delimited, by, comma) { 為了讓程式碼簡潔易懂,建議在函式中以使用區域變數和參數為主,少用外部變數。 +<<<<<<< HEAD:1-js/02-first-steps/14-function-basics/article.md 比起沒有取得任何參數,直接修改外部變數作為副作用的做法,函式先獲取參數並對其操作再回傳結果,更容易讓人理解。 +======= +It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side effect. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/15-function-basics/article.md 函式命名: diff --git a/1-js/02-first-steps/15-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md similarity index 69% rename from 1-js/02-first-steps/15-function-expressions/article.md rename to 1-js/02-first-steps/16-function-expressions/article.md index 797338a82..4bbaa8e65 100644 --- a/1-js/02-first-steps/15-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -12,7 +12,13 @@ function sayHi() { 有另一種創建函式的語法,稱之為 *函式表達式(Function Expression)*。 +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md 寫法如下: +======= +It allows us to create a new function in the middle of any expression. + +For example: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md ```js let sayHi = function() { @@ -20,9 +26,25 @@ let sayHi = function() { }; ``` +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md 在這邊,函式被創建,並像其他值ㄧ樣,明確地指定給一個變數。無論函式是如何被定義的,它只是一個儲存於變數 `sayHi` 的值。 這兩段程式碼範例的意思是相同的:"創造一個函式,並將其放入變數 `sayHi`"。 +======= +Here we can see a variable `sayHi` getting a value, the new function, created as `function() { alert("Hello"); }`. + +As the function creation happens in the context of the assignment expression (to the right side of `=`), this is a *Function Expression*. + +Please note, there's no name after the `function` keyword. Omitting a name is allowed for Function Expressions. + +Here we immediately assign it to the variable, so the meaning of these code samples is the same: "create a function and put it into the variable `sayHi`". + +In more advanced situations, that we'll come across later, a function may be created and immediately called or scheduled for a later execution, not stored anywhere, thus remaining anonymous. + +## Function is a value + +Let's reiterate: no matter how the function is created, a function is a value. Both examples above store a function in the `sayHi` variable. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md 我們甚至可以利用 `alert` 將其值印出: @@ -64,21 +86,31 @@ sayHi(); // Hello // 這也能正常運作(怎麼會不行呢) 2. 行 `(2)` 將其複製給變數 `func`。請注意:`sayHi` 後面沒有括號。如果有括號,`func = sayHi()` 會將 `sayHi()` *呼叫的結果* 寫入 `func`,而非 `sayHi` 函式本身。 3. 現在 `sayHi()` 與 `func()` 都可以呼叫到同個函式。 +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md 注意這裡的第一行中,我們也可以使用函式表達式來宣告 `sayHi`: +======= +We could also have used a Function Expression to declare `sayHi`, in the first line: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md ```js -let sayHi = function() { +let sayHi = function() { // (1) create alert( "Hello" ); }; -let func = sayHi; +let func = sayHi; //(2) // ... ``` 這並不影響其餘運作,一切都與先前相同。 +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md ````smart header="為何函式表達式的結尾有個分號?" 你可能會好奇,為什麼函式表達式的結尾有個分號 `;`,而函式宣告式卻沒有: +======= + +````smart header="Why is there a semicolon at the end?" +You might wonder, why do Function Expressions have a semicolon `;` at the end, but Function Declarations do not: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md ```js function sayHi() { @@ -90,9 +122,15 @@ let sayHi = function() { }*!*;*/!* ``` +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md 答案很簡單: - 在使用像是 `if { ... }`、`for { }`、`function f { }` 等的程式碼區塊與語法結構時,並不需要在結尾放上 `;`。 - 函式表達式通常使用在述語中: `let sayHi = ...;`,像個變數。這並不是一個程式碼區塊,而是一段指定運算。不管是什麼值,建議總是在述語結尾處加上分號 `;`。所以這裡的分號與函式表達式本身沒有關係,他只是用來終止述語。 +======= +The answer is simple: a Function Expression is created here as `function(…) {…}` inside the assignment statement: `let sayHi = …;`. The semicolon `;` is recommended at the end of the statement, it's not a part of the function syntax. + +The semicolon would be there for a simpler assignment, such as `let sayHi = 5;`, and it's also there for a function assignment. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md ```` ## 回呼函式(Callback functions) @@ -132,13 +170,21 @@ function showCancel() { ask("Do you agree?", showOk, showCancel); ``` +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md 實務上,類似的函式挺有用的。現實生活中的 `ask` 實作與上述範例的主要差別在於,現實中的函式用更複雜的方式與使用者互動,而不是簡單的 `confirm` (確認)。在瀏覽器中,這樣的函式通常會繪製一個漂亮的問答窗口。但那牽扯到另一個主題了。 +======= +In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such functions usually draw a nice-looking question window. But that's another story. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md **`ask` 的引數 `showOk` 與 `showCancel` 稱作 *回呼函數* 或就叫 *回乎*。** 背後的想法是,我們傳遞一個函式,並期望稍後它能在必要時被 "回呼"。在我們的例子中,`showOk` 成為了 "yes" 答案的回呼,而 `showCancel` 則是對應 "no" 答案。 +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md 我們可以使用函式表達式來將其簡寫: +======= +We can use Function Expressions to write an equivalent, shorter function: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md ```js run no-beautify function ask(question, yes, no) { @@ -174,7 +220,13 @@ ask( 首先是語法:如何在程式碼中區別他們。 +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md - *函式宣告式:* 一個函式,在主程式碼中分開宣告的述語。 +======= +First, the syntax: how to differentiate between them in the code. + +- *Function Declaration:* a function, declared as a separate statement, in the main code flow: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md ```js // 函數宣告式 @@ -182,8 +234,12 @@ ask( return a + b; } ``` +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md - *函式表達式:* 一個函式,在一個表達式或另一個語法結構中創建。這邊的例子,函式在指定運算表達符號 `=` 的右側被創建: +======= +- *Function Expression:* a function, created inside an expression or inside another syntax construct. Here, the function is created on the right side of the "assignment expression" `=`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md ```js // Function Expression @@ -280,9 +336,15 @@ if (age < 18) { welcome(); // \ (運行) */!* // | +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md function welcome() { // | alert("Hello!"); // | 函式宣告式可被存取於 } // | 其所被宣告區塊內的任何地方 +======= + function welcome() { // | + alert("Hello!"); // | Function Declaration is available + } // | everywhere in the block where it's declared +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md // | *!* welcome(); // / (運行) @@ -290,7 +352,7 @@ alert("Hello!"); // | 函式宣告式可被存取於 } else { - function welcome() { + function welcome() { alert("Greetings!"); } } @@ -350,7 +412,12 @@ welcome(); // ok now ```smart header="何時選擇函式宣告式或函式表達式?" 根據經驗,當我們需要宣告函式時,首先考慮的是函式宣告式語法。對於組織我們的程式碼上,它給了我們更多的自由,因為我們可以在這些函式被宣告前呼叫他們。 +<<<<<<< HEAD:1-js/02-first-steps/15-function-expressions/article.md 在可讀性上也更好,在程式碼中找尋 `function f(...) {...}` 比起 `let f = function(...) {...}` 更簡單。函式宣告式更能 "吸引目光"。 +======= +```smart header="When to choose Function Declaration versus Function Expression?" +As a rule of thumb, when we need to declare a function, the first thing to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/16-function-expressions/article.md ...但如果函式宣告式由於某些原因不適合我們,或是我們需要條件式宣告(我們剛剛才看過例子),那就應該使用函式表達式。 ``` diff --git a/1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/solution.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md similarity index 86% rename from 1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/solution.md rename to 1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md index 0f0264d67..4e5b217d7 100644 --- a/1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/solution.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md @@ -1,7 +1,7 @@ ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/task.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md similarity index 89% rename from 1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/task.md rename to 1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md index 1c095b8c3..1cbb05b4f 100644 --- a/1-js/02-first-steps/16-arrow-functions-basics/1-rewrite-arrow/task.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md @@ -5,7 +5,7 @@ ```js run function ask(question, yes, no) { - if (confirm(question)) yes() + if (confirm(question)) yes(); else no(); } diff --git a/1-js/02-first-steps/16-arrow-functions-basics/article.md b/1-js/02-first-steps/17-arrow-functions-basics/article.md similarity index 52% rename from 1-js/02-first-steps/16-arrow-functions-basics/article.md rename to 1-js/02-first-steps/17-arrow-functions-basics/article.md index 8655855a2..cecc4e2fe 100644 --- a/1-js/02-first-steps/16-arrow-functions-basics/article.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/article.md @@ -5,15 +5,19 @@ 它被稱為 "箭頭函式(arrow functions)",因為它看起來像這樣: ```js -let func = (arg1, arg2, ...argN) => expression +let func = (arg1, arg2, ..., argN) => expression; ``` +<<<<<<< HEAD:1-js/02-first-steps/16-arrow-functions-basics/article.md ...這會創建一個接受引數 `arg1..argN` 的函式 `func`,運行右側的 `expression` 並回傳結果。 +======= +This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/17-arrow-functions-basics/article.md 換句話說,它是這個的簡短版本: ```js -let func = function(arg1, arg2, ...argN) { +let func = function(arg1, arg2, ..., argN) { return expression; }; ``` @@ -33,7 +37,11 @@ let sum = function(a, b) { alert( sum(1, 2) ); // 3 ``` +<<<<<<< HEAD:1-js/02-first-steps/16-arrow-functions-basics/article.md 如你所見,`(a, b) => a + b` 代表接收兩個名為 `a` 與 `b` 引數的函式。在執行時,它會運算 `a + b` 的表達式然後回傳結果。 +======= +As you can see, `(a, b) => a + b` means a function that accepts two arguments named `a` and `b`. Upon the execution, it evaluates the expression `a + b` and returns the result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/17-arrow-functions-basics/article.md - 如果我們只有一個引數,那麼參數旁的括號可以省略,讓它更短。 @@ -48,7 +56,11 @@ alert( sum(1, 2) ); // 3 alert( double(3) ); // 6 ``` +<<<<<<< HEAD:1-js/02-first-steps/16-arrow-functions-basics/article.md - 如果沒有任何引數,括號應該是空的(但他們應該被保留): +======= +- If there are no arguments, parentheses are empty, but they must be present: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/17-arrow-functions-basics/article.md ```js run let sayHi = () => alert("Hello!"); @@ -64,7 +76,7 @@ alert( sum(1, 2) ); // 3 let age = prompt("What is your age?", 18); let welcome = (age < 18) ? - () => alert('Hello') : + () => alert('Hello!') : () => alert("Greetings!"); welcome(); @@ -76,9 +88,15 @@ welcome(); ## 多行箭頭函式 +<<<<<<< HEAD:1-js/02-first-steps/16-arrow-functions-basics/article.md 上述的範例拿取 `=>` 左側的引數並在右側的表達式中使用它們。 有時我們需要一些更複雜的東西,像是多個表達式或是述語。這也是可行的,但我們應該用大括弧將它們括起來。然後在其中使用普通的 `return`。 +======= +The arrow functions that we've seen so far were very simple. They took arguments from the left of `=>`, evaluated and returned the right-side expression with them. + +Sometimes we need a more complex function, with multiple expressions and statements. In that case, we can enclose them in curly braces. The major difference is that curly braces require a `return` within them to return a value (just like a regular function does). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/17-arrow-functions-basics/article.md 像這樣: @@ -86,7 +104,11 @@ welcome(); let sum = (a, b) => { // 大括弧開啟多行函式 let result = a + b; *!* +<<<<<<< HEAD:1-js/02-first-steps/16-arrow-functions-basics/article.md return result; // 如果我們使用大括弧,則需要明確的 "return" +======= + return result; // if we use curly braces, then we need an explicit "return" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/17-arrow-functions-basics/article.md */!* }; @@ -105,7 +127,14 @@ alert( sum(1, 2) ); // 3 ## 總結 +<<<<<<< HEAD:1-js/02-first-steps/16-arrow-functions-basics/article.md 箭頭函式對於單行動作來說非常方便,以下是它的兩個樣貌: 1. 沒有大括號:`(...args) => expression` -- 右側是一個表達式:函式執行它並回傳結果。 2. 有大括號:`(...args) => { body }` -- 括號允許我們在函式內撰寫多行述語,但我們需要用明確地 `return` 來回傳東西。 +======= +Arrow functions are handy for simple actions, especially for one-liners. They come in two flavors: + +1. Without curly braces: `(...args) => expression` -- the right side is an expression: the function evaluates it and returns the result. Parentheses can be omitted, if there's only a single argument, e.g. `n => n*2`. +2. With curly braces: `(...args) => { body }` -- brackets allow us to write multiple statements inside the function, but we need an explicit `return` to return something. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/17-arrow-functions-basics/article.md diff --git a/1-js/02-first-steps/17-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md similarity index 67% rename from 1-js/02-first-steps/17-javascript-specials/article.md rename to 1-js/02-first-steps/18-javascript-specials/article.md index 2313a1059..84530c974 100644 --- a/1-js/02-first-steps/17-javascript-specials/article.md +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -57,7 +57,11 @@ for(;;) { 該指令必須放在 JavaScript 腳本的頂部或是函數的開頭。 +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md 沒有使用 `"use strict"`,一切依然能正常運行,但是某些特性會以 "兼容" 舊式的方式表現,我們通常偏好使用現代的方式。 +======= +Without `"use strict"`, everything still works, but some features behave in the old-fashioned, "compatible" way. We'd generally prefer the modern behavior. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md 語言內有些現代功能與特性(像是我們將來要學的類別)會隱式地啟用嚴格模式。 @@ -83,6 +87,7 @@ let x = 5; x = "John"; ``` +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md 有 8 種資料類型: - `number` 可以是浮點數或是整數。 @@ -94,6 +99,17 @@ x = "John"; - `object` 與 `symbol` -- 用於複雜的資料結構和唯一識別符號,我們還沒學習這個類型。 `typeof` 運算子會回傳值的類型,但有兩個例外: +======= +There are 8 data types: + +- `number` for both floating-point and integer numbers, +- `bigint` for integer numbers of arbitrary length, +- `string` for strings, +- `boolean` for logical values: `true/false`, +- `null` -- a type with a single value `null`, meaning "empty" or "does not exist", +- `undefined` -- a type with a single value `undefined`, meaning "not assigned", +- `object` and `symbol` -- for complex data structures and unique identifiers, we haven't learnt them yet. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md ```js typeof null == "object" // 語言本身的錯誤 @@ -106,6 +122,7 @@ typeof function(){} == "function" // 函式被特別對待 我們使用瀏覽器作為工作環境,所以基本的 UI 功能會是: +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md [`prompt(question, [default])`](mdn:api/Window/prompt) : 詢問一個 `question`,並回傳使用者輸入的內容,若使用者按下 "取消",則回傳 `null` 。 @@ -114,6 +131,16 @@ typeof function(){} == "function" // 函式被特別對待 [`alert(message)`](mdn:api/Window/alert) : 輸出一個 `message`。 +======= +[`prompt(question, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) +: Ask a `question`, and return either what the visitor entered or `null` if they clicked "cancel". + +[`confirm(question)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) +: Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`. + +[`alert(message)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) +: Output a `message`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md 所有這些函式都是 *模態*,他們會暫停程式碼執行並阻擋使用者與頁面互動,直到使用者回應模態。 @@ -146,8 +173,13 @@ JavaScript 支援以下運算子: 指定運算子(Assignments) : 簡單的指定: `a = b` 和結合其他運算的指定運算 `a *= 2`。 +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md 位元操作(Bitwise) : 位元操作在最底層的位元層面使用 32-bit 整數: 有需要時,請參閱 [docs](mdn:/JavaScript/Reference/Operators/Bitwise_Operators)。 +======= +Bitwise +: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md 條件運算(Conditional) : 唯一具有三個參數的操作: `cond ? resultA : resultB`. 如果 `cond` 是真值,則回傳 `resultA`否則回傳 `resultB`。 @@ -155,8 +187,16 @@ JavaScript 支援以下運算子: 邏輯運算子(Logical operators) : 邏輯的 AND `&&` 和 OR `||` 執行短路核定,然後回傳停止時的值(不一定為 `true`/`false`)。邏輯的 NOT `!` 轉換操作運算元成布林類型,並回傳相反的值。 +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md 比較運算子(Comparisons) : 相等性檢查 `==` 將不同類型的值轉換成數值(除了 `null` 和 `undefined`,它們除了彼此相等外,不跟任何人相等),所以這些是相等的: +======= +Nullish coalescing operator +: The `??` operator provides a way to choose a defined value from a list of variables. The result of `a ?? b` is `a` unless it's `null/undefined`, then `b`. + +Comparisons +: Equality check `==` for values of different types converts them to a number (except `null` and `undefined` that equal each other and nothing else), so these are equal: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md ```js run alert( 0 == false ); // true @@ -174,7 +214,11 @@ JavaScript 支援以下運算子: 其他運算子 : 有一些其他的運算子,像是逗號運算子。 +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md 更多資訊: , , . +======= +More in: , , , . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md ## 迴圈 @@ -215,7 +259,12 @@ let age = prompt('Your age?', 18); switch (age) { case 18: +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md alert("Won't work"); // 提示的結果是一個字串,而非數字 +======= + alert("Won't work"); // the result of prompt is a string, not a number + break; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md case "18": alert("This works!"); @@ -255,7 +304,11 @@ switch (age) { 3. 箭頭函式: ```js +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md // 表達式在右邊 +======= + // expression on the right side +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md let sum = (a, b) => a + b; // 或是帶有 { ... } 的多行語法,需要回傳: @@ -272,9 +325,15 @@ switch (age) { ``` +<<<<<<< HEAD:1-js/02-first-steps/17-javascript-specials/article.md - 函式可能有區域變數: 那些在函式內宣告的變數。 這類的變數其作用域只存在函式內部。 - 參數可以有預設值:`function sum(a = 1, b = 2) {...}`。 - 函式永遠會回傳一些東西。如果沒有 `return` 述語,則其結果為 `undefined`。 +======= +- Functions may have local variables: those declared inside its body or its parameter list. Such variables are only visible inside the function. +- Parameters can have default values: `function sum(a = 1, b = 2) {...}`. +- Functions always return something. If there's no `return` statement, then the result is `undefined`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/02-first-steps/18-javascript-specials/article.md 更多資訊:參見 。 diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md index ea68c2c8b..f19cdec67 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,14 +1,22 @@ +<<<<<<< HEAD # 在 Chrome 中除錯 +======= +# Debugging in the browser +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 在我們寫更複雜的程式碼之前,來說說怎麼除錯吧。 [除錯(Debugging)](https://en.wikipedia.org/wiki/Debugging) 是在腳本中找出並修正錯誤的過程。所有現代瀏覽器和大部分的環境都支援除錯工具 -- 開發工具中特別的使用者介面,用來讓除錯更為簡單。它也可以一步步追蹤程式碼執行步驟,以看出現在到底發生什麼事。 +<<<<<<< HEAD <<<<<<< HEAD 在此我們會使用 Chrome,因為它具有足夠的功能,大部分其它的瀏覽器也有相似的除錯流程。 ======= We'll be using Chrome here, because it has enough features, most other browsers have a similar process. >>>>>>> 71120d5968cec3103743014cf563e0f7c8045a16 +======= +We'll be using Chrome here, because it has enough features, most other browsers have a similar process. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## "Sources" 面板 @@ -32,6 +40,7 @@ The toggler button >>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](chrome-sources-console.svg) @@ -82,13 +102,22 @@ Now you could click the same toggler >>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 當我們只有在某個特定變數值或特別的函式參數需要中斷時,這會很方便。 ``` +<<<<<<< HEAD ## 除錯命令 +======= +## The command "debugger" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 我們也可以經由使用 `debugger` 命令來暫停程式碼,像這樣: @@ -104,9 +133,13 @@ function hello(name) { } ``` +<<<<<<< HEAD 當我們正在程式碼編輯器中,而不想切換到瀏覽器再到除錯器內查看腳本並設置中斷點時,這會非常方便。 ## 暫停且查看 +======= +Such command works only when the development tools are open, otherwise the browser ignores it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 在我們的例子中,`hello()` 在頁面載入時被呼叫,所以(在我們設置中斷點後)啟動除錯器最簡單的方法就是重新載入頁面。所以來按下 `key:F5`(Windows, Linux)或 `key:Cmd+R`(Mac)吧。 @@ -118,7 +151,11 @@ function hello(name) { 1. **`Watch` -- 顯示目前任何表達式的值。** +<<<<<<< HEAD 你可以點擊加號 `+` 並輸入表達式,除錯器會隨時顯示它的值,並在程序執行時自動重新計算。 +======= + You can click the plus `+` and input an expression. The debugger will show its value, automatically recalculating it in the process of execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 2. **`Call Stack` -- 顯示巢狀呼叫鏈。** @@ -178,11 +215,11 @@ There are buttons for it at the top of the right panel. Let's engage them. Clicking this again and again will step through all script statements one by one. -- "Step over": run the next command, but *don't go into a function*, hotkey `key:F10`. -: Similar to the previous the "Step" command, but behaves differently if the next statement is a function call. That is: not a built-in, like `alert`, but a function of our own. +: Similar to the previous "Step" command, but behaves differently if the next statement is a function call (not a built-in, like `alert`, but a function of our own). - The "Step" command goes into it and pauses the execution at its first line, while "Step over" executes the nested function call invisibly, skipping the function internals. + If we compare them, the "Step" command goes into a nested function call and pauses the execution at its first line, while "Step over" executes the nested function call invisibly to us, skipping the function internals. - The execution is then paused immediately after that function. + The execution is then paused immediately after that function call. That's good if we're not interested to see what happens inside the function call. @@ -198,8 +235,12 @@ There are buttons for it at the top of the right panel. Let's engage them. : That button does not move the execution. Just a mass on/off for breakpoints. -- enable/disable automatic pause in case of an error. +<<<<<<< HEAD : When enabled, and the developer tools is open, a script error automatically pauses the execution. Then we can analyze variables to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment. >>>>>>> en_upstream +======= +: When enabled, if the developer tools is open, an error during the script execution automatically pauses it. Then we can analyze variables in the debugger to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="繼續至此(Continue to here)" 右鍵點擊一行程式碼可以開啟有著一個非常棒選項的選單,"Continue to here"。 @@ -231,7 +272,11 @@ for (let i = 0; i < 5; i++) { 2. `debugger` 述語。 3. 錯誤(若開發者工具開著且按鈕 開啟著)。 +<<<<<<< HEAD 當暫停時,我們可以除錯 - 檢查變數和追蹤程式碼來看執行哪裡有問題。 +======= +When paused, we can debug: examine variables and trace the code to see where the execution goes wrong. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 開發者工具有更多尚未在此被介紹的選項,完整的操作手冊在 。 diff --git a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md index d3dabe155..3405ed6f8 100644 --- a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md +++ b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md @@ -10,11 +10,19 @@ function pow(x,n) // <- 引數間沒有空格 return result; } +<<<<<<< HEAD let x=prompt("x?",''), n=prompt("n?",'') // <-- 技術上雖可行, // 但最好還是拆成兩行,此外也沒有空格,又缺少分號 ; if (n<0) // <- 在 (n < 0) 缺少空格,而且應該在上方要有額外的一行 { // <- 大括號自成一行了 // 底下這行太長了,可以拆分成多行以增加可讀性 +======= +let x=prompt("x?",''), n=prompt("n?",'') // <-- technically possible, +// but better make it 2 lines, also there's no spaces and missing ; +if (n<=0) // <- no spaces inside (n <= 0), and should be extra line above it +{ // <- figure bracket on a separate line + // below - long lines can be split into multiple lines for improved readability +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert(`Power ${n} is not supported, please enter an integer number greater than zero`); } else // <- 可以像這樣 "} else {" 寫成一行 @@ -39,7 +47,7 @@ function pow(x, n) { let x = prompt("x?", ""); let n = prompt("n?", ""); -if (n < 0) { +if (n <= 0) { alert(`Power ${n} is not supported, please enter an integer number greater than zero`); } else { diff --git a/1-js/03-code-quality/02-coding-style/article.md b/1-js/03-code-quality/02-coding-style/article.md index 4cbc7f5d6..f02355fee 100644 --- a/1-js/03-code-quality/02-coding-style/article.md +++ b/1-js/03-code-quality/02-coding-style/article.md @@ -117,7 +117,11 @@ if ( 使用空格而非 tabs 有個好處是,空格比 tab 符號更具有縮排配置的彈性。 +<<<<<<< HEAD 舉個例,我們可以將某個開括號的引數們對其,像這樣: +======= + For instance, we can align the parameters with the opening bracket, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify show(parameters, @@ -286,7 +290,7 @@ function pow(x, n) { 一些受歡迎的選擇: -- [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml) +- [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) - [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) - [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) - [StandardJS](https://standardjs.com/) @@ -302,11 +306,19 @@ Linters 是可以自動檢查你程式碼風格,並給予改進建議的工具 這些是知名的 linting 工具: +<<<<<<< HEAD - [JSLint](http://www.jslint.com/) -- 第一批的 linter 之一。 - [JSHint](http://www.jshint.com/) -- 可以比 JSLint 做更多設定。 - [ESLint](http://eslint.org/) -- 也許是目前最新的一個。 以上皆可,作者是使用 [ESLint](http://eslint.org/)。 +======= +- [JSLint](https://www.jslint.com/) -- one of the first linters. +- [JSHint](https://jshint.com/) -- more settings than JSLint. +- [ESLint](https://eslint.org/) -- probably the newest one. + +All of them can do the job. The author uses [ESLint](https://eslint.org/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 大部分 linters 都有和熱門的編輯器整合:只要在編輯器內啟用插件並設置風格即可。 @@ -329,14 +341,18 @@ Linters 是可以自動檢查你程式碼風格,並給予改進建議的工具 }, "rules": { "no-console": 0, - "indent": ["warning", 2] + "indent": 2 } } ``` `extends` 這個指令表示配置是基於 "eslint:recommended" 來設定,此外我們還制定了自己的規則。 +<<<<<<< HEAD 或者也可以從網路下載風格規則集合並延伸使用,查看 以了解更多安裝細節。 +======= +It is also possible to download style rule sets from the web and extend them instead. See for more details about installation. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 甚至某些特定 IDEs 很方便的內建 linting,但不像 ESLint 那樣可以自行客製化。 diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index 191349b7e..808617edd 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -125,6 +125,7 @@ function addJuice(container) { 將函式參數和用途寫入文件 : 有個特殊的語法 [JSDoc](http://en.wikipedia.org/wiki/JSDoc) 可以幫函式寫文件:用途、參數和回傳值。 +<<<<<<< HEAD <<<<<<< HEAD 舉個例: ```js @@ -159,6 +160,27 @@ function pow(x, n) { ... } ``` +======= +For instance: +```js +/** + * Returns x raised to the n-th power. + * + * @param {number} x The number to raise. + * @param {number} n The power, must be a natural number. + * @return {number} x raised to the n-th power. + */ +function pow(x, n) { + ... +} +``` + +Such comments allow us to understand the purpose of the function and use it the right way without looking in its code. + +By the way, many editors like [WebStorm](https://www.jetbrains.com/webstorm/) can understand them as well and use them to provide autocomplete and some automatic code-checking. + +Also, there are tools like [JSDoc 3](https://github.com/jsdoc/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Such comments allow us to understand the purpose of the function and use it the right way without looking in its code. diff --git a/1-js/03-code-quality/04-ninja-code/article.md b/1-js/03-code-quality/04-ninja-code/article.md index 53a77a46b..5a4ef3259 100644 --- a/1-js/03-code-quality/04-ninja-code/article.md +++ b/1-js/03-code-quality/04-ninja-code/article.md @@ -1,7 +1,13 @@ # 忍者程式碼(Ninja code) +<<<<<<< HEAD ```quote author="孔子" 學而不思則罔,思而不學則殆。 +======= + +```quote author="Confucius (Analects)" +Learning without thought is labor lost; thought without learning is perilous. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 過去的程式忍者使用這些技巧來讓程式碼維護者的心思更加敏銳。 @@ -39,7 +45,11 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; 道隱無名。夫唯道,善貸且成。 ``` +<<<<<<< HEAD 另一個寫程式更快的方式是到處使用單一字母的變數名稱,像是 `a`、`b` 或 `c`。 +======= +Another way to code shorter is to use single-letter variable names everywhere. Like `a`, `b` or `c`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 短變數會像個真正的忍者處於森林中一樣,消失於程式碼之中,沒有人能夠使用編輯器的 "搜尋" 找到它們。即使有人辦得到,他們也無法 "破譯" `a` 或 `b` 名稱的意義。 @@ -99,9 +109,16 @@ i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ## 聰明的同義詞 +<<<<<<< HEAD ```quote author="孔子" 最困難的事是在黑暗的房間內尋找一隻黑貓,尤其當那裡根本沒有貓時。
(譯者註:這則引用應是個烏龍,孔子沒有說過這句話,可上網查詢相關來源) +======= +## Smart synonyms + +```quote author="Laozi (Tao Te Ching)" +The Tao that can be told is not the eternal Tao. The name that can be named is not the eternal name. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 對 *同件* 事情使用 *相似* 的名稱使得生活更為有趣,並向大眾顯示出你的創意。 diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 2294c993e..903b89e65 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -2,7 +2,11 @@ 自動化測試將會在未來的任務中被使用,且也會在真實專案上被廣泛地使用。 +<<<<<<< HEAD ## 為什麼我們需要測試? +======= +## Why do we need tests? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 當我們寫下一個函式,我們通常可以想像它應該要做些什麼:哪些參數會給出哪些結果。 @@ -51,7 +55,11 @@ describe("pow", function() { 從上面你可以發現一份規格有三個主要的建構區塊: `describe("title", function() { ... })` +<<<<<<< HEAD : 代表我們正在描述什麼樣的功能。在我們的例子中描述的是 `pow` 這個函式。用來組合 "workers" -- 也就是 `it` 區塊。 +======= +: What functionality we're describing? In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `it("use case description", function() { ... })` : 在 `it` 的標題中,我們使用 *人類可讀的方式* 來描述特定的使用情境,而第二個引數是用來測試它的函式。 @@ -67,6 +75,7 @@ describe("pow", function() { 開發流程通常看起來像這樣: +<<<<<<< HEAD 1. 寫下最初規格,測試最為基本的功能。 2. 建立最初實作。 3. 要檢查它是否運作,我們執行測試框架 [Mocha](http://mochajs.org/)(很快就有更多細節)來運行規格。當功能尚未完成時,錯誤會被顯示出來,我們得持續修正直到一切運作正常為止。 @@ -74,20 +83,39 @@ describe("pow", function() { 5. 我們加入更多使用情境到規格中,也許某些在目前的實作中尚未支援,所以測試將會失敗。 6. 回到 3,更新實作直到測試無誤為止。 7. 重複步驟 3-6 直到功能齊全為止。 +======= +1. An initial spec is written, with tests for the most basic functionality. +2. An initial implementation is created. +3. To check whether it works, we run the testing framework [Mocha](https://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works. +4. Now we have a working initial implementation with tests. +5. We add more use cases to the spec, probably not yet supported by the implementations. Tests start to fail. +6. Go to 3, update the implementation till tests give no errors. +7. Repeat steps 3-6 till the functionality is ready. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 所以,開發是 *迭代的*。我們寫下規格、實作它、保證通過測試、然後再寫更多測試、確認它們無誤等等。最後我們將擁有運行正常的實作與其測試。 在我們的實際例子中來看看這個開發流程。 +<<<<<<< HEAD 第一步已經完成了:對於 `pow` 我們已經有初始的規格。現在,在開始實作之前,來用些 JavaScript 函式庫運行測試,看看它們是否運作正常(全部都會失敗)。 +======= +The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use a few JavaScript libraries to run the tests, just to see that they are working (they will all fail). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 實作規格 在教程中我們將使用以下 JavaScript 函式庫做測試: +<<<<<<< HEAD - [Mocha](http://mochajs.org/) -- 核心框架:它提供包含 `describe` 和 `it` 的常見測試函式,與運行測試的主函式。 - [Chai](http://chaijs.com) -- 有許多斷言的函式庫。它可以使用很多不同的斷言,現在我們只需要 `assert.equal`。 - [Sinon](http://sinonjs.org/) -- 該函式庫可監控函式、模擬內建函式與其它功能,我們稍後會需要它。 +======= +- [Mocha](https://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. +- [Chai](https://www.chaijs.com/) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. +- [Sinon](https://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 這些函式庫用於瀏覽器端或是伺服器端的測試都很適合,在此我們用在瀏覽器上。 @@ -182,11 +210,15 @@ function pow(x, n) { [iframe height=250 src="pow-2" edit border="1"] +<<<<<<< HEAD <<<<<<< HEAD 如我們所預期,第二個測試失敗了。當然,我們的函式總是回傳 `8`,然而 `assert` 預期的是 `27`。 ======= As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `81`. >>>>>>> 71120d5968cec3103743014cf563e0f7c8045a16 +======= +As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `81`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 改進實作 @@ -342,6 +374,7 @@ describe("pow", function() { ```smart header="其它斷言" 請注意斷言 `assert.isNaN`:它用於確認 `NaN`。 +<<<<<<< HEAD [Chai](http://chaijs.com) 中也有其它斷言,例如: - `assert.equal(value1, value2)` -- 確認相等 `value1 == value2`。 @@ -350,6 +383,16 @@ describe("pow", function() { - `assert.isTrue(value)` -- 確認 `value === true` - `assert.isFalse(value)` -- 確認 `value === false` - ...完整的列表在 [文件](http://chaijs.com/api/assert/) +======= +There are other assertions in [Chai](https://www.chaijs.com/) as well, for instance: + +- `assert.equal(value1, value2)` -- checks the equality `value1 == value2`. +- `assert.strictEqual(value1, value2)` -- checks the strict equality `value1 === value2`. +- `assert.notEqual`, `assert.notStrictEqual` -- inverse checks to the ones above. +- `assert.isTrue(value)` -- checks that `value === true` +- `assert.isFalse(value)` -- checks that `value === false` +- ...the full list is in the [docs](https://www.chaijs.com/api/assert/) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 所以我們應該在 `pow` 多加幾行: diff --git a/1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js b/1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js index cad51d3ee..d3de82546 100644 --- a/1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/beforeafter.view/test.js @@ -1,5 +1,11 @@ describe("test", function() { + + // Mocha usually waits for the tests for 2 seconds before considering them wrong + + this.timeout(200000); // With this code we increase this - in this case to 200,000 milliseconds + // This is because of the "alert" function, because if you delay pressing the "OK" button the tests will not pass! + before(() => alert("Testing started – before all tests")); after(() => alert("Testing finished – after all tests")); diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 5964f900e..bef48079b 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,16 +1,27 @@ -# Polyfills +# Polyfills and transpilers +<<<<<<< HEAD JavaScript 語言穩定發展。有關該語言的新提案會定期出現,他們會被分析,而且如果被認為有價值,則會新增到 的列表中,然後進展至 [規格](http://www.ecma-international.org/publications/standards/Ecma-262.htm)。 +======= +The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 JavaScript 引擎背後的團隊對於首先實現什麼有自己的想法。他們可能會決定先實做草案中的提案,反而推延規格中已經存在的提案,因為它們不那麼有趣或純粹只是比較難做。 +<<<<<<< HEAD 因此,引擎僅實現部分標準是很普遍的。 是一個很好的頁面,可以看到目前對語言功能的支援狀態(它很多,我們還有很多要研究)。 +======= +So it's quite common for an engine to implement only part of the standard. + +A good page to see the current state of support for language features is (it's big, we have a lot to study yet). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -## Babel +As programmers, we'd like to use most recent features. The more good stuff - the better! +<<<<<<< HEAD 當我們使用語言的現代功能時,某些引擎可能無法支援這類型的程式碼。就像之前說的,並非所有功能都在各處實現。 Babel 是一帖良方。 @@ -55,3 +66,81 @@ alert('Press the "Play" button in the upper-right corner to run'); ``` Google Chrome 通常有最新的語言功能,可以很好地運行尖端的演示,而無需任何 transpiler,不過其他現代瀏覽器也運作得還可以。 +======= +On the other hand, how to make our modern code work on older engines that don't understand recent features yet? + +There are two tools for that: + +1. Transpilers. +2. Polyfills. + +Here, in this chapter, our purpose is to get the gist of how they work, and their place in web development. + +## Transpilers + +A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that translates source code to another source code. It can parse ("read and understand") modern code and rewrite it using older syntax constructs, so that it'll also work in outdated engines. + +E.g. JavaScript before year 2020 didn't have the "nullish coalescing operator" `??`. So, if a visitor uses an outdated browser, it may fail to understand the code like `height = height ?? 100`. + +A transpiler would analyze our code and rewrite `height ?? 100` into `(height !== undefined && height !== null) ? height : 100`. + +```js +// before running the transpiler +height = height ?? 100; + +// after running the transpiler +height = (height !== undefined && height !== null) ? height : 100; +``` + +Now the rewritten code is suitable for older JavaScript engines. + +Usually, a developer runs the transpiler on their own computer, and then deploys the transpiled code to the server. + +Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there. + +Modern project build systems, such as [webpack](https://webpack.js.org/), provide a means to run a transpiler automatically on every code change, so it's very easy to integrate into the development process. + +## Polyfills + +New language features may include not only syntax constructs and operators, but also built-in functions. + +For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23)` returns `1`. + +In some (very outdated) JavaScript engines, there's no `Math.trunc`, so such code will fail. + +As we're talking about new functions, not syntax changes, there's no need to transpile anything here. We just need to declare the missing function. + +A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations. + +For this particular case, the polyfill for `Math.trunc` is a script that implements it, like this: + +```js +if (!Math.trunc) { // if no such function + // implement it + Math.trunc = function(number) { + // Math.ceil and Math.floor exist even in ancient JavaScript engines + // they are covered later in the tutorial + return number < 0 ? Math.ceil(number) : Math.floor(number); + }; +} +``` + +JavaScript is a highly dynamic language. Scripts may add/modify any function, even built-in ones. + +One interesting polyfill library is [core-js](https://github.com/zloirock/core-js), which supports a wide range of features and allows you to include only the ones you need. + +## Summary + +In this chapter we'd like to motivate you to study modern and even "bleeding-edge" language features, even if they aren't yet well-supported by JavaScript engines. + +Just don't forget to use a transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). They'll ensure that the code works. + +For example, later when you're familiar with JavaScript, you can setup a code build system based on [webpack](https://webpack.js.org/) with the [babel-loader](https://github.com/babel/babel-loader) plugin. + +Good resources that show the current state of support for various features: +- - for pure JavaScript. +- - for browser-related functions. + +P.S. Google Chrome is usually the most up-to-date with language features, try it if a tutorial demo fails. Most tutorial demos work with any modern browser though. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md index fd350e546..55978b817 100644 --- a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md +++ b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md @@ -2,9 +2,15 @@ importance: 3 --- +<<<<<<< HEAD # 數值屬性都乘以 2 建立一個函式 `multiplyNumeric(obj)` 來把 `obj` 的所有數值屬性都乘以 2。 +======= +# Multiply numeric property values by 2 + +Create a function `multiplyNumeric(obj)` that multiplies all numeric property values of `obj` by `2`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉個例: diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 7a499429a..59b1e2559 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -2,7 +2,11 @@ 如我們從 所知,JavaScript 內有八種資料類型。其中七種被稱為 "原生類型(primitive)",因為它們的值只包含了單一種東西(是個字串或數值或什麼的)。 +<<<<<<< HEAD 相對的,物件被用來儲存使用鍵配對的多種資料群集與更為複雜的實體。在 JavaScript,物件幾乎滲入該語言的各個方面,所以我們必須在更深入其它主題前先理解物件。 +======= +As we know from the chapter , there are eight data types in JavaScript. Seven of them are called "primitive", because their values contain only a single thing (be it a string or a number or whatever). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 物件可以經由花括號 `{…}` 與一些可選的 *屬性(properties)* 來建立。一個屬性也是一組 "鍵(key):值(value)" 配對,其中 `key` 是一串字串(也被稱為 "屬性名稱(property name)"),而 `value` 可以是任何東西。 @@ -43,7 +47,11 @@ let user = { // 一個物件 ![user object](object-user.svg) +<<<<<<< HEAD 我們可以在任意時間增加、移除或讀取檔案。 +======= +We can add, remove and read files from it at any time. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 屬性值可以使用點號(dot notation)來存取: @@ -61,7 +69,11 @@ user.isAdmin = true; ![user object 2](object-user-isadmin.svg) +<<<<<<< HEAD 要移除一個屬性,我們可以用 `delete` 運算子: +======= +To remove a property, we can use the `delete` operator: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js delete user.age; @@ -102,7 +114,13 @@ let user = { user.likes birds = true ``` +<<<<<<< HEAD JavaScript 無法理解這樣的語法。它會解析到 `user.likes`,接者遇到預期外的 `birds` 時,給予語法錯誤。 +======= +JavaScript doesn't understand that. It thinks that we address `user.likes`, and then gives a syntax error when comes across unexpected `birds`. + +The dot requires the key to be a valid variable identifier. That implies: contains no spaces, doesn't start with a digit and doesn't include special characters (`$` and `_` are allowed). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 這是因為句點需要鍵是個有效的變數識別符,也就是:沒有空格、不是由數值為開頭,以及不包含特殊字元(`$` 和 `_` 可被允許)。 @@ -162,7 +180,11 @@ alert( user.key ) // undefined ### 計算屬性(Computed properties) +<<<<<<< HEAD 我們可以在物件字面值使用方括號,這被稱為 *計算屬性(computed properties)*。 +======= +We can use square brackets in an object literal, when creating an object. That's called *computed properties*. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉個例: @@ -203,10 +225,15 @@ let bag = { }; ``` +<<<<<<< HEAD 方括號比點號更為強大,它允許任意屬性名稱和變數,但寫起來也較累贅。 +======= +Square brackets are much more powerful than dot notation. They allow any property names and variables. But they are also more cumbersome to write. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 所以大多時候,當屬性名稱已知且單純時,用句點就好,而若我們需要某些較複雜的東西時,再轉用方括號即可。 +<<<<<<< HEAD ````smart header="保留字可被允許用於屬性名稱" 變數不能使用語言保留字作為名稱,像是 "for"、"let"、"return" 等等。 @@ -246,13 +273,24 @@ alert(obj.__proto__); // [object Object],與預期的不同 在真正寫程式時我們常使用現存的變數作為屬性名稱的值。 舉個例: +======= +## Property value shorthand + +In real code, we often use existing variables as values for property names. + +For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function makeUser(name, age) { return { name: name, age: age, +<<<<<<< HEAD // ...其它屬性 +======= + // ...other properties +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; } @@ -268,8 +306,13 @@ alert(user.name); // John function makeUser(name, age) { *!* return { +<<<<<<< HEAD name, // 和 name: name 相同 age, // 和 age: age 相同 +======= + name, // same as name: name + age, // same as age: age +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // ... }; */!* @@ -286,6 +329,7 @@ let user = { >>> ``` +<<<<<<< HEAD ## 屬性值命名限制 屬性值的名稱(keys)必須是字串或是符號(一種特殊類型的識別符,之後會介紹)。 @@ -311,6 +355,17 @@ alert( obj[0] ); // test (同個屬性值) 但對於物件的屬性值,就沒有這樣的限制。任何的名稱都可以: ```js run +======= + +## Property names limitations + +As we already know, a variable cannot have a name equal to one of the language-reserved words like "for", "let", "return" etc. + +But for an object property, there's no such restriction: + +```js run +// these properties are all right +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 let obj = { for: 1, let: 2, @@ -320,6 +375,7 @@ let obj = { alert( obj.for + obj.let + obj.return ); // 6 ``` +<<<<<<< HEAD 我們可以用任何字串當作鍵值,但有個特殊的屬性值 `__proto__` 由於歷史因素,擁有不同的待遇。 舉例來說,我們沒辦法將一個非物件的值賦值給它: @@ -346,6 +402,41 @@ alert(obj.__proto__); // [object Object] - 該值是一個物件, 不如預期 ## 存在性確認,"in" 運作子 有個值得注意的物件功能是我們可以存取任意屬性,就算屬性不存在也不會有任何錯誤!存取一個不存在的屬性只會回傳 `undefined`,這提供了一個非常常見的方式來檢測屬性是否存在 -- 取得它並跟 undefined 做比較: +======= +In short, there are no limitations on property names. They can be any strings or symbols (a special type for identifiers, to be covered later). + +Other types are automatically converted to strings. + +For instance, a number `0` becomes a string `"0"` when used as a property key: + +```js run +let obj = { + 0: "test" // same as "0": "test" +}; + +// both alerts access the same property (the number 0 is converted to string "0") +alert( obj["0"] ); // test +alert( obj[0] ); // test (same property) +``` + +There's a minor gotcha with a special property named `__proto__`. We can't set it to a non-object value: + +```js run +let obj = {}; +obj.__proto__ = 5; // assign a number +alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended +``` + +As we see from the code, the assignment to a primitive `5` is ignored. + +We'll cover the special nature of `__proto__` in [subsequent chapters](info:prototype-inheritance), and suggest the [ways to fix](info:prototype-methods) such behavior. + +## Property existence test, "in" operator + +A notable feature of objects in JavaScript, compared to many other languages, is that it's possible to access any property. There will be no error if the property doesn't exist! + +Reading a non-existing property just returns `undefined`. So we can easily test whether the property exists: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = {}; @@ -353,9 +444,13 @@ let user = {}; alert( user.noSuchProperty === undefined ); // true 代表 "沒有這個屬性" ``` +<<<<<<< HEAD 同樣還有一個特殊的運算子 `"in"` 用來確認屬性是否存在。 語法是: +======= +There's also a special operator `"in"` for that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js "key" in object @@ -372,17 +467,30 @@ alert( "blabla" in user ); // false,user.blabla 不存在 請注意 `in` 的左側必須要是個 *屬性名稱*,通常是個置於引號內的字串。 +<<<<<<< HEAD 若我們忽略引號,代表將使用某個變數包含的實際名稱來測試。舉個例: +======= +If we omit quotes, that means a variable should contain the actual name to be tested. For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { age: 30 }; let key = "age"; +<<<<<<< HEAD alert( *!*key*/!* in user ); // true,名稱由 key 而來,並檢查該屬性 ``` ````smart header="對儲存 `undefined` 的屬性使用 \"in\"" 通常,使用嚴格比較 `"=== undefined"` 來確認屬性是否存在是沒問題的,然而有個特殊情況這麼做會失敗,但 `"in"` 能正確運作。 +======= +alert( *!*key*/!* in user ); // true, property "age" exists +``` + +Why does the `in` operator exist? Isn't it enough to compare against `undefined`? + +Well, most of the time the comparison with `undefined` works fine. But there's a special case when it fails, but `"in"` works correctly. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 就是當物件屬性存在,卻儲存著 `undefined` 時: @@ -396,12 +504,21 @@ alert( obj.test ); // 這是 undefined,所以屬性不存在? alert( "test" in obj ); // true,該屬性存在! ``` +<<<<<<< HEAD 在上面的程式碼中,屬性 `obj.test` 技術上來說是存在的,所以 `in` 運算子運作正確。 類似的情況非常少發生,因為 `undefined` 通常不會被指定,我們對於 "未知" 或 "空白" 的值大多會使用 `null`,所以 `in` 運算子可以算是程式碼的過客。 ```` ## "for..in" 迴圈 +======= +In the code above, the property `obj.test` technically exists. So the `in` operator works right. + +Situations like this happen very rarely, because `undefined` should not be explicitly assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. + + +## The "for..in" loop [#forin] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 要走遍物件的所有鍵,有個特殊的迴圈型式可用: `for..in`。這是完全不同於我們曾經讀過的 `for(;;)` 的構造。 @@ -434,9 +551,13 @@ for (let key in user) { 同樣地,我們這裡可以使用另一個變數名稱而不用 `key`。例如,`"for (let prop in obj)"` 也很廣泛地使用。 +<<<<<<< HEAD ### 像物件一樣排序 物件是否有序?換句話說,若我們巡迴物件,是否所有屬性都能同樣以當初加入的順序取得呢?我們能依賴這性質嗎? +======= +### Ordered like an object +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 簡短的答案是:"特異的順序":整數屬性為排序過的,其它則按照加入時的順序,細節在底下。 @@ -458,7 +579,11 @@ for (let code in codes) { */!* ``` +<<<<<<< HEAD 此物件被用來建議一個選項清單給使用者,假若我們正在建立以德國用戶為主的網站,那我們也許想要 `49` 為清單第一個選項。 +======= +The object may be used to suggest a list of options to the user. If we're making a site mainly for a German audience then we probably want `49` to be the first. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 但若我們執行程式,我們會看到完全不同的情景: @@ -470,6 +595,7 @@ for (let code in codes) { ````smart header="整數屬性?那是什麼?" 這邊的 "整數屬性" 詞彙代表著某種字串,可以在不被變更的情況下轉換為整數。 +<<<<<<< HEAD 所以,"49" 是整數屬性名稱,因為當它被轉成整數值再轉回來時,依然保持一樣。但 "+49" 和 "1.2" 就不行: ```js run @@ -477,6 +603,16 @@ for (let code in codes) { alert( String(Math.trunc(Number("49"))) ); // "49",一樣,是整數屬性 alert( String(Math.trunc(Number("+49"))) ); // "49",跟 "+49" 不一樣 ⇒ 非整數屬性 alert( String(Math.trunc(Number("1.2"))) ); // "1",跟 "1.2" 不一樣 ⇒ 非整數屬性 +======= +So, `"49"` is an integer property name, because when it's transformed to an integer number and back, it's still the same. But `"+49"` and `"1.2"` are not: + +```js run +// Number(...) explicitly converts to a number +// Math.trunc is a built-in function that removes the decimal part +alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property +alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property +alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```` @@ -517,6 +653,7 @@ for (let code in codes) { 現在它們會如預期般的運作了。 +<<<<<<< HEAD ## 依照參考複製(Copying by reference) 物件與原生類型之間有個本質上的差異,就是物件是 "依照參考(by reference)" 被儲存和複製。 @@ -781,6 +918,9 @@ There's a standard algorithm for deep cloning that handles the case above and mo 它們儲存屬性(鍵值配對),其中: - 屬性鍵必須為字串或符號(symbols)(通常為字串)。 - 值可以為任意類型。 +======= +## Summary +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 要存取屬性,我們可以使用: - 點號:`obj.property`。 @@ -791,10 +931,17 @@ There's a standard algorithm for deep cloning that handles the case above and mo - 要確認某個鍵的屬性是否存在:`"key" in obj`。 - 要迭代整個物件:`for (let key in obj)` 迴圈。 +<<<<<<< HEAD 物件經由參考被被指定與複製,換句話說,變數儲存的並非 "物件值",而是值的 "參考"(記憶體位址)。所以複製該變數或將它作為函式引數傳遞都只會複製參考,而非物件。所有經由複製的參考所做的操作(像是 新增/移除 屬性),都會在同一個物件上進行。 +======= +To access a property, we can use: +- The dot notation: `obj.property`. +- Square brackets notation `obj["property"]`. Square brackets allow taking the key from a variable, like `obj[varWithKey]`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 要建立一份 "真正的複本"(克隆體),我們可以使用 `Object.assign` 或 [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep)。 +<<<<<<< HEAD 我們這章所學到的都被稱為 "普通物件(plain object)" 或就叫 `物件(Object)`。 JavaScript 中還有更多其它種類的物件: @@ -803,6 +950,9 @@ JavaScript 中還有更多其它種類的物件: - `Date` 用來儲存日期與時間的資訊, - `Error` 用來儲存關於錯誤的資訊。 - ...等等。 +======= +What we've studied in this chapter is called a "plain object", or just `Object`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 它們有著自身的特殊特性,我們晚點會讀到。有時候人們說了像是 "矩陣類型" 或 "日期類型",但正式上這些都沒有自己的類型,而是屬於一個 "物件" 資料類型,且以多種方式對其延伸。 diff --git a/1-js/04-object-basics/01-object/variable-copy-value.svg b/1-js/04-object-basics/01-object/variable-copy-value.svg deleted file mode 100644 index e09f521fe..000000000 --- a/1-js/04-object-basics/01-object/variable-copy-value.svg +++ /dev/null @@ -1 +0,0 @@ -"Hello!"message"Hello!"phrase \ No newline at end of file diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md new file mode 100644 index 000000000..e80f748ab --- /dev/null +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -0,0 +1,325 @@ +# Object references and copying + +One of the fundamental differences of objects versus primitives is that objects are stored and copied "by reference", whereas primitive values: strings, numbers, booleans, etc -- are always copied "as a whole value". + +That's easy to understand if we look a bit under the hood of what happens when we copy a value. + +Let's start with a primitive, such as a string. + +Here we put a copy of `message` into `phrase`: + +```js +let message = "Hello!"; +let phrase = message; +``` + +As a result we have two independent variables, each one storing the string `"Hello!"`. + +![](variable-copy-value.svg) + +Quite an obvious result, right? + +Objects are not like that. + +**A variable assigned to an object stores not the object itself, but its "address in memory" -- in other words "a reference" to it.** + +Let's look at an example of such a variable: + +```js +let user = { + name: "John" +}; +``` + +And here's how it's actually stored in memory: + +![](variable-contains-reference.svg) + +The object is stored somewhere in memory (at the right of the picture), while the `user` variable (at the left) has a "reference" to it. + +We may think of an object variable, such as `user`, like a sheet of paper with the address of the object on it. + +When we perform actions with the object, e.g. take a property `user.name`, the JavaScript engine looks at what's at that address and performs the operation on the actual object. + +Now here's why it's important. + +**When an object variable is copied, the reference is copied, but the object itself is not duplicated.** + +For instance: + +```js no-beautify +let user = { name: "John" }; + +let admin = user; // copy the reference +``` + +Now we have two variables, each storing a reference to the same object: + +![](variable-copy-reference.svg) + +As you can see, there's still one object, but now with two variables that reference it. + +We can use either variable to access the object and modify its contents: + +```js run +let user = { name: 'John' }; + +let admin = user; + +*!* +admin.name = 'Pete'; // changed by the "admin" reference +*/!* + +alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference +``` + +It's as if we had a cabinet with two keys and used one of them (`admin`) to get into it and make changes. Then, if we later use another key (`user`), we are still opening the same cabinet and can access the changed contents. + +## Comparison by reference + +Two objects are equal only if they are the same object. + +For instance, here `a` and `b` reference the same object, thus they are equal: + +```js run +let a = {}; +let b = a; // copy the reference + +alert( a == b ); // true, both variables reference the same object +alert( a === b ); // true +``` + +And here two independent objects are not equal, even though they look alike (both are empty): + +```js run +let a = {}; +let b = {}; // two independent objects + +alert( a == b ); // false +``` + +For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake. + +````smart header="Const objects can be modified" +An important side effect of storing objects as references is that an object declared as `const` *can* be modified. + +For instance: + +```js run +const user = { + name: "John" +}; + +*!* +user.name = "Pete"; // (*) +*/!* + +alert(user.name); // Pete +``` + +It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change. + +In other words, the `const user` gives an error only if we try to set `user=...` as a whole. + +That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter . +```` + +## Cloning and merging, Object.assign [#cloning-and-merging-object-assign] + +So, copying an object variable creates one more reference to the same object. + +But what if we need to duplicate an object? + +We can create a new object and replicate the structure of the existing one, by iterating over its properties and copying them on the primitive level. + +Like this: + +```js run +let user = { + name: "John", + age: 30 +}; + +*!* +let clone = {}; // the new empty object + +// let's copy all user properties into it +for (let key in user) { + clone[key] = user[key]; +} +*/!* + +// now clone is a fully independent object with the same content +clone.name = "Pete"; // changed the data in it + +alert( user.name ); // still John in the original object +``` + +We can also use the method [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). + +The syntax is: + +```js +Object.assign(dest, ...sources) +``` + +- The first argument `dest` is a target object. +- Further arguments is a list of source objects. + +It copies the properties of all source objects into the target `dest`, and then returns it as the result. + +For example, we have `user` object, let's add a couple of permissions to it: + +```js run +let user = { name: "John" }; + +let permissions1 = { canView: true }; +let permissions2 = { canEdit: true }; + +*!* +// copies all properties from permissions1 and permissions2 into user +Object.assign(user, permissions1, permissions2); +*/!* + +// now user = { name: "John", canView: true, canEdit: true } +alert(user.name); // John +alert(user.canView); // true +alert(user.canEdit); // true +``` + +If the copied property name already exists, it gets overwritten: + +```js run +let user = { name: "John" }; + +Object.assign(user, { name: "Pete" }); + +alert(user.name); // now user = { name: "Pete" } +``` + +We also can use `Object.assign` to perform a simple object cloning: + +```js run +let user = { + name: "John", + age: 30 +}; + +*!* +let clone = Object.assign({}, user); +*/!* + +alert(clone.name); // John +alert(clone.age); // 30 +``` + +Here it copies all properties of `user` into the empty object and returns it. + +There are also other methods of cloning an object, e.g. using the [spread syntax](info:rest-parameters-spread) `clone = {...user}`, covered later in the tutorial. + +## Nested cloning + +Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. + +Like this: +```js run +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } +}; + +alert( user.sizes.height ); // 182 +``` + +Now it's not enough to copy `clone.sizes = user.sizes`, because `user.sizes` is an object, and will be copied by reference, so `clone` and `user` will share the same sizes: + +```js run +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } +}; + +let clone = Object.assign({}, user); + +alert( user.sizes === clone.sizes ); // true, same object + +// user and clone share sizes +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 60, get the result from the other one +``` + +To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning. + + +### structuredClone + +The call `structuredClone(object)` clones the `object` with all nested properties. + +Here's how we can use it in our example: + +```js run +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } +}; + +*!* +let clone = structuredClone(user); +*/!* + +alert( user.sizes === clone.sizes ); // false, different objects + +// user and clone are totally unrelated now +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 50, not related +``` + +The `structuredClone` method can clone most data types, such as objects, arrays, primitive values. + +It also supports circular references, when an object property references the object itself (directly or via a chain or references). + +For instance: + +```js run +let user = {}; +// let's create a circular reference: +// user.me references the user itself +user.me = user; + +let clone = structuredClone(user); +alert(clone.me === clone); // true +``` + +As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well. + +Although, there are cases when `structuredClone` fails. + +For instance, when an object has a function property: + +```js run +// error +structuredClone({ + f: function() {} +}); +``` + +Function properties aren't supported. + +To handle such complex cases we may need to use a combination of cloning methods, write custom code or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). + +## Summary + +Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object itself. + +All operations via copied references (like adding/removing properties) are performed on the same single object. + +To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function `structuredClone` or use a custom cloning implementation, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). diff --git a/1-js/04-object-basics/01-object/variable-contains-reference.svg b/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg similarity index 63% rename from 1-js/04-object-basics/01-object/variable-contains-reference.svg rename to 1-js/04-object-basics/02-object-copy/variable-contains-reference.svg index a59c9210e..267f04578 100644 --- a/1-js/04-object-basics/01-object/variable-contains-reference.svg +++ b/1-js/04-object-basics/02-object-copy/variable-contains-reference.svg @@ -1 +1 @@ -username \ No newline at end of file +username \ No newline at end of file diff --git a/1-js/04-object-basics/01-object/variable-copy-reference.svg b/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg similarity index 73% rename from 1-js/04-object-basics/01-object/variable-copy-reference.svg rename to 1-js/04-object-basics/02-object-copy/variable-copy-reference.svg index 5d0bc1594..a847fb200 100644 --- a/1-js/04-object-basics/01-object/variable-copy-reference.svg +++ b/1-js/04-object-basics/02-object-copy/variable-copy-reference.svg @@ -1 +1 @@ -useradminname \ No newline at end of file +useradminname \ No newline at end of file diff --git a/1-js/04-object-basics/02-object-copy/variable-copy-value.svg b/1-js/04-object-basics/02-object-copy/variable-copy-value.svg new file mode 100644 index 000000000..0d6ca67bc --- /dev/null +++ b/1-js/04-object-basics/02-object-copy/variable-copy-value.svg @@ -0,0 +1 @@ +"Hello!"message"Hello!"phrase \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md similarity index 63% rename from 1-js/04-object-basics/02-garbage-collection/article.md rename to 1-js/04-object-basics/03-garbage-collection/article.md index 37187ac70..692e09632 100644 --- a/1-js/04-object-basics/02-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -14,16 +14,27 @@ JavaScript 中最重要的記憶體管理原則是 *可達性(reachability)* 舉個例: +<<<<<<< HEAD:1-js/04-object-basics/02-garbage-collection/article.md - 目前函式的區域變數與參數。 - 目前巢狀呼叫鏈上的其它函式所擁有的變數與參數。 - 全域變數。 - (還有其它些內部的東西) +======= + - The currently executing function, its local variables and parameters. + - Other functions on the current chain of nested calls, their local variables and parameters. + - Global variables. + - (there are some other, internal ones as well) +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/03-garbage-collection/article.md 這些值被稱為 *根(roots)*。 2. 當某個值可以從根經由參考或是參考鍊抵達,那也會被視為可達。 +<<<<<<< HEAD:1-js/04-object-basics/02-garbage-collection/article.md 舉個例,若區域變數有某個物件且該物件有個屬性參考至另一個物件,則後者的物件就會被視為可達,且它所參考到的所有東西也會被視為可達,底下會有詳細的例子。 +======= + For instance, if there's an object in a global variable, and that object has a property referencing another object, *that* object is considered reachable. And those that it references are also reachable. Detailed examples to follow. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/03-garbage-collection/article.md JavaScript 引擎有個背景程序稱為 [垃圾回收器(garbage collector)](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)),它監視所有物件並移除那些變成不可達的東西。 @@ -75,7 +86,11 @@ let admin = user; user = null; ``` +<<<<<<< HEAD:1-js/04-object-basics/02-garbage-collection/article.md ...則物件經由 `admin` 這個全域變數依然是可達的,所以它還是會在記憶體中。若我們也覆蓋了 `admin`,則它就會被移除掉。 +======= +...Then the object is still reachable via `admin` global variable, so it must stay in memory. If we overwrite `admin` too, then it can be removed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/03-garbage-collection/article.md ## 互相連結的物件 @@ -170,11 +185,19 @@ family = null; ![](garbage-collection-2.svg) +<<<<<<< HEAD:1-js/04-object-basics/02-garbage-collection/article.md 然後標記它們的參考: ![](garbage-collection-3.svg) ...若還有參考就再標記: +======= +Then we follow their references and mark referenced objects: + +![](garbage-collection-3.svg) + +...And continue to follow further references, while possible: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/03-garbage-collection/article.md ![](garbage-collection-4.svg) @@ -184,13 +207,23 @@ family = null; 我們也可以將此過程想像成從根倒出一大桶油漆,流經所有參考並標記所有可達的物件。沒被標記到的就會被移除。 +<<<<<<< HEAD:1-js/04-object-basics/02-garbage-collection/article.md 這就是垃圾回收器如何運作的概念。JavaScript 引擎套用許多優化來讓它跑得更快,且不影響執行。 +======= +That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not introduce any delays into the code execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/03-garbage-collection/article.md 某些優化的地方: +<<<<<<< HEAD:1-js/04-object-basics/02-garbage-collection/article.md - **分代回收(Generational collection)** -- 物件被分為兩個集合:"舊的" 和 "新的"。很多物件快速地出現做完事情就消失,它們可被積極的清理。那些存活夠久並變得 "老舊" 的物件將會減少檢查。 - **遞增回收(Incremental collection)** -- 若存在很多物件,且我們試著一次拜訪並標示完成,那可能會花些時間並在執行上產生看得見的延遲。所以引擎會試圖將垃圾回收拆成多個部分,然後每個部分會一個接一個被分開執行。在它們之間將需要些額外的標記來追蹤變動,但我們就可以只有些許微小而非整個很大的延遲。 - **閒時回收(Idle-time collection)** -- 垃圾回收器試著只在 CPU 閒下來的時候執行,來減少對執行可能的影響。 +======= +- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". In typical code, many objects have a short life span: they appear, do their job and die fast, so it makes sense to track new objects and clear the memory from them if that's the case. Those that survive for long enough, become "old" and are examined less often. +- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine splits the whole set of existing objects into multiple parts. And then clear these parts one after another. There are many small garbage collections instead of a total one. That requires some extra bookkeeping between them to track changes, but we get many tiny delays instead of a big one. +- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/03-garbage-collection/article.md 還有其它垃圾回收演算法的優化與方式。儘管我想描述多點也得先停下來,因為不同引擎實作不同的調整與技巧。並且更重要的是,隨著引擎的開發會隨時變動,所以若沒有實際需要而 "事先" 知道這麼深入其實不太值得。當然,若你有著純粹有興趣想了解,那底下有些為你準備的連結。 @@ -198,17 +231,31 @@ family = null; 主要該知道的是: +<<<<<<< HEAD:1-js/04-object-basics/02-garbage-collection/article.md - 垃圾回收是自動被執行的,我們不能強迫或預防它執行。 - 當物件為可達時會被保留在記憶體中。 - 被參考跟可(由根)抵達是不一樣的:一組互相連結的物件可以整個變成不可達。 +======= +- Garbage collection is performed automatically. We cannot force or prevent it. +- Objects are retained in memory while they are reachable. +- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole, as we've seen in the example above. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/03-garbage-collection/article.md 現代化引擎實作垃圾回收的進階演算法。 有本通用的書 "The Garbage Collection Handbook: The Art of Automatic Memory Management"(R. Jones et al)介紹了一部分。 +<<<<<<< HEAD:1-js/04-object-basics/02-garbage-collection/article.md 若你對低階程式運作感興趣,在這篇文章 [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection) 有關於 V8 垃圾回收的更詳細資訊。 [V8 blog](https://v8.dev/) 也不時會發表關於記憶體管理變更的文章。當然,要學習垃圾回收,你最好經由學習關於 V8 的內部知識來準備,並閱讀 [Vyacheslav Egorov](http://mrale.ph) 的部落格,他是負責 V8 的工程師之一。我之所以說 "V8",是因為它最容易被網路文章所提到。對於其它引擎,許多作法雖然是相似的,但垃圾回收在很多方面會不太一樣。 當你需要底層優化時,有關於引擎的更深入知識是很好的。在你熟悉了此語言之後,將它作為下一步的學習計畫會很明智。 +======= +If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). + +The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](https://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. + +In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/03-garbage-collection/article.md diff --git a/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg b/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg new file mode 100644 index 000000000..a582ca64b --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family-delete-refs.svg @@ -0,0 +1 @@ +<global variable>ObjectObjectwifefamilyname: "John"name: "Ann"motherObjectfatherhusband \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family-no-family.svg b/1-js/04-object-basics/03-garbage-collection/family-no-family.svg new file mode 100644 index 000000000..c73dd6a48 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family-no-family.svg @@ -0,0 +1 @@ +<global>ObjectObjectfatherwifename: "John"name: "Ann"motherObjecthusbandfamily: null \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father-2.svg b/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg similarity index 61% rename from 1-js/04-object-basics/02-garbage-collection/family-no-father-2.svg rename to 1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg index 11f4ada35..6bd13c0e8 100644 --- a/1-js/04-object-basics/02-garbage-collection/family-no-father-2.svg +++ b/1-js/04-object-basics/03-garbage-collection/family-no-father-2.svg @@ -1 +1 @@ -Objectfamilyname: "Ann"motherObject<global> \ No newline at end of file +Objectfamilyname: "Ann"motherObject<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family-no-father.svg b/1-js/04-object-basics/03-garbage-collection/family-no-father.svg new file mode 100644 index 000000000..fd1f20607 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family-no-father.svg @@ -0,0 +1 @@ +ObjectObjectwifefamilyname: "John"name: "Ann"motherObject<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/family.svg b/1-js/04-object-basics/03-garbage-collection/family.svg new file mode 100644 index 000000000..fd0534874 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/family.svg @@ -0,0 +1 @@ +ObjectObjectfatherwifefamilyname: "John"name: "Ann"motherObjecthusband<global variable> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg new file mode 100644 index 000000000..5cac52e9a --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-1.svg @@ -0,0 +1 @@ +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg new file mode 100644 index 000000000..7dd3a693a --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-2.svg @@ -0,0 +1 @@ +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg new file mode 100644 index 000000000..106057787 --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-3.svg @@ -0,0 +1 @@ +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg new file mode 100644 index 000000000..bd485adee --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-4.svg @@ -0,0 +1 @@ +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg b/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg new file mode 100644 index 000000000..2d85432bc --- /dev/null +++ b/1-js/04-object-basics/03-garbage-collection/garbage-collection-5.svg @@ -0,0 +1 @@ +<global>unreachables \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg similarity index 67% rename from 1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.svg rename to 1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg index dc4cce1c7..191324354 100644 --- a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.svg +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john-admin.svg @@ -1 +1 @@ -username: "John"Objectadmin<global> \ No newline at end of file +username: "John"Objectadmin<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg similarity index 75% rename from 1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.svg rename to 1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg index e75b8d465..07914a9ca 100644 --- a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.svg +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john-lost.svg @@ -1 +1 @@ -name: "John"Objectuser: null<global> \ No newline at end of file +name: "John"Objectuser: null<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john.svg b/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg similarity index 70% rename from 1-js/04-object-basics/02-garbage-collection/memory-user-john.svg rename to 1-js/04-object-basics/03-garbage-collection/memory-user-john.svg index 0191e3f07..15bd51afb 100644 --- a/1-js/04-object-basics/02-garbage-collection/memory-user-john.svg +++ b/1-js/04-object-basics/03-garbage-collection/memory-user-john.svg @@ -1 +1 @@ -username: "John"Object<global> \ No newline at end of file +username: "John"Object<global> \ No newline at end of file diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md index fc769eddd..2c2e90800 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md @@ -8,7 +8,7 @@ function makeUser() { name: "John", ref: this }; -}; +} let user = makeUser(); @@ -23,7 +23,22 @@ alert( user.ref.name ); // Error: Cannot read property 'name' of undefined 因此 `ref: this` 實際上拿到當前函式的 `this`。 +<<<<<<< HEAD 這邊有個相反的情況: +======= +We can rewrite the function and return the same `this` with `undefined` value: + +```js run +function makeUser(){ + return this; // this time there's no object literal +} + +alert( makeUser().name ); // Error: Cannot read property 'name' of undefined +``` +As you can see the result of `alert( makeUser().name )` is the same as the result of `alert( user.ref.name )` from the previous example. + +Here's the opposite case: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function makeUser() { @@ -35,7 +50,7 @@ function makeUser() { } */!* }; -}; +} let user = makeUser(); diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md index 601e44d32..78f8d1399 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md @@ -14,7 +14,7 @@ function makeUser() { name: "John", ref: this }; -}; +} let user = makeUser(); diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js index 1f71eda4c..4decb76dc 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js +++ b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js @@ -15,6 +15,11 @@ describe("calculator", function() { afterEach(function() { prompt.restore(); }); + + it('the read get two values and saves them as object properties', function () { + assert.equal(calculator.a, 2); + assert.equal(calculator.b, 3); + }); it("the sum is 5", function() { assert.equal(calculator.sum(), 5); diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/task.md b/1-js/04-object-basics/04-object-methods/7-calculator/task.md index 34ea109b2..32d222e42 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/task.md +++ b/1-js/04-object-basics/04-object-methods/7-calculator/task.md @@ -6,9 +6,15 @@ importance: 5 建立一個物件 `calculator` 並有三個方法在內: +<<<<<<< HEAD - `read()` 提示輸入(prompt)兩個值並將它們儲存到物件屬性內。 - `sum()` 回傳儲存值的總和。 - `mul()` 相乘儲存的值並回傳結果。 +======= +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. +- `sum()` returns the sum of saved values. +- `mul()` multiplies saved values and returns the result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let calculator = { @@ -21,4 +27,3 @@ alert( calculator.mul() ); ``` [demo] - diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js index e98fe6410..a35c009cc 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js @@ -11,5 +11,6 @@ let ladder = { }, showStep: function() { alert(this.step); + return this; } }; \ No newline at end of file diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js index a2b17fcc4..b4f2459b7 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js @@ -32,6 +32,14 @@ describe('Ladder', function() { it('down().up().up().up() ', function() { assert.equal(ladder.down().up().up().up().step, 2); }); + + it('showStep() should return this', function() { + assert.equal(ladder.showStep(), ladder); + }); + + it('up().up().down().showStep().down().showStep()', function () { + assert.equal(ladder.up().up().down().showStep().down().showStep().step, 0) + }); after(function() { ladder.step = 0; diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md index bed0cd667..a2556bd15 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md @@ -21,9 +21,9 @@ let ladder = { return this; */!* } -} +}; -ladder.up().up().down().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` 我們也可以在每行都使用單獨的呼叫,對於長鏈接而言這樣更具可讀性: @@ -33,8 +33,8 @@ ladder .up() .up() .down() - .up() + .showStep() // 1 .down() - .showStep(); // 1 + .showStep(); // 0 ``` diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md index 7354cef8e..8059f304c 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md @@ -4,7 +4,11 @@ importance: 2 # 鏈接呼叫(Chaining) +<<<<<<< HEAD 有個 `ladder` 物件允許向上或向下: +======= +There's a `ladder` object that allows you to go up and down: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let ladder = { @@ -21,20 +25,34 @@ let ladder = { }; ``` +<<<<<<< HEAD 現在,若我們需要依序發出多個呼叫,可以像這樣做: +======= +Now, if we need to make several calls in sequence, we can do it like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 +ladder.down(); +ladder.showStep(); // 0 ``` +<<<<<<< HEAD 修改 `up`、`down` 和 `showStep` 的程式碼以使得呼叫為鏈接方式,像這樣: +======= +Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js -ladder.up().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` +<<<<<<< HEAD 這種作法在 JavaScript 函式庫內被廣泛地使用著。 +======= +Such an approach is widely used across JavaScript libraries. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md index 4067ae757..843f61224 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -32,11 +32,19 @@ user.sayHi = function() { user.sayHi(); // Hello! ``` +<<<<<<< HEAD 這邊我們已使用函式表達式來建立函式並指定為該物件的屬性 `user.sayHi`。 接著可以呼叫它,使用者就會說話了! 函式若為某個物件的屬性,則稱它為 *方法(method)*。 +======= +Here we've just used a Function Expression to create a function and assign it to the property `user.sayHi` of the object. + +Then we can call it as `user.sayHi()`. The user can now speak! + +A function that is a property of an object is called its *method*. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 所以,這邊我們得到物件 `user` 的一個方法 `sayHi`。 @@ -51,7 +59,7 @@ let user = { // 先宣告 function sayHi() { alert("Hello!"); -}; +} // 然後加進去做為方法 user.sayHi = sayHi; @@ -63,7 +71,11 @@ user.sayHi(); // Hello! ```smart header="物件導向程式設計" 當我們使用物件寫程式碼來表現實體時,被稱為 [物件導向程式設計(object-oriented programming)](https://en.wikipedia.org/wiki/Object-oriented_programming),簡稱為:"OOP"。 +<<<<<<< HEAD OOP 是門很大的學問,本身就是個有趣的科學。要怎麼選擇正確的實體?如何組織兩者間的交互作用?這就是架構,且有些很棒的書在探討這個議題,像是 E.Gamma, R.Helm, R.Johnson, J.Vissides 的 "Design Patterns: Elements of Reusable Object-Oriented Software" 或 G.Booch 的 "Object-Oriented Analysis and Design with Applications" 等等。 +======= +OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture, and there are great books on that topic, like "Design Patterns: Elements of Reusable Object-Oriented Software" by E. Gamma, R. Helm, R. Johnson, J. Vissides or "Object-Oriented Analysis and Design with Applications" by G. Booch, and more. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### 方法簡寫 @@ -82,7 +94,11 @@ user = { // 物件簡寫看起來更讚,對吧? user = { *!* +<<<<<<< HEAD sayHi() { // 與 "sayHi: function()" 相同 +======= + sayHi() { // same as "sayHi: function(){...}" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* alert("Hello"); } @@ -91,7 +107,11 @@ user = { 如所示,我們可以省略 `"function"` 而只寫 `sayHi()`。 +<<<<<<< HEAD 老實說,這兩種表示法不是完全相同,在物件繼承上(晚點會提)依然有些微妙的差別,但現在它們沒差。大多情況下簡短語法會更受青睞。 +======= +To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases, the shorter syntax is preferred. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 方法中的 "this" @@ -161,14 +181,24 @@ let user = { let admin = user; user = null; // 將其覆蓋使情況更明顯 +<<<<<<< HEAD admin.sayHi(); // 哎呀!在 sayHi() 內使用了舊的變數名!錯了! +======= +*!* +admin.sayHi(); // TypeError: Cannot read property 'name' of null +*/!* +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 若我們使用 `this.name` 而非 `user.name` 於 `alert` 中,則程式碼就可正常運作。 ## "this" 沒被綁定 +<<<<<<< HEAD JavaScript 中,關鍵字 `this` 的行為不像其它大多數程式語言一樣,它可以被用於任何函式內。 +======= +In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function, even if it's not a method of an object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 底下例子不會有語法錯誤: @@ -234,6 +264,7 @@ sayHi(); // undefined 這邊我們的立場不是要判斷語言設計決定是好或壞,我們只要理解它如何運作,且怎麼使用好處並避免問題。 ``` +<<<<<<< HEAD ## 內部情況:參考類型(Reference Type) ```warn header="深入語言特性" @@ -327,6 +358,9 @@ hi(); // 錯誤,因為 this 是 undefined 所以,作為結果,`this` 的值只有在函式被直接使用句點 `obj.method()` 或方括號 `obj['method']()` 語法呼叫時(它們在此做一樣的事),才會經過正確地途徑傳遞下去。在之後的教程中,我們將會學習多種解決此問題的方式,像是使用 [func.bind()](/bind#solution-2-bind)。 ## 箭頭函式沒有 "this" +======= +## Arrow functions have no "this" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 箭頭函式很特別:它們沒有 "自己的" `this`。若我們從這種函式參考了 `this`,會從更外層的 "正常" 函式中獲取其值。 diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md index e7ab8733e..ce30de1b2 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md @@ -4,14 +4,18 @@ importance: 2 # 兩個函式 - 一個物件 +<<<<<<< HEAD 有可能建立函式 `A` 和 `B` 使得 `new A()==new B()` 嗎? +======= +Is it possible to create functions `A` and `B` so that `new A() == new B()`? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify function A() { ... } function B() { ... } -let a = new A; -let b = new B; +let a = new A(); +let b = new B(); alert( a == b ); // true ``` diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js index 036053927..bba80e5c2 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js @@ -10,6 +10,11 @@ describe("calculator", function() { calculator = new Calculator(); calculator.read(); }); + + it("the read method asks for two values using prompt and remembers them in object properties", function() { + assert.equal(calculator.a, 2); + assert.equal(calculator.b, 3); + }); it("when 2 and 3 are entered, the sum is 5", function() { assert.equal(calculator.sum(), 5); diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md index 015469edc..7b9f3a410 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md @@ -6,9 +6,15 @@ importance: 5 建立一個建構子函式 `Calculator` 來建立有著三個方法的物件: +<<<<<<< HEAD - `read()` 使用 `prompt` 詢問兩個值並記憶在物件屬性內。 - `sum()` 回傳這些屬性的加總。 - `mul()` 回傳這些屬性的乘積。 +======= +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. +- `sum()` returns the sum of these properties. +- `mul()` returns the multiplication product of these properties. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉個例: diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index 0be17945b..48df2f217 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -1,6 +1,10 @@ # 建構子,"new" 運算子 +<<<<<<< HEAD 普通的 `{...}` 語法可以建立一個物件,但通常我們需要建立許多類似的物件,像是多個使用者或選單物品等等。 +======= +The regular `{...}` syntax allows us to create one object. But often we need to create many similar objects, like multiple users or menu items and so on. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 這可以使用建構子函式和 `"new"` 運算子來完成。 @@ -64,13 +68,21 @@ let user = { 這也是建構子主要的用途 -- 實作可重複使用的建立物件程式碼。 +<<<<<<< HEAD 再注意一下 -- 技術上,任何函式都可以作為建構子被使用。也就是:任何函式都可以使用 `new` 來運行,且它會執行上述的演算法。而 "首個字母大寫" 是個共識,來更清楚地表示該函式要使用 `new` 來運行。 ````smart header="new function() { ... }" 若我們有許多行關於建立單一個複雜物件的程式碼,我們可以包裝它們在建構子函式內,像這樣: +======= +Let's note once again -- technically, any function (except arrow functions, as they don't have `this`) can be used as a constructor. It can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`. + +````smart header="new function() { ... }" +If we have many lines of code all about creation of a single complex object, we can wrap them in an immediately called constructor function, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js -let user = new function() { +// create a function and immediately call it with new +let user = new function() { this.name = "John"; this.isAdmin = false; @@ -80,7 +92,11 @@ let user = new function() { }; ``` +<<<<<<< HEAD 該建構子不能被再次呼叫,因為它還沒存在任何地方,就只是被建立並呼叫了而已。所以這個技巧主要用於封裝程式碼以建構單一物件,未來不會重複使用。 +======= +This constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## 建構子模式測試:new.target @@ -91,7 +107,11 @@ let user = new function() { 在函式中,我們可以使用一個特殊的 `new.target` 屬性,來確認它是否經由 `new` 被呼叫。 +<<<<<<< HEAD 對於常規呼叫而言它會是空的,而若使用 `new` 呼叫則會相等於該函式: +======= +It is undefined for regular calls and equals the function if called with `new`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run function User() { @@ -169,8 +189,13 @@ alert( new SmallUser().name ); // John 通常建構子不會有 `return` 述語,這邊我們提到回傳物件的這個特殊行為主要是為了完整性而言。 +<<<<<<< HEAD ````smart header="省略括號" 順帶一提,若沒有引數的話,我們可以在 `new` 之後省略括號: +======= +````smart header="Omitting parentheses" +By the way, we can omit parentheses after `new`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let user = new User; // <-- 沒有括號 diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md new file mode 100644 index 000000000..4c6029423 --- /dev/null +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -0,0 +1,233 @@ + +# Optional chaining '?.' + +[recent browser="new"] + +The optional chaining `?.` is a safe way to access nested object properties, even if an intermediate property doesn't exist. + +## The "non-existing property" problem + +If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common. + +As an example, let's say we have `user` objects that hold the information about our users. + +Most of our users have addresses in `user.address` property, with the street `user.address.street`, but some did not provide them. + +In such case, when we attempt to get `user.address.street`, and the user happens to be without an address, we get an error: + +```js run +let user = {}; // a user without "address" property + +alert(user.address.street); // Error! +``` + +That's the expected result. JavaScript works like this. As `user.address` is `undefined`, an attempt to get `user.address.street` fails with an error. + +In many practical cases we'd prefer to get `undefined` instead of an error here (meaning "no street"). + +...and another example. In Web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element. + +```js run +// document.querySelector('.elem') is null if there's no element +let html = document.querySelector('.elem').innerHTML; // error if it's null +``` + +Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` property of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result. + +How can we do this? + +The obvious solution would be to check the value using `if` or the conditional operator `?`, before accessing its property, like this: + +```js +let user = {}; + +alert(user.address ? user.address.street : undefined); +``` + +It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. + +Here's how the same would look for `document.querySelector`: + +```js run +let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null; +``` + +We can see that the element search `document.querySelector('.elem')` is actually called twice here. Not good. + +For more deeply nested properties, it becomes even uglier, as more repetitions are required. + +E.g. let's get `user.address.street.name` in a similar fashion. + +```js +let user = {}; // user has no address + +alert(user.address ? user.address.street ? user.address.street.name : null : null); +``` + +That's just awful, one may even have problems understanding such code. + +There's a little better way to write it, using the `&&` operator: + +```js run +let user = {}; // user has no address + +alert( user.address && user.address.street && user.address.street.name ); // undefined (no error) +``` + +AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn't ideal. + +As you can see, property names are still duplicated in the code. E.g. in the code above, `user.address` appears three times. + +That's why the optional chaining `?.` was added to the language. To solve this problem once and for all! + +## Optional chaining + +The optional chaining `?.` stops the evaluation if the value before `?.` is `undefined` or `null` and returns `undefined`. + +**Further in this article, for brevity, we'll be saying that something "exists" if it's not `null` and not `undefined`.** + +In other words, `value?.prop`: +- works as `value.prop`, if `value` exists, +- otherwise (when `value` is `undefined/null`) it returns `undefined`. + +Here's the safe way to access `user.address.street` using `?.`: + +```js run +let user = {}; // user has no address + +alert( user?.address?.street ); // undefined (no error) +``` + +The code is short and clean, there's no duplication at all. + +Here's an example with `document.querySelector`: + +```js run +let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element +``` + +Reading the address with `user?.address` works even if `user` object doesn't exist: + +```js run +let user = null; + +alert( user?.address ); // undefined +alert( user?.address.street ); // undefined +``` + +Please note: the `?.` syntax makes optional the value before it, but not any further. + +E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/undefined` (and returns `undefined` in that case), but that's only for `user`. Further properties are accessed in a regular way. If we want some of them to be optional, then we'll need to replace more `.` with `?.`. + +```warn header="Don't overuse the optional chaining" +We should use `?.` only where it's ok that something doesn't exist. + +For example, if according to our code logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`. + +Then, if `user` happens to be undefined, we'll see a programming error about it and fix it. Otherwise, if we overuse `?.`, coding errors can be silenced where not appropriate, and become more difficult to debug. +``` + +````warn header="The variable before `?.` must be declared" +If there's no variable `user` at all, then `user?.anything` triggers an error: + +```js run +// ReferenceError: user is not defined +user?.address; +``` +The variable must be declared (e.g. `let/const/var user` or as a function parameter). The optional chaining works only for declared variables. +```` + +## Short-circuiting + +As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist. + +So, if there are any further function calls or operations to the right of `?.`, they won't be made. + +For instance: + +```js run +let user = null; +let x = 0; + +user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++ + +alert(x); // 0, value not incremented +``` + +## Other variants: ?.(), ?.[] + +The optional chaining `?.` is not an operator, but a special syntax construct, that also works with functions and square brackets. + +For example, `?.()` is used to call a function that may not exist. + +In the code below, some of our users have `admin` method, and some don't: + +```js run +let userAdmin = { + admin() { + alert("I am admin"); + } +}; + +let userGuest = {}; + +*!* +userAdmin.admin?.(); // I am admin +*/!* + +*!* +userGuest.admin?.(); // nothing happens (no such method) +*/!* +``` + +Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the `user` object exists, so it's safe read from it. + +Then `?.()` checks the left part: if the `admin` function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors. + +The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist. + +```js run +let key = "firstName"; + +let user1 = { + firstName: "John" +}; + +let user2 = null; + +alert( user1?.[key] ); // John +alert( user2?.[key] ); // undefined +``` + +Also we can use `?.` with `delete`: + +```js run +delete user?.name; // delete user.name if user exists +``` + +````warn header="We can use `?.` for safe reading and deleting, but not writing" +The optional chaining `?.` has no use on the left side of an assignment. + +For example: +```js run +let user = null; + +user?.name = "John"; // Error, doesn't work +// because it evaluates to: undefined = "John" +``` + +```` + +## Summary + +The optional chaining `?.` syntax has three forms: + +1. `obj?.prop` -- returns `obj.prop` if `obj` exists, otherwise `undefined`. +2. `obj?.[prop]` -- returns `obj[prop]` if `obj` exists, otherwise `undefined`. +3. `obj.method?.()` -- calls `obj.method()` if `obj.method` exists, otherwise returns `undefined`. + +As we can see, all of them are straightforward and simple to use. The `?.` checks the left part for `null/undefined` and allows the evaluation to proceed if it's not so. + +A chain of `?.` allows to safely access nested properties. + +Still, we should apply `?.` carefully, only where it's acceptable, according to our code logic, that the left part doesn't exist. So that it won't hide programming errors from us, if they occur. diff --git a/1-js/04-object-basics/03-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md similarity index 63% rename from 1-js/04-object-basics/03-symbol/article.md rename to 1-js/04-object-basics/08-symbol/article.md index 9aae2f2ec..1356e7e07 100644 --- a/1-js/04-object-basics/03-symbol/article.md +++ b/1-js/04-object-basics/08-symbol/article.md @@ -1,9 +1,22 @@ # 符號類型(Symbol Type) +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md 根據規格,物件屬性鍵值只可能是字串類型或符號類型。不是數值類型、不是布林類型,只有字串或符號這兩種類型。 到目前為止,我們一直只用到字串。現在讓我們來看看 Symbol 能給我們帶來哪些好處。 +======= +By specification, only two primitive types may serve as object property keys: + +- string type, or +- symbol type. + +Otherwise, if one uses another type, such as number, it's autoconverted to string. So that `obj[1]` is the same as `obj["1"]`, and `obj[true]` is the same as `obj["true"]`. + +Until now we've been using only strings. + +Now let's explore symbols, see what they can do for us. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md ## 符號(Symbol) @@ -12,6 +25,7 @@ 可以用 `Symbol()` 來創建此類型的值: ```js +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md // id 是一個新的 Symbol let id = Symbol(); ``` @@ -24,6 +38,19 @@ let id = Symbol("id"); ``` Symbol 保證是唯一的。即使我們創建了許多擁有相同敘述的 Symbol,它們的值還是不同的。敘述只是一個標籤,不影響任何東西。 +======= +let id = Symbol(); +``` + +Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes: + +```js +// id is a symbol with the description "id" +let id = Symbol("id"); +``` + +Symbols are guaranteed to be unique. Even if we create many symbols with exactly the same description, they are different values. The description is just a label that doesn't affect anything. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 舉例來說,這裡有兩個擁有相同敘述的 Symbol -- 它們並不相等: @@ -38,8 +65,15 @@ alert(id1 == id2); // false 如果你熟悉 Ruby 或是其他也同樣擁有所謂 "Symbol" 的語言 -- 請不要被誤導。 JavaScript 的 Symbol 是不一樣的。 +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md ````warn header="Symbol 並不會被自動轉換成字串" JavaScript 中,大部分的值都支援字串的隱性轉換。例如,我們可以 `alert` 幾乎任何值,且它可以正常運作。Symbol 是特殊的。它們不會自動轉換。 +======= +So, to summarize, a symbol is a "primitive unique value" with an optional description. Let's see where we can use them. + +````warn header="Symbols don't auto-convert to a string" +Most values in JavaScript support implicit conversion to a string. For instance, we can `alert` almost any value, and it will work. Symbols are special. They don't auto-convert. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 例如,這個 `alert` 會顯示錯誤: @@ -54,6 +88,11 @@ alert(id); // TypeError: Cannot convert a Symbol value to a string 如果我們真的想要顯示一個Symbol,我們需要在它上面明確地呼叫 `.toString()`,像這樣: +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md +======= +If we really want to show a symbol, we need to explicitly call `.toString()` on it, like here: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md ```js run let id = Symbol("id"); *!* @@ -61,7 +100,12 @@ alert(id.toString()); // Symbol(id), 現在它可以正常運作了 */!* ``` +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md 或是取得 `symbol.description` 屬性來單純顯示敘述: +======= +Or get `symbol.description` property to show the description only: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md ```js run let id = Symbol("id"); *!* @@ -73,7 +117,12 @@ alert(id.description); // id ## "隱藏(Hidden)" 屬性 +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md Symbol 允許我們創建物件的 "隱藏" 屬性,其他部分的程式碼都無法意外存取到或是覆寫它。 +======= + +Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 舉例來說,如果我們正在操作屬於第三方程式碼的 `user` 物件們。我們想要增加識別符到它們上。 @@ -93,9 +142,15 @@ alert( user[id] ); // 我們可以用 Symbol 當作鍵值來存取資料 比起用字串 `"id"`,用 `Symbol("id")` 我們可以獲得什麼好處? +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md 當 `user` 物件屬於其他程式碼,且那些程式碼同樣會操作它時,我們不應該增加任何欄位到物件上。這樣不安全。但 Symbol 是沒辦法被意外存取的,第三方的程式碼甚至可能不會看到它,所以這麼做沒問題。 此外,想像一下此時有另一個腳本想要在 `user` 內放入它們自己的識別符號,用於它們自己的目的。那可能是另一個 JavaScript 套件,所以腳本之間完全不會意識到對方的存在。 +======= +As `user` objects belong to another codebase, it's unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code won't be aware of newly defined symbols, so it's safe to add symbols to the `user` objects. + +Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 然後該腳本可以創建自己的 `Symbol("id")`,像這樣: @@ -110,7 +165,7 @@ user[id] = "Their id value"; ...但如果我們用字串 `"id"` 而非 Symbol,那麼 *就會* 發生衝突: -```js run +```js let user = { name: "John" }; // 我們的腳本使用 "id" 屬性 @@ -122,7 +177,11 @@ user.id = "Their id value" // 碰!被另一個腳本給覆寫了! ``` +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md ### 字面值中的符號 +======= +### Symbols in an object literal +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 如果我們想要在物件字面值 `{...}` 中使用 Symbol,我們需要方括號包圍它。 @@ -134,7 +193,11 @@ let id = Symbol("id"); let user = { name: "John", *!* +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md [id]: 123 // 不是 "id: 123" +======= + [id]: 123 // not "id": 123 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md */!* }; ``` @@ -159,6 +222,7 @@ let user = { for (let key in user) alert(key); // name, age(沒有 Symbol) */!* +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md // 直接存取 Symbol 是沒問題的 alert( "Direct: " + user[id] ); ``` @@ -166,6 +230,13 @@ alert( "Direct: " + user[id] ); `Object.keys(user)` 也會忽略它們。那是一般 Symbol 中 "隱藏屬性" 原則的一部分。如果另一個腳本或是一個套件在我們的物件上循環,它不會不小心存取到 Symbol 類型的屬性。 相反地,[Object.assign](mdn:js/Object/assign) 同時複製字串與 Symbol 屬性: +======= +// the direct access by the symbol works +alert( "Direct: " + user[id] ); // Direct: 123 +``` + +[Object.keys(user)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md ```js run @@ -181,6 +252,7 @@ alert( clone[id] ); // 123 這裡沒有矛盾,就是這樣設計的。想法是當我們複製一個物件,或是合併多個物件時,我們通常想要 *所有* 屬性都被複製(包含像 `id` 這樣的 Symbol )。 +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md ````smart header="其他類別的屬性鍵值會被強行轉換成字串" 在物件中,我們只能使用字串或 Symbol 當作鍵值。其他類型都會被轉成字串。 @@ -198,6 +270,9 @@ alert( obj[0] ); // test (同樣的屬性) ```` ## 全局符號 +======= +## Global symbols +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 正如我們所見,通常所有的 Symbol 都是不同的,即使它們擁有相同的名稱。但是有時候我們想要擁有相同名稱的 Symbol 被當作相同的物體。例如,我們應用程式中的不同部分想用Symbol `"id"` 存取到完全相同的屬性。 @@ -225,12 +300,20 @@ alert( id === idAgain ); // true ```smart header="這聽起來像是 Ruby" 在某些程式語言中,像是 Ruby,每個名稱都只有單一個 symbol。 +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md 在 JavaScript 中,如我們所見,只有全域 Symbol 才是如此。 +======= +In JavaScript, as we can see, that's true for global symbols. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md ``` ### Symbol.keyFor +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md 對於全域 Symbol,不止有 `Symbol.for(key)` 可以根據某個名稱回傳其 Symbol,還有個反向呼叫,`Symbol.keyFor(sym)` 做反過來的事:根據一個全域 Symbol 回傳其名稱。 +======= +We have seen that for global symbols, `Symbol.for(key)` returns a symbol by name. To do the opposite -- return a name by global symbol -- we can use: `Symbol.keyFor(sym)`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 例如: @@ -244,9 +327,15 @@ alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id ``` +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md `Symbol.keyFor` 在內部使用全域 Symbol 註冊表來查詢 Symbol 的鍵值。所以它並不適用於非全域的 Symbol。如果某個 Symbol 不是全域的,此方法將無法找到它,並會回傳 `undefined`。 是說,任何 Symbol 都有 `description` 屬性。 +======= +The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and returns `undefined`. + +That said, all symbols have the `description` property. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 例如: @@ -286,11 +375,21 @@ Symbol 永遠是不同的值,即使它們擁有相同的名稱。如果我們 Symbol 有兩個主要使用場景: +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md 1. "隱藏" 物件屬性。 如果我們想要增加一個屬性到 "屬於" 其他腳本或是套件的物件之中,我們可以創建一個 Symbol 並用它當作屬性的鍵值。Symbol 屬性不會出現在 `for..in` 中,所以他不會不小心被其他屬性一起處理。此外它也不能被直接存取,因為其他腳本不擁有我們的 Symbol。所以該屬性將會被保護,以防意外被存取或覆寫。 +======= +1. "Hidden" object properties. + + If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md 所以我們可以使用 Symbol 屬性, "秘密地" 將一些我們需要,但其他人不需要的東西藏進物件中。 2. JavaScript 內部使用了許多系統 Symbol,以類似 `Symbol.*` 的形式被存取。我們可以使用它們來更改一些內建的行為。舉例來說,在之後的教程我們將會在 [迭代(iterables)](info:iterable) 中使用 `Symbol.iterator`,設定 [物件轉換原生值(object-to-primitive conversion)](info:object-toprimitive) 時使用 `Symbol.toPrimitive`,等等。 +<<<<<<< HEAD:1-js/04-object-basics/03-symbol/article.md 從技術上來說,Symbol 並非 100% 隱藏。有一個內建方法 [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) 允許我們取得所有的 Symbol。另外,還有個方法叫做 [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) 會回傳物件中的 *所有* 鍵值,包含 Symbol。所以它們並非真的被隱藏。但大多套件、內建函式和語法結構都不會使用這些方法。 +======= +Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. But most libraries, built-in functions and syntax constructs don't use these methods. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/08-symbol/article.md diff --git a/1-js/04-object-basics/05-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md similarity index 50% rename from 1-js/04-object-basics/05-object-toprimitive/article.md rename to 1-js/04-object-basics/09-object-toprimitive/article.md index 6efa52ab5..a07c74a43 100644 --- a/1-js/04-object-basics/05-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -4,17 +4,54 @@ 在這種情況,物件被自動轉換為原生類型,並且操作會被執行。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 在章節 我們已經看到數值、字串與布林等原生類型的轉換規則,但我們對於物件遺留了一些空白。現在,當我們知道方法和符號後,就該來填上它了。 1. 所有物件在布林上下文(boolean context)中皆為 `true`,所以就只會有數值與字串的轉換。 2. 當我們相減物件或套用數學函式時,數值轉換才會發生。舉個例,`Date` 物件(在章節 中會介紹)可以相減,而 `date1 - date2` 的結果是兩個日期之間的時間差。 3. 至於字串轉換 -- 它通常發生在當我們像是 `alert(obj)` 這樣輸出物件或類似的上下文中才會發生。 +======= +JavaScript doesn't allow you to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle addition (or other operators). -## ToPrimitive +In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value. +That's an important limitation: the result of `obj1 + obj2` (or another math operation) can't be another object! + +E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board". + +So, because we can't technically do much here, there's no maths with objects in real projects. When it happens, with rare exceptions, it's because of a coding mistake. + +In this chapter we'll cover how an object converts to primitive and how to customize it. + +We have two purposes: + +1. It will allow us to understand what's going on in case of coding mistakes, when such an operation happened accidentally. +2. There are exceptions, where such operations are possible and look good. E.g. subtracting or comparing dates (`Date` objects). We'll come across them later. + +## Conversion rules + +In the chapter we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it. + +1. There's no conversion to boolean. All objects are `true` in a boolean context, as simple as that. There exist only numeric and string conversions. +2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates. +3. As for the string conversion -- it usually happens when we output an object with `alert(obj)` and in similar contexts. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md + +We can implement string and numeric conversion by ourselves, using special object methods. + +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 我們可以使用特殊的物件方法來微調字串和數值的轉換。 有三種類型轉換的變化,被稱為 "提示(hints)",被描述於 [規格](https://tc39.github.io/ecma262/#sec-toprimitive) 內: +======= +Now let's get into technical details, because it's the only way to cover the topic in-depth. + +## Hints + +How does JavaScript decide which conversion to apply? + +There are three variants of type conversion, that happen in various situations. They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md `"string"` : 物件至字串的轉換,發生在我們操作物件並預期得到一個字串時,像是 `alert`: @@ -42,10 +79,16 @@ let greater = user1 > user2; ``` + Most built-in mathematical functions also include such conversion. + `"default"` : 發生於極少情況下,當 "不確定" 運算子會預期是什麼類型時。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 舉個例,二元加法 `+` 可以同時運作在字串(串接)和數值(相加)上,所以字串與數值兩者皆可使用。因此若二元加法以物件作為引數時,它會使用 `"default"` 提示來轉換。 +======= + For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them). So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md 同樣地,若物件使用 `==` 比較字串、數值或符號時,哪種轉換會進行也很不清楚,所以會使用 `"default"` 提示。 @@ -59,6 +102,7 @@ 大於/小於 運算子 `<>` 一樣可以同時作用於字串和數值上。但它仍使用 "number" 提示而非 "default",這是因為歷史因素。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 實際上,我們不需要記住這些罕見的細節,因為除了某種例外(`Date` 物件,我們晚點會學到),所有內建物件都實作了和 `"number"` 一樣的 `"default"` 轉換,所以可以同樣方式運作。 ```smart header="No `\"boolean\"` hint" @@ -66,14 +110,29 @@ 不存在 "布林" 提示(所有物件在布林上下文中都是 `true`)等等其它的轉換。且若我們將 `"default"` 和 `"number"` 一視同仁,如同大多內建物件那樣,那將只有兩種轉換了。 ``` +======= +In practice though, things are a bit simpler. + +All built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we probably should do the same. + +Still, it's important to know about all 3 hints, soon we'll see why. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md **要做轉換時,JavaScript 試著找尋並呼叫三種物件方法:** +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 1. 呼叫 `obj[Symbol.toPrimitive](hint)` - 若該方法存在時,會置於符號鍵 `Symbol.toPrimitive`(系統符號)之中。 2. 否則,若提示為 `"string"` - 嘗試呼叫 `obj.toString()` 和 `obj.valueOf()`,不論是哪個存在。 3. 否則,若提示為 `"number"` 或`"default"` - 嘗試呼叫 `obj.valueOf()` 和 `obj.toString()`,不論是哪個存在。 +======= +1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists, +2. Otherwise if hint is `"string"` + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. +3. Otherwise if hint is `"number"` or `"default"` + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md ## Symbol.toPrimitive @@ -81,12 +140,24 @@ ```js obj[Symbol.toPrimitive] = function(hint) { +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md // 必須回傳一個原生類型值 // hint = "string"、"number" 和 "default" 其中一個 }; ``` 舉個例,這個 `user` 物件實作了它: +======= + // here goes the code to convert this object to a primitive + // it must return a primitive value + // hint = one of "string", "number", "default" +}; +``` + +If the method `Symbol.toPrimitive` exists, it's used for all hints, and no more methods are needed. + +For instance, here `user` object implements it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md ```js run let user = { @@ -105,6 +176,7 @@ alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 如我們從程式碼所見,`user` 依據轉換類型變成描述自己的字串或錢的總量。該單一方法 `user[Symbol.toPrimitive]` 處理了所有轉換情況。 ## toString/valueOf @@ -117,6 +189,20 @@ alert(user + 500); // hint: default -> 1500 - 否則,`valueOf -> toString`。 這些方法必須回傳一個原生值,若 `toString` 或 `valueOf` 回傳一個物件,則它將被忽略(視同根本不存在這個方法)。 +======= +As we can see from the code, `user` becomes a self-descriptive string or a money amount, depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases. + +## toString/valueOf + +If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: + +- For the `"string"` hint: call `toString` method, and if it doesn't exist or if it returns an object instead of a primitive value, then call `valueOf` (so `toString` has the priority for string conversions). +- For other hints: call `valueOf`, and if it doesn't exist or if it returns an object instead of a primitive value, then call `toString` (so `valueOf` has the priority for maths). + +Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. + +These methods must return a primitive value. If `toString` or `valueOf` returns an object, then it's ignored (same as if there were no method). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md 每個物件會有下列預設的 `toString` 與 `valueOf` 方法: @@ -134,9 +220,15 @@ alert(user.valueOf() === user); // true 所以若我們試圖將某個物件作為字串使用,像是用於 `alert` 等其它地方,那我們預設將看到 `[object Object]`。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 為了避免搞混,而在這邊提到預設的 `valueOf` 只是為了完整性考量。如同你所見,它回傳了該物件本身從而被忽略了。別問我為什麼,那是歷史因素。所以我們可以假設它根本不存在。 來實作這些方法吧。 +======= +The default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist. + +Let's implement these methods to customize the conversion. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md 舉個例,這個 `user` 使用 `toString` 和 `valueOf` 的組合做了如同上述的事,而不是用 `Symbol.toPrimitive`: @@ -181,27 +273,45 @@ alert(user + 500); // toString -> John500 在少了 `Symbol.toPrimitive` 與 `valueOf` 的情況下,`toString` 將會處理所有原生類型的轉換。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md ## 回傳類型 +======= +### A conversion can return any primitive type +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md 要理解關於原生類型轉換方法,有件很重要的事就是它們不需要回傳 "被提示" 的原生類型。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 沒有限制 `toString()` 是否一定就得回傳一個字串,或 `Symbol.toPrimitive` 方法是否得對 "number" 提示回傳一個數值。 +======= +There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for the hint `"number"`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md 唯一強制的事情是:這些方法必須回傳原生類型,而非物件。 ```smart header="歷史筆記" 由於歷史因素,若 `toString` 或 `valueOf` 回傳一個物件時不會產生錯誤,但該值將被忽略(就像此方法不存在一樣)。那是因為古早時期在 JavaScript 中並沒有好的 "錯誤" 觀念。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 相對地,`Symbol.toPrimitive` *必須* 回傳一個原生類型,否則就會產生錯誤。 +======= +In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md ``` ## 進一步的轉換 如同我們所知的,許多運算子與函式都會做類型轉換,像是:乘法 `*` 會轉換運算元為數值。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 若我們傳入一個物件作為引數,那將會進行兩個步驟: 1. 物件被轉為原生值(使用上述的規則)。 2. 若轉完的原生值並非正確類型,就再進行轉換。 +======= +If we pass an object as an argument, then there are two stages of calculations: +1. The object is converted to a primitive (using the rules described above). +2. If necessary for further calculations, the resulting primitive is also converted. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md 舉個例: @@ -228,22 +338,36 @@ let obj = { } }; +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md alert(obj + 2); // 22("2" + 2),轉換為原生值時回傳一個字串 => 串接 +======= +alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md ``` ## 總結 在許多預期使用原生類型作為值的函式和運算子中,物件轉換為原生類型是自動被呼叫的。 +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 對其有三種類型(提示): - `"string"`(對於 `alert` 和其他需要字串的操作) - `"number"` (對於數學運算) - `"default"` (少數操作) 規格明確描述哪個運算子使用哪種提示。有極少運算 "不知如何預期" 就會使用 `"default"` 提示。通常對於內建物件來說,`"default"` 提示會採用跟 `"number"` 一樣的處理方式,所以實務上後兩者會被合併在一起。 +======= +There are 3 types (hints) of it: +- `"string"` (for `alert` and other operations that need a string) +- `"number"` (for maths) +- `"default"` (few operators, usually objects implement it the same way as `"number"`) + +The specification describes explicitly which operator uses which hint. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md 轉換的演算法為: +<<<<<<< HEAD:1-js/04-object-basics/05-object-toprimitive/article.md 1. 呼叫 `obj[Symbol.toPrimitive](hint)` 若該方法存在。 2. 否則若提示為 `"string"` - 嘗試 `obj.toString()` 和 `obj.valueOf`,不論是哪個存在。 @@ -252,3 +376,14 @@ alert(obj + 2); // 22("2" + 2),轉換為原生值時回傳一個字串 => 實際上,對於紀錄或除錯用途而言,通常只要實作 `obj.toString()` 作為 "全包" 所有轉換的方法,使其能回傳某物件 "人類能讀懂" 的表示型式就夠了。 +======= +1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, +2. Otherwise if hint is `"string"` + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. +3. Otherwise if hint is `"number"` or `"default"` + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. + +All these methods must return a primitive to work (if defined). + +In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/04-object-basics/09-object-toprimitive/article.md diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md index 8f242aef1..a26015118 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md @@ -14,5 +14,9 @@ str.test = 5; alert(str.test); ``` +<<<<<<< HEAD 你覺得如何,這樣行嗎?會顯示什麼? +======= +What do you think, will it work? What will be shown? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index 477e585ec..c933b7872 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -12,7 +12,10 @@ JavaScript 允許我們像物件一樣來使用原生類型(字串、數值等 ======= - Is a value of a primitive type. - There are 7 primitive types: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` and `undefined`. +<<<<<<< HEAD >>>>>>> 71120d5968cec3103743014cf563e0f7c8045a16 +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 物件 @@ -44,8 +47,13 @@ john.sayHi(); // Hi buddy! JavaScript 的建立者們面臨了這樣的一個矛盾: +<<<<<<< HEAD - 人們想對字串或數值這樣的原生類型做許多事,若能透過其方法來存取就太棒了。 - 原生類型應該要盡可能的快速和輕量化。 +======= +- There are many things one would want to do with a primitive, like a string or a number. It would be great to access them using methods. +- Primitives must be as fast and lightweight as possible. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 解法看起來有點笨,但就是這樣: @@ -53,7 +61,11 @@ JavaScript 的建立者們面臨了這樣的一個矛盾: 2. 語言允許存取字串、數值、布林與符號的方法與屬性。 3. 為了要讓其可以運作,建立一個特殊的 "物件包裝" 並提供額外的功能,然後運作後再銷毀。 +<<<<<<< HEAD "物件包裝" 對於每種原生類型都不同,名為:`String`、`Number`、`Boolean` 和 `Symbol`。因此,它們提供不同的方法集合。 +======= +The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. Thus, they provide different sets of methods. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉個例,有個字串方法 [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) 回傳被大寫的 `str`。 @@ -108,10 +120,17 @@ if (zero) { // zero 是 true,因為它是物件 } ``` +<<<<<<< HEAD 另一方面,不透過 `new` 而使用同樣的函式 `String/Number/Boolean` 是明智且有用的用法,它們將值轉換為對應的類型:字串、數值或布林(原生類型)。 例如,這樣完全有效: +======= +On the other hand, using the same functions `String/Number/Boolean` without `new` is totally fine and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive). + +For example, this is entirely valid: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let num = Number("123"); // 轉換字串為數值 ``` diff --git a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md index 290ee9e8f..22c0eefec 100644 --- a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md +++ b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md @@ -27,6 +27,6 @@ alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000 注意這個 `63.5` 完全沒有精度損失,這是因為小數部分的 `0.5` 事實上為 `1/2`。被 `2` 的次方所除的除法可以在二進位系統中被完全表示出來,現在我們可以進位它了: ```js run -alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 +alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 ``` diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 275e3a9f0..6d245563a 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -1,8 +1,18 @@ # 數值(Numbers) +<<<<<<< HEAD 在現代 JavaScript 中,有兩種類型的數值: 1. 一般數值以 64 位元格式儲存 [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision) ,亦被稱為 "雙精度浮點數值"。 這種數值是我們最常使用的類型,而我們將會在這章節中討論它。 +======= +In modern JavaScript, there are two types of numbers: + +1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter. + +2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter . + +So here we'll talk about regular numbers. Let's expand our knowledge of them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 2. BigInt 數值,用來表示任意長度的整數。有時我們會需要它,因為一般數字無法超過253 或小於 -253。 由於 bigints 只用在少數幾個特殊領域,我們將用一個特殊章節來介紹它。 . @@ -16,29 +26,55 @@ let billion = 1000000000; ``` +<<<<<<< HEAD 但現實生活中,我們通常避免寫下有這麼多零的長字串,因為太容易打錯了。同樣地,我們很懶惰,我們通常只想要寫 `"1bn"` 來代表一個十億或 `"7.3bn"` 來代表七十三億,對多數的大數字而言也是同樣態度。 JavaScript 中,我們對於數字附加一個字母 `"e"` 用以指定零的數量來縮短一個數值: +======= +We also can use underscore `_` as the separator: + +```js +let billion = 1_000_000_000; +``` + +Here the underscore `_` plays the role of the "[syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)", it makes the number more readable. The JavaScript engine simply ignores `_` between digits, so it's exactly the same one billion as above. + +In real life though, we try to avoid writing long sequences of zeroes. We're too lazy for that. We'll try to write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers. + +In JavaScript, we can shorten a number by appending the letter `"e"` to it and specifying the zeroes count: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let billion = 1e9; // 1 個十億,如字面所說:1 與 9 個零 +<<<<<<< HEAD alert( 7.3e9 ); // 7.3 個十億 (7,300,000,000) ``` 換句話說,`"e"` 把該數字乘上 `1` 後面跟著指定數量的零。 +======= +alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000) +``` + +In other words, `e` multiplies the number by `1` with the given zeroes count. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js -1e3 = 1 * 1000 -1.23e6 = 1.23 * 1000000 +1e3 === 1 * 1000; // e3 means *1000 +1.23e6 === 1.23 * 1000000; // e6 means *1000000 ``` +<<<<<<< HEAD 現在來寫些非常小的數值吧,例如 1 微秒(百萬分之一秒): +======= +Now let's write something very small. Say, 1 microsecond (one-millionth of a second): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js -let ms = 0.000001; +let mсs = 0.000001; ``` +<<<<<<< HEAD 就跟之前一樣,使用 `"e"` 會有幫助。若我們想避免明確寫下那麼多零,我們可以: ```js @@ -46,15 +82,35 @@ let ms = 1e-6; // 在 1 左側有六個零 ``` 若我們在 `0.000001` 中數零的數量,會有 6 個,所以自然就是 `1e-6`。 +======= +Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could write the same as: + +```js +let mcs = 1e-6; // five zeroes to the left from 1 +``` + +If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 換句話說,`"e"` 之後的負數代表除以 1 後面跟著指定數量的零: ```js +<<<<<<< HEAD // -3 除以 1 後面跟著 3 個零 1e-3 = 1 / 1000 (=0.001) // -6 除以 1 後面跟著 6 個零 1.23e-6 = 1.23 / 1000000 (=0.00000123) +======= +// -3 divides by 1 with 3 zeroes +1e-3 === 1 / 1000; // 0.001 + +// -6 divides by 1 with 6 zeroes +1.23e-6 === 1.23 / 1000000; // 0.00000123 + +// an example with a bigger number +1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### 十六進位(hex)、二進位(binary)和八進位(octal)數值 @@ -92,13 +148,23 @@ alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111 ``` +<<<<<<< HEAD `base` 可以在 `2` 至 `36` 中變換,預設是 `10`。 +======= +The `base` can vary from `2` to `36`. By default, it's `10`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 常用的使用情境是: +<<<<<<< HEAD - **base=16** 被用於十六進位顏色、字元編碼等,數字可以是 `0..9` 或 `A..F`。 - **base=2** 大多用於除錯以位元為單位的操作,數字可以是 `0` 或 `1`。 - **base=36** 是最大值,數字可以是 `0..9` 或 `A..Z`,整個拉丁字母表都被用來表示數值。對於 `36` 的一個有趣但有用的情境會發生在,當我們需要把一個長數值識別符轉為更簡短時,例如做個短 URL 的時候。使用 `36` 作為基底,可以簡單的在數值系統表示它: +======= +- **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`. +- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`. +- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole Latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example, to make a short url. Can simply represent it in the numeral system with base `36`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 123456..toString(36) ); // 2n9c @@ -107,9 +173,16 @@ alert( num.toString(2) ); // 11111111 ```warn header="兩個句點來呼叫方法" 請注意在 `123456..toString(36)` 中的兩個句點並非打錯字。若我們想直接在數值上呼叫像是上述的 `toString` 方法,那我們需要在其後放兩個句點 `..`。 +<<<<<<< HEAD 若我們只放一個句點:`123456.toString(36)`,那就會產生錯誤,因為 JavaScript 的語法中,在第一個句點之後意味著的是小數點部分。而若我們放置多於一個句點,那 JavaScript 就知道小數點部分為空,接下來的是方法。 也可寫成 `(123456).toString(36)`。 +======= +If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now uses the method. + +Also could write `(123456).toString(36)`. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 進位(Rounding) @@ -125,7 +198,11 @@ alert( num.toString(2) ); // 11111111 : 向上進位:`3.1` 變成 `4`,且 `-1.1` 變成 `-1`。 `Math.round` +<<<<<<< HEAD : 四捨五入進位至最近的整數:`3.1` 變成 `3`、`3.6` 變成 `4` 且 `-1.1` 變成 `-1`。 +======= +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `Math.trunc`(Internet Explorer 不支援) : 不進位直接捨去小數點之後的部分:`3.1` 變成 `3`,`-1.1` 變成 `-1`。 @@ -135,8 +212,10 @@ alert( num.toString(2) ); // 11111111 | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |---|---------|--------|---------|---------| |`3.1`| `3` | `4` | `3` | `3` | +|`3.5`| `3` | `4` | `4` | `3` | |`3.6`| `3` | `4` | `4` | `3` | |`-1.1`| `-2` | `-1` | `-1` | `-1` | +|`-1.5`| `-2` | `-1` | `-1` | `-1` | |`-1.6`| `-2` | `-1` | `-2` | `-1` | 這些函式涵蓋處理一個數值其小數點部分的所有可能方法。但如果我們想要進位數值到小數點後 `第 n 位` 該怎麼辦? @@ -152,7 +231,7 @@ alert( num.toString(2) ); // 11111111 ```js run let num = 1.23456; - alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 + alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23 ``` 2. 方法 [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) 進位數值到小數點後第 `n` 個數字,並回傳其結果的字串表示法。 @@ -169,20 +248,34 @@ alert( num.toString(2) ); // 11111111 alert( num.toFixed(1) ); // "12.4" ``` +<<<<<<< HEAD 請注意 `toFixed` 的結果是個字串,若小數點部分比要求的還短,結尾會被添加零: +======= + Please note that the result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let num = 12.34; alert( num.toFixed(5) ); // "12.34000",加零來讓它剛好五個數字 ``` +<<<<<<< HEAD 我們可以使用一元正號將它轉為數值或是呼叫 `Number()`:`+num.toFixed(5)`。 +======= + We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 不精確計算 +<<<<<<< HEAD 在內部,數值使用 64 位元格式表示 [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision),所以只有 64 個位元用來儲存一個數值:其中 52 個用來儲存數字部分,11 個用來儲存小數點位置(對整數來說都是零),且 1 個位元表示正負號。 若某個數值太大,它可能會溢出 64 位元的儲存空間,潛在地給出無窮大(infinity): +======= +Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point, and 1 bit is for the sign. + +If a number is really huge, it may overflow the 64-bit storage and become a special numeric value `Infinity`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 1e500 ); // Infinity @@ -190,7 +283,11 @@ alert( 1e500 ); // Infinity 精度的損失稍微沒那麼明顯但卻很常發生。 +<<<<<<< HEAD 考慮這個(錯的!)測試: +======= +Consider this (falsy!) equality test: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* @@ -204,13 +301,27 @@ alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* alert( 0.1 + 0.2 ); // 0.30000000000000004 ``` +<<<<<<< HEAD 哎呀!這會造成比不正確的比較更為嚴重的後果。想像一下你正在製作電子商務網站,而訪問者放入 `$0.10` 和 `$0.20` 的商品到購物車內,但訂單總額卻是 `$0.30000000000000004`,那會讓任何人都感到驚訝。 +======= +Ouch! Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 但這為什麼會發生? 數值在記憶體中以二進位格式存放,一和零的一串位元。但像是 `0.1`、`0.2` 這樣在十進位數值系統內很簡單的小數點,實際上在二進位格式內是循環小數。 +<<<<<<< HEAD 換句話說,什麼是 `0.1`?是一除以十 `1/10`,也就是十分之一,在十進位數值系統中要表示這種數值很簡單。但跟三分之一比較看看:`1/3`,就變成一個無窮循環小數了 `0.33333(3)`。 +======= +```js run +alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 +alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 +alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101 +``` + +What is `0.1`? It is one divided by ten `1/10`, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 因此,除以 `10` 的次方數在十進位系統中保證運作良好,但除以 `3` 就不是了。同樣的原因,在二進位數值系統中,除以 `2` 的次方數保證可以運作,但 `1/10` 會變成一個無窮循環小數。 @@ -231,14 +342,18 @@ alert( 0.1.toFixed(20) ); // 0.10000000000000000555 ```smart header="不僅是 JavaScript 而已" 同樣的問題存在許多程式語言之中。 +<<<<<<< HEAD PHP、Java、C、Perl、Ruby 都給出同樣的結果,因為它們都以同一個數值格式為基底。 +======= +PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 我們有辦法繞過這個問題嗎?當然,最可靠的做法就經由方法 [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) 的幫助將結果進位處理: ```js run let sum = 0.1 + 0.2; -alert( sum.toFixed(2) ); // 0.30 +alert( sum.toFixed(2) ); // "0.30" ``` 請注意 `toFixed` 總是回傳字串。它保證小數點後會有兩個數字。若我們有一個電子商務網站且需要顯示 `$0.30` 時,這實際上是很方便的。對於其它情境,我們可以使用一元正號以強制轉為數值: @@ -255,7 +370,11 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` +<<<<<<< HEAD 所以,乘/除 的作法雖然減少錯誤,但卻不能完全消除。 +======= +So, the multiply/divide approach reduces the error, but doesn't remove it totally. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 有時候我們可以試著完全避免分數。像是若我們在處理一個商店時,我們可以用美分而不是用美元來儲存價格。但若我們採用 30% 的折扣時怎麼辦?實際上,不太可能完全避免使用分數。就只能採取進位並在需要時切除 "尾部" 的作法了。 @@ -275,6 +394,7 @@ JavaScript 在這種事件下不會觸發錯誤,它會盡全力讓數值符合 ```smart header="兩個零" 另一個數值內部表現的有趣結果是,存在有兩個零:`0` 和 `-0`。 +<<<<<<< HEAD 這是因為使用單一個位元來表示正負號,所以每個數值都可以為正或負,包括零。 在大多情況下不會注意到有這樣的區別,因為運算子對待它們一視同仁。 @@ -286,6 +406,14 @@ JavaScript 在這種事件下不會觸發錯誤,它會盡全力讓數值符合 - `Infinity`(和 `-Infinity`)是個大於(小於)任何東西的特殊數值。 - `NaN` 表示有錯誤產生。 +======= +That's because a sign is represented by a single bit, so it can be set or not set for any number including a zero. + +In most cases, the distinction is unnoticeable, because operators are suited to treat them as the same. +``` + +## Tests: isFinite and isNaN +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 它們屬於 `數值` 類型,但並非 "一般" 數值,因此有特別的函式用來確認它們: @@ -296,7 +424,11 @@ JavaScript 在這種事件下不會觸發錯誤,它會盡全力讓數值符合 alert( isNaN("str") ); // true ``` +<<<<<<< HEAD 但我們需要這個函式嗎?不能只用 `=== NaN` 這樣的比較嗎?抱歉,答案是不行。`NaN` 這個值很特殊,它不會跟任何東西相等,包括它自己: +======= + But do we need this function? Can't we just use the comparison `=== NaN`? Unfortunately not. The value `NaN` is unique in that it does not equal anything, including itself: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( NaN === NaN ); // false @@ -319,6 +451,7 @@ let num = +prompt("Enter a number", ''); alert( isFinite(num) ); ``` +<<<<<<< HEAD 請注意空字串或是只有空格在內的字串,在包括 `isFinite` 在內的所有的數值函式中,都會被視為 `0`。 ```smart header="和 `Object.is` 相比" @@ -327,10 +460,52 @@ alert( isFinite(num) ); 1. `NaN` 適用:`Object.is(NaN, NaN) === true`,這是件好事。 2. 值 `0` 和 `-0` 是不同的:`Object.is(0, -0) === false`,技術上來說這是對的,因為在數值內部有個正負號位元不同,就算其它位元都為零。 +======= +Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. + +````smart header="`Number.isNaN` and `Number.isFinite`" +[Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) and [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) methods are the more "strict" versions of `isNaN` and `isFinite` functions. They do not autoconvert their argument into a number, but check if it belongs to the `number` type instead. + +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case, it returns `false`. + + ```js run + alert( Number.isNaN(NaN) ); // true + alert( Number.isNaN("str" / 2) ); // true + + // Note the difference: + alert( Number.isNaN("str") ); // false, because "str" belongs to the string type, not the number type + alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion + ``` + +- `Number.isFinite(value)` returns `true` if the argument belongs to the `number` type and it is not `NaN/Infinity/-Infinity`. In any other case, it returns `false`. + + ```js run + alert( Number.isFinite(123) ); // true + alert( Number.isFinite(Infinity) ); // false + alert( Number.isFinite(2 / 0) ); // false + + // Note the difference: + alert( Number.isFinite("123") ); // false, because "123" belongs to the string type, not the number type + alert( isFinite("123") ); // true, because isFinite converts string "123" into a number 123 + ``` + +In a way, `Number.isNaN` and `Number.isFinite` are simpler and more straightforward than `isNaN` and `isFinite` functions. In practice though, `isNaN` and `isFinite` are mostly used, as they're shorter to write. +```` + +```smart header="Comparison with `Object.is`" +There is a special built-in method `Object.is` that compares values like `===`, but is more reliable for two edge cases: + +1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing. +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct because internally the number has a sign bit that may be different even if all other bits are zeroes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 在其它情況下,`Object.is(a, b)` 與 `a === b` 相同。 +<<<<<<< HEAD 這種比較方式通常用於 JavaScript 規格內,當一個內部演算法需要比較兩個值是否完全相等時,會使用 `Object.is`(內部稱為[SameValue](https://tc39.github.io/ecma262/#sec-samevalue))。 +======= +We mention `Object.is` here, because it's often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## parseInt 和 parseFloat @@ -343,7 +518,11 @@ alert( +"100px" ); // NaN 唯一的例外是字串起始或結尾的空格,它們會被忽略。 +<<<<<<< HEAD 但在現實世界裡數字通常會給單位,像是 CSS 內的 `"100px"` 或 `"12pt"`。在許多國家貨幣符號也會放在數量之後,所以我們可能會有 `"19€"` 並想把其中的數值抽出來的情況。 +======= +But in real life, we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries, the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 這就是 `parseInt` 和 `parseFloat` 要處理的。 @@ -381,7 +560,11 @@ JavaScript 有個內建的 [Math](https://developer.mozilla.org/en/docs/Web/Java 幾個例子: `Math.random()` +<<<<<<< HEAD : 回傳由 0 至 1 的隨機數值(不包含 1) +======= +: Returns a random number from 0 to 1 (not including 1). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Math.random() ); // 0.1234567894322 @@ -389,8 +572,13 @@ JavaScript 有個內建的 [Math](https://developer.mozilla.org/en/docs/Web/Java alert( Math.random() ); // ...(其它隨機數值) ``` +<<<<<<< HEAD `Math.max(a, b, c...)` / `Math.min(a, b, c...)` : 回傳引數中的 最大值/最小值。 +======= +`Math.max(a, b, c...)` and `Math.min(a, b, c...)` +: Returns the greatest and smallest from the arbitrary number of arguments. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Math.max(3, 5, -10, 0, 1) ); // 5 @@ -398,28 +586,58 @@ JavaScript 有個內建的 [Math](https://developer.mozilla.org/en/docs/Web/Java ``` `Math.pow(n, power)` +<<<<<<< HEAD : 回傳 `n` 的給定次方數 +======= +: Returns `n` raised to the given power. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Math.pow(2, 10) ); // 2 的 10 次方 = 1024 ``` +<<<<<<< HEAD 在 `Math` 物件中有更多的函式與常數,包括三角函數,你可以在 [Math 的文件](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) 中找到。 +======= +There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math object](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 總結 +<<<<<<< HEAD 要寫一個擁有許多零的數值: - 附加 `"e"` 與零的數量到數值後方,像是:`123e6` 是 `123` 後有 6 個零。 - `"e"` 之後的負數會使得數值除以 1 的後面帶有給定數量的零,像是百萬分之一這樣。 +======= +To write numbers with many zeroes: + +- Append `"e"` with the zeroes count to the number. Like: `123e6` is the same as `123` with 6 zeroes `123000000`. +- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. E.g. `123e-6` means `0.000123` (`123` millionths). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 對於不同的數值系統: +<<<<<<< HEAD - 可以直接寫十六進位(`0x`)、八進位(`0o`)和二進位(`0b`)系統。 - `parseInt(str, base)` 於給定 `base` 為基底的數值系統內,將字串 `str` 解析為整數,其中 `2 ≤ base ≤ 36`。 - `num.toString(base)` 於給定 `base` 為基底的數值系統內,將數值轉換為字串。 要轉換像是 `12pt` 和 `100px` 的值為數值: +======= +- Can write numbers directly in hex (`0x`), octal (`0o`) and binary (`0b`) systems. +- `parseInt(str, base)` parses the string `str` into an integer in numeral system with given `base`, `2 ≤ base ≤ 36`. +- `num.toString(base)` converts a number to a string in the numeral system with the given `base`. + +For regular number tests: + +- `isNaN(value)` converts its argument to a number and then tests it for being `NaN` +- `Number.isNaN(value)` checks whether its argument belongs to the `number` type, and if so, tests it for being `NaN` +- `isFinite(value)` converts its argument to a number and then tests it for not being `NaN/Infinity/-Infinity` +- `Number.isFinite(value)` checks whether its argument belongs to the `number` type, and if so, tests it for not being `NaN/Infinity/-Infinity` + +For converting values like `12pt` and `100px` to a number: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - 對於 "軟性" 轉換使用 `parseInt/parseFloat`,這會從字串讀取數值且在錯誤之前回傳它盡可能讀取到的值。 @@ -432,3 +650,7 @@ JavaScript 有個內建的 [Math](https://developer.mozilla.org/en/docs/Web/Java - 當你需要時,查看 [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) 物件,這個函式庫很小,但涵蓋基礎的需求。 +<<<<<<< HEAD +======= +- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small but can cover basic needs. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/03-string/1-ucfirst/solution.md b/1-js/05-data-types/03-string/1-ucfirst/solution.md index 00ec8c14d..e936bc35a 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/solution.md +++ b/1-js/05-data-types/03-string/1-ucfirst/solution.md @@ -8,12 +8,16 @@ let newStr = str[0].toUpperCase() + str.slice(1); 但仍然有個小問題。若 `str` 為空,則 `str[0]` 會是 `undefined`。而 `undefined` 不會有 `toUpperCase()` 方法,我們會得到錯誤。 +<<<<<<< HEAD 有兩種處理方式: 1. 使用 `str.charAt(0)`,它會永遠回傳一個字串 (可能為空) 。 2. 為空字串添加一個測試。 這是第二個作法: +======= +The easiest way out is to add a test for an empty string, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run demo function ucFirst(str) { @@ -24,4 +28,3 @@ function ucFirst(str) { alert( ucFirst("john") ); // John ``` - diff --git a/1-js/05-data-types/03-string/3-truncate/solution.md b/1-js/05-data-types/03-string/3-truncate/solution.md index 269805fab..9999b8a84 100644 --- a/1-js/05-data-types/03-string/3-truncate/solution.md +++ b/1-js/05-data-types/03-string/3-truncate/solution.md @@ -1,6 +1,10 @@ 最大的長度必須是 `maxlength`,因此我們需將其剪短一點,為省略號留岀空間。 +<<<<<<< HEAD 注意,省略號實際上是一個單獨的 unicode 字元,那不是三個點。 +======= +Note that there is actually a single Unicode character for an ellipsis. That's not three dots. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run demo function truncate(str, maxlength) { diff --git a/1-js/05-data-types/03-string/3-truncate/task.md b/1-js/05-data-types/03-string/3-truncate/task.md index c068c46cb..5dce539d9 100644 --- a/1-js/05-data-types/03-string/3-truncate/task.md +++ b/1-js/05-data-types/03-string/3-truncate/task.md @@ -11,7 +11,7 @@ importance:5 例如: ```js -truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…" +truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…" -truncate("Hi everyone!", 20) = "Hi everyone!" +truncate("Hi everyone!", 20) == "Hi everyone!" ``` diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index 32f352782..582df7706 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -49,9 +49,15 @@ let guestList = "Guests: // 錯誤:意料之外的令牌(標記、符號) * John"; ``` +<<<<<<< HEAD 單引號和雙引號來自創造語言的古早時代,當時沒有考慮對多行字串的需求。 反引號出現得較晚,因此用途更廣。 反引號還允許我們,在第一個反引號前指定 "模板功能 (template function)"。 語法為 func`string`。 函數 `func` 會被自動調用,接收字串和嵌入式表達式並處理他們,這稱為 "標記模板 (tagged templates)"。 此功能使實現自定義模板更加容易,但很少在實踐中使用。您可以在 [手冊](mdn:/JavaScript/Reference/Template_literals#Tagged_templates) 中了解更多信息。 +======= +Single and double quotes come from ancient times of language creation, when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile. + +Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`. The function `func` is called automatically, receives the string and embedded expressions and can process them. This feature is called "tagged templates", it's rarely seen, but you can read about it in the MDN: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 特殊字元 @@ -61,10 +67,17 @@ let guestList = "Guests: // 錯誤:意料之外的令牌(標記、符號) ```js run let guestList = "Guests:\n * John\n * Pete\n * Mary"; +<<<<<<< HEAD alert(guestList); // 一個多行的來賓列表 ``` 例如, 這兩行做法是相同的,只是書寫方式不同: +======= +alert(guestList); // a multiline list of guests, same as above +``` + +As a simpler example, these two lines are equal, just written differently: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str1 = "Hello\nWorld"; // 使用一個 "換行符" 創建的兩行字串 @@ -76,12 +89,17 @@ World`; alert(str1 == str2); // true ``` +<<<<<<< HEAD 還有其他一些不太常見的 "特殊" 字元。 這裡是完整的列表: +======= +There are other, less common special characters: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 | 字元 | 描述 | |-----------|-------------| +<<<<<<< HEAD |`\n`| 換行、新行| |`\r`| 回車:不單獨使用。 Windows 純文字檔案使用兩個字元組合 `\r\n` 來表示換行。 | |`\'`, `\"`| 引號。| @@ -103,6 +121,24 @@ alert( "\u{1F60D}" ); // 😍, 一個笑臉符號(另一個長的 unicode) 所有特殊字元均以反斜槓字元 `\` 開頭。也稱為 "跳脫字元"。 當我們想在字串中安插引號,也可以使用它。 +======= +|`\n`|New line| +|`\r`|In Windows text files a combination of two characters `\r\n` represents a new break, while on non-Windows OS it's just `\n`. That's for historical reasons, most Windows software also understands `\n`. | +|`\'`, `\"`, \\`|Quotes| +|`\\`|Backslash| +|`\t`|Tab| +|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- mentioned for completeness, coming from old times, not used nowadays (you can forget them right now). | + +As you can see, all special characters start with a backslash character `\`. It is also called an "escape character". + +Because it's so special, if we need to show an actual backslash `\` within the string, we need to double it: + +```js run +alert( `The backslash: \\` ); // The backslash: \ +``` + +So-called "escaped" quotes `\'`, `\"`, \\` are used to insert a quote into the same-quoted string. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 例如: @@ -112,12 +148,17 @@ alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! 就像你看到的,我們必須在內部的引號前加上反斜槓 `\`,否則它將結束字串。 +<<<<<<< HEAD 當然,只有跟該封閉引號相同的引號才需要跳脫。因此,作為更優雅的解決方案,我們可以轉為使用雙引號或反引號: +======= +Of course, only the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run -alert( `I'm the Walrus!` ); // I'm the Walrus! +alert( "I'm the Walrus!" ); // I'm the Walrus! ``` +<<<<<<< HEAD 請注意,反斜槓 `\` 是為了使 JavaScript 正確讀取字串,然後就消失。儲存的字串中沒有 `\`。你可以在上面的 `alert` 範例中,清楚地看到這點。 但若我們需要在字串中顯示實際的反斜槓 `\` 怎麼辦? @@ -127,6 +168,9 @@ alert( `I'm the Walrus!` ); // I'm the Walrus! ```js run alert( `The backslash: \\` ); // The backslash: \ ``` +======= +Besides these special characters, there's also a special notation for Unicode codes `\u…`, it's rarely used and is covered in the optional chapter about [Unicode](info:unicode). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 字串長度 @@ -141,33 +185,55 @@ alert( `My\n`.length ); // 3 ```warn header="`length` 是一個屬性" 有其他程式語言背景的人,有時會打字錯誤,用 `str.length()` 而不是 `str.length`。那是行不通的。 +<<<<<<< HEAD 請注意,`str.length` 是數值屬性,而不是函數,不需在後面添加括號。 +======= +Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. Not `.length()`, but `.length`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 訪問字元 +<<<<<<< HEAD 在位置 `pos` 取一個字元,請使用方括號 `[pos]` 或調用方法 [str.charAt(pos)](mdn:js/String/charAt)。 第一個字元是由位置零開始: +======= +To get a character at position `pos`, use square brackets `[pos]` or call the method [str.at(pos)](mdn:js/String/at). The first character starts from the zero position: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = `Hello`; // 第一個字元 alert( str[0] ); // H -alert( str.charAt(0) ); // H +alert( str.at(0) ); // H // 最後一個字元 alert( str[str.length - 1] ); // o +alert( str.at(-1) ); ``` +<<<<<<< HEAD 使用方括號,是獲取字元的現代化方式,而 `charAt` 的存在主要出於歷史性原因。 它們之間的唯一區別是,如果找不到字元,則 `[]` 將返回 `undefined`,而 `charAt` 返回一個空字串: +======= +As you can see, the `.at(pos)` method has a benefit of allowing negative position. If `pos` is negative, then it's counted from the end of the string. + +So `.at(-1)` means the last character, and `.at(-2)` is the one before it, etc. + +The square brackets always return `undefined` for negative indexes, for instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = `Hello`; +<<<<<<< HEAD alert( str[1000] ); // undefined alert( str.charAt(1000) ); // '' (一個空字串) +======= +alert( str[-2] ); // undefined +alert( str.at(-2) ); // l +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` 我們也可以使用 `for..of` 來迭代過每個字元 @@ -216,7 +282,7 @@ alert( 'Interface'.toLowerCase() ); // interface 或者,我們想要一個單獨的小寫字元: -```js +```js run alert( 'Interface'[0].toLowerCase() ); // 'i' ``` @@ -241,7 +307,11 @@ alert( str.indexOf('widget') ); // -1,沒找到,檢索是有區分大小寫 alert( str.indexOf("id") ); // 1, "id" 在索引位置 "1" 處就被找到 (..idget 中的 id) 。 ``` +<<<<<<< HEAD 第二個參數是可選的,允許我們從給定的位置開始檢索。 +======= +The optional second parameter allows us to start searching from a given position. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉例來說,`"id"` 第一次出現的位置是 `1`,要尋找下一個,我們從位置 `2` 開始找。 @@ -312,6 +382,7 @@ if (str.indexOf("Widget") != -1) { } ``` +<<<<<<< HEAD #### 按位(bitwise)NOT 技巧 這裡使用一個古老的技巧, [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` 運算子。它將數字轉換為一個 32 位元 (32-bit)的整數(如果有小數點則全部捨棄),然後反轉它的二進製表示中的所有位元。 @@ -351,6 +422,8 @@ if (~str.indexOf("Widget")) { 現在,我們只能在舊程式碼中看到此技巧,因為現代 JavaScript 提供了 `.includes` 方法(見下文)。 +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### includes, startsWith, endsWith 更現代的方法 [str.includes(substr, pos)](mdn:js/String/includes) 會根據 `str` 中是否包含 `substr` 來回傳 `true/false`。 @@ -373,8 +446,13 @@ alert( "Widget".includes("id", 3) ); // false, 從索引位置 3 開始檢索不 方法 [str.startsWith](mdn:js/String/startsWith) 和 [str.endsWith](mdn:js/String/endsWith) 完全如它們所說: ```js run +<<<<<<< HEAD alert( "Widget".startsWith("Wid") ); // true, "Widget" 以 "Wid" 開始 alert( "Widget".endsWith("get") ); // true, "Widget" 以 "get" 結尾 +======= +alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Widget" starts with "Wid" +alert( "Wid*!*get*/!*".endsWith("get") ); // true, "Widget" ends with "get" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## 取得一個子字串 @@ -410,9 +488,16 @@ alert( str.slice(-4, -1) ); // 'gif' `str.substring(start [, end])` : 回傳該字串 `start` 和 `end` *之間* 的部分。 +<<<<<<< HEAD 它幾乎與 `slice` 一樣,但它允許 `start` 可以大於 `end`。 例如: +======= +`str.substring(start [, end])` +: Returns the part of the string *between* `start` and `end` (not including `end`). + + This is almost the same as `slice`, but it allows `start` to be greater than `end` (in this case it simply swaps `start` and `end` values). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let str = "st*!*ring*/!*ify"; @@ -447,16 +532,39 @@ alert( str.substr(-4, 2) ); // 'gi', 從位置 4 開始取 2 個字元 讓我們回顧一下這些方法,以免混淆: +<<<<<<< HEAD | 方法 | 選擇器 | 負號參數 | |--------|-----------|-----------| | `slice(start, end)` | 從 `start` 到 `end` (不包含 `end`) | 允許負號參數 | | `substring(start, end)` | `start` 與 `end` 之間 | 負號參數視為 `0`| | `substr(start, length)` | 從 `start` 取 `length` 個字元 | 允許 `start` 為負數 | +======= + ```js run + let str = "strin*!*gi*/!*fy"; + alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters + ``` + + This method resides in the [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) of the language specification. It means that only browser-hosted Javascript engines should support it, and it's not recommended to use it. In practice, it's supported everywhere. + +Let's recap these methods to avoid any confusion: + +| method | selects... | negatives | +|--------|-----------|-----------| +| `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives | +| `substring(start, end)` | between `start` and `end` (not including `end`)| negative values mean `0` | +| `substr(start, length)` | from `start` get `length` characters | allows negative `start` | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="使用哪一個?" 它們都可以勝任工作。從形式上看,`substr` 有個小缺點:它不是在 JavaScript 的核心規範中被描述,而是寫在附件 B 中,它涵蓋了主要由於歷史因素而存在的瀏覽器特性。因此,非瀏覽器環境可能無法支持它,但實際上它在任何地方都可運作。 +<<<<<<< HEAD 另外兩個變種,`slice` 更靈活一點,它允許負號參數,且寫得短些。所以只要記住這三個方法中的 `slice` 就夠了。 +======= +Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. + +So, for practical use it's enough to remember only `slice`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Comparing strings 比對字串 @@ -479,6 +587,7 @@ alert( str.substr(-4, 2) ); // 'gi', 從位置 4 開始取 2 個字元 這可能導致奇怪的結果,如果我們排序這些國名,一般人們會認為列表中 `Zealand` 會排在 `Österreich` 之後。 +<<<<<<< HEAD 為了明白發生什麼事,我們回顧一下 JavaScript 中,字串的內部表現形式。 所有字串都是用 [UTF-16](https://en.wikipedia.org/wiki/UTF-16) 編碼的。即:每一個字元都有一個對應的數字代碼。有一些特殊的方法可以獲取代碼表示的字符,以及字符對應的代碼。 @@ -491,10 +600,26 @@ alert( str.substr(-4, 2) ); // 'gi', 從位置 4 開始取 2 個字元 alert( "z".codePointAt(0) ); // 122 alert( "Z".codePointAt(0) ); // 90 ``` +======= +To understand what happens, we should be aware that strings in Javascript are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. + +There are special methods that allow to get the character for the code and back: + +`str.codePointAt(pos)` +: Returns a decimal number representing the code for the character at position `pos`: + + ```js run + // different case letters have different codes + alert( "Z".codePointAt(0) ); // 90 + alert( "z".codePointAt(0) ); // 122 + alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value) + ``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `String.fromCodePoint(code)`: 用字元的代碼數字 `code` 創建字元 +<<<<<<< HEAD ```js run alert( String.fromCodePoint(90) ); // Z ``` @@ -505,6 +630,12 @@ alert( String.fromCodePoint(90) ); // Z // 在十六進制系統中 90 為 5a alert( '\u005a' ); // Z ``` +======= + ```js run + alert( String.fromCodePoint(90) ); // Z + alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument) + ``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 現在我們來看代碼 `60..220` 的字元(拉丁字母和一些額外的字元)方法是用它們創建一個字串: @@ -515,6 +646,7 @@ for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); +// Output: // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜ ``` @@ -525,16 +657,27 @@ alert( str ); 字元通過它們的數字代碼進行比較。代碼較大表示該字元較大。 `a` 代碼 (97) 大於 `Z` 代碼 (90)。 +<<<<<<< HEAD - 所有小寫字母都在大寫字母後面,因為它們的代碼更大。 - 一些字母像是 `Ö` 與主要字母分開。在這裏,它的代碼比從 `a` 到 `z` 的任何字元代碼都大。 ### Correct comparisons 正確的比較 +======= +- All lowercase letters go after uppercase letters because their codes are greater. +- Some letters like `Ö` stand apart from the main alphabet. Here, its code is greater than anything from `a` to `z`. + +### Correct comparisons [#correct-comparisons] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 執行字串比較,"正確" 的演算法比看起來更複雜,因為不同語言的字母是不同的。 所以,瀏覽器需要知道要比較的語言是什麼。 +<<<<<<< HEAD 幸運的是,所有現代瀏覽器(IE10 -- 需要額外的函式庫 [Intl.JS](https://github.com/andyearnshaw/Intl.js/))都支援國際化標準 [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf)。 +======= +Luckily, modern browsers support the internationalization standard [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 It provides a special method to compare strings in different languages, following their rules. 它提供一種特殊方法來比較不同的語言的字串,遵循語言的規則。 @@ -553,6 +696,7 @@ alert( 'Österreich'.localeCompare('Zealand') ); // -1 這個方法實際上在 [此文件](mdn:js/String/localeCompare) 指定了兩個額外的參數,它允許指定語言(預設會在環境中獲取語言,字母順序會根據語言不同)並設定額外規則,像是區分大小寫,或是否將 `"a"` 和 `"á"` 視為相同等等。 +<<<<<<< HEAD ## 內部的, Unicode ```warn header="進階知識" @@ -672,10 +816,31 @@ alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true - 字串大小寫轉換,使用:`toLowerCase/toUpperCase`。 - 要搜尋一個子字串,使用: `indexOf`,或用 `includes/startsWith/endsWith` 來做簡單的確認。 - 要依據語言比較字串時使用:`localeCompare`,否則將使用字元編碼比較 +======= +## Summary + +- There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions `${…}`. +- We can use special characters, such as a line break `\n`. +- To get a character, use: `[]` or `at` method. +- To get a substring, use: `slice` or `substring`. +- To lowercase/uppercase a string, use: `toLowerCase/toUpperCase`. +- To look for a substring, use: `indexOf`, or `includes/startsWith/endsWith` for simple checks. +- To compare strings according to the language, use: `localeCompare`, otherwise they are compared by character codes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 關於字串,其他ㄧ些有用的方法: +<<<<<<< HEAD - `str.trim()` -- 移除 ("trims") 字串前後的空格。 - `str.repeat(n)` -- 重複該字串 `n` 次。 - ...更多內容請參考 [manual 手冊](mdn:js/String). 字串也有使用正則表達式進行 檢索/替換 的方法。但這是個大主題,因此,將在一個單獨的教程章節 中說明。 +======= +- `str.trim()` -- removes ("trims") spaces from the beginning and end of the string. +- `str.repeat(n)` -- repeats the string `n` times. +- ...and more to be found in the [manual](mdn:js/String). + +Strings also have methods for doing search/replace with regular expressions. But that's big topic, so it's explained in a separate tutorial section . + +Also, as of now it's important to know that strings are based on Unicode encoding, and hence there're issues with comparisons. There's more about Unicode in the chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md index 91a9b142d..a0867b987 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md +++ b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md @@ -57,9 +57,15 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 ``` +<<<<<<< HEAD 該解法的時間複雜度為 [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation) 。換句話說,若我們增加兩倍的陣列大小,該演算法就會多花四倍的時間。 對於大陣列來說(1000、10000 或更多項目)該演算法可能導致嚴重的延遲。 +======= +The solution has a time complexity of [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer. + +For big arrays (1000, 10000 or more items) such algorithms can lead to serious sluggishness. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 # 快的解法 @@ -93,3 +99,7 @@ alert( getMaxSubSum([-1, -2, -3]) ); // 0 你可以在這裡找到更多關於該演算法的細節資訊:[Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem)。若覺得對於它如何運作依然沒那麼明顯,請追蹤上述例子中的演算法來看看它是如何運作的,這麼做會比任何文字還要有用。 +<<<<<<< HEAD +======= +You can find more detailed information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/task.md b/1-js/05-data-types/04-array/10-maximal-subarray/task.md index 337d7cd84..45a935300 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/task.md +++ b/1-js/05-data-types/04-array/10-maximal-subarray/task.md @@ -10,6 +10,7 @@ importance: 2 寫一個函式 `getMaxSubSum(arr)` 並回傳該加總值。 +<<<<<<< HEAD 舉個例: ```js @@ -19,6 +20,18 @@ getMaxSubSum([-1, 2, 3, -9, *!*11*/!*]) = 11 getMaxSubSum([-2, -1, *!*1, 2*/!*]) = 3 getMaxSubSum([*!*100*/!*, -9, 2, -3, 5]) = 100 getMaxSubSum([*!*1, 2, 3*/!*]) = 6(全拿) +======= +For instance: + +```js +getMaxSubSum([-1, *!*2, 3*/!*, -9]) == 5 (the sum of highlighted items) +getMaxSubSum([*!*2, -1, 2, 3*/!*, -9]) == 6 +getMaxSubSum([-1, 2, 3, -9, *!*11*/!*]) == 11 +getMaxSubSum([-2, -1, *!*1, 2*/!*]) == 3 +getMaxSubSum([*!*100*/!*, -9, 2, -3, 5]) == 100 +getMaxSubSum([*!*1, 2, 3*/!*]) == 6 (take all) +``` +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 若所有項目都是負值,代表我們不要拿任何東西(子陣列為空),所以加總為零: diff --git a/1-js/05-data-types/04-array/2-create-array/task.md b/1-js/05-data-types/04-array/2-create-array/task.md index 87dfd0541..7fb709964 100644 --- a/1-js/05-data-types/04-array/2-create-array/task.md +++ b/1-js/05-data-types/04-array/2-create-array/task.md @@ -6,11 +6,19 @@ importance: 5 來試試 5 個陣列操作吧。 +<<<<<<< HEAD 1. 建立一個擁有 "Jazz" 和 "Blues" 作為項目的陣列 `styles`。 2. 附加 "Rock-n-Roll" 到其末端。 3. 使用 "Classics" 替換正中央的值,你寫的用於找出正中央值的程式碼,應該要能對任意奇數長度陣列運作。 4. 抽離陣列第一個值並顯示它。 5. 由前端附加 `Rap` 和 `Reggae` 至陣列中。 +======= +1. Create an array `styles` with items "Jazz" and "Blues". +2. Append "Rock-n-Roll" to the end. +3. Replace the value in the middle with "Classics". Your code for finding the middle value should work for any arrays with odd length. +4. Strip off the first value of the array and show it. +5. Prepend `Rap` and `Reggae` to the array. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 這些過程中的陣列要長這樣: diff --git a/1-js/05-data-types/04-array/3-call-array-this/solution.md b/1-js/05-data-types/04-array/3-call-array-this/solution.md index fa8b24464..4e473058f 100644 --- a/1-js/05-data-types/04-array/3-call-array-this/solution.md +++ b/1-js/05-data-types/04-array/3-call-array-this/solution.md @@ -9,7 +9,7 @@ arr.push(function() { alert( this ); }) -arr[2](); // "a","b",function +arr[2](); // a,b,function(){...} ``` 該陣列有三個值:一開始有兩個,再加上該函式。 diff --git a/1-js/05-data-types/04-array/3-call-array-this/task.md b/1-js/05-data-types/04-array/3-call-array-this/task.md index 6b40b81e3..8810b6d38 100644 --- a/1-js/05-data-types/04-array/3-call-array-this/task.md +++ b/1-js/05-data-types/04-array/3-call-array-this/task.md @@ -11,7 +11,7 @@ let arr = ["a", "b"]; arr.push(function() { alert( this ); -}) +}); arr[2](); // ? ``` diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 1f2bb1e5b..636e35f8a 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -91,7 +91,42 @@ let fruits = [ 該 "尾部逗號" 風格讓它更容易 插入/移除 項目,因為每一行都變得很相似。 ```` +<<<<<<< HEAD ## pop/push 和 shift/unshift 方法 +======= +## Get last elements with "at" + +[recent browser="new"] + +Let's say we want the last element of the array. + +Some programming languages allow the use of negative indexes for the same purpose, like `fruits[-1]`. + +Although, in JavaScript it won't work. The result will be `undefined`, because the index in square brackets is treated literally. + +We can explicitly calculate the last element index and then access it: `fruits[fruits.length - 1]`. + +```js run +let fruits = ["Apple", "Orange", "Plum"]; + +alert( fruits[fruits.length-1] ); // Plum +``` + +A bit cumbersome, isn't it? We need to write the variable name twice. + +Luckily, there's a shorter syntax: `fruits.at(-1)`: + +```js run +let fruits = ["Apple", "Orange", "Plum"]; + +// same as fruits[fruits.length-1] +alert( fruits.at(-1) ); // Plum +``` + +In other words, `arr.at(i)`: +- is exactly the same as `arr[i]`, if `i >= 0`. +- for negative values of `i`, it steps back from the end of the array. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 [佇列(queue)](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) 是陣列最常見的用途之一。在計算機科學中,這代表著支援這兩種操作的有序群集元素: @@ -119,9 +154,15 @@ let fruits = [ 對於堆疊而言,最後被放入的物品會最早被取得,也被稱為 LIFO (Last-In-First-Out) 原則。對於佇列而言,則是 FIFO (First-In-First-Out)。 +<<<<<<< HEAD JavaScript 中的陣列被視為佇列與堆疊兩者皆可。它們允許你 加入/移除 元素 至/由 最前端或最末端都可以。 在計算機科學中,允許這種運作的資料結構被稱為 [雙端佇列(deque)](https://en.wikipedia.org/wiki/Double-ended_queue)。 +======= +Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements, both to/from the beginning or the end. + +In computer science, the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **運作於陣列末端的方法:** @@ -136,6 +177,8 @@ JavaScript 中的陣列被視為佇列與堆疊兩者皆可。它們允許你 alert( fruits ); // Apple, Orange ``` + Both `fruits.pop()` and `fruits.at(-1)` return the last element of the array, but `fruits.pop()` also modifies the array by removing it. + `push` : 附加元素至陣列末端: @@ -154,7 +197,7 @@ JavaScript 中的陣列被視為佇列與堆疊兩者皆可。它們允許你 `shift` : 抽取陣列第一個元素並回傳它: - ```js + ```js run let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.shift() ); // 移除 Apple 並 alert 它 @@ -165,7 +208,7 @@ JavaScript 中的陣列被視為佇列與堆疊兩者皆可。它們允許你 `unshift` : 於陣列最前端加上該元素: - ```js + ```js run let fruits = ["Orange", "Pear"]; fruits.unshift('Apple'); @@ -191,7 +234,11 @@ alert( fruits ); 它們延伸了物件,提供特殊方法以使得有序群集資料可以運作,並給予 `length` 屬性,但其核心依然是個物件。 +<<<<<<< HEAD 要記得,JavaScript 內只有 7 種基本類型。陣列是個物件類型,因此會產生像是物件的行為。 +======= +Remember, there are only eight basic data types in JavaScript (see the [Data types](info:types) chapter for more info). Array is an object and thus behaves like an object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉個例,它是經由參考被複製的: @@ -207,7 +254,11 @@ arr.push("Pear"); // 經由參考修改陣列 alert( fruits ); // Banana, Pear - 現在有兩個物品 ``` +<<<<<<< HEAD ...但使陣列真的變得特殊的是它們的內部表示方式。引擎試圖以連續記憶體區塊,一個接一個儲存它的元素,就像本章插圖描繪的那樣。同樣也存在其他優化方法,來讓陣列可以很快地運作。 +======= +...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 但若我們放棄以 "有序群集" 的方式來操作陣列,並開始將它視為普通物件來使用時,這些優化就都不會生效了。 @@ -245,7 +296,11 @@ fruits.age = 25; // 以任意名稱建立一個屬性 fruits.shift(); // 由前端取出 1 個元素 ``` +<<<<<<< HEAD 只取出並移除編號 `0` 的元素還不夠,其它元素也需要被重新編號。 +======= +It's not enough to take and remove the element with the index `0`. Other elements need to be renumbered as well. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `shift` 操作必須做三件事情: @@ -361,11 +416,19 @@ alert( arr[3] ); // undefined:值回不來了 let arr = *!*new Array*/!*("Apple", "Pear", "etc"); ``` +<<<<<<< HEAD 這很少被用到,因為中括號 `[]` 較簡短,且這語法還有個微妙的特性。 +======= +It's rarely used, because square brackets `[]` are shorter. Also, there's a tricky feature with it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 若 `new Array` 使用單一數值作為引數來呼叫,那它會建立一個 *內部沒有項目而只有給定 length* 的陣列。 +<<<<<<< HEAD 來看看這會如何拿石頭砸自己的腳: +======= +Let's see how one can shoot themselves in the foot: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let arr = new Array(2); // 它會建立 [2] 這樣的陣列嗎? @@ -375,9 +438,13 @@ alert( arr[0] ); // 是 undefined!沒有元素在內。 alert( arr.length ); // 但 length 是 2 ``` +<<<<<<< HEAD 在上面的程式碼中,`new Array(number)` 的所有元素都是 `undefined`。 要避免這種驚喜,我們通常使用中括號語法就好,除非我們真的知道自己在做什麼。 +======= +To avoid such surprises, we usually use square brackets, unless we really know what we're doing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 多維度陣列 @@ -390,7 +457,11 @@ let matrix = [ [7, 8, 9] ]; +<<<<<<< HEAD alert( matrix[1][1] ); // 5,最中央的元素 +======= +alert( matrix[0][1] ); // 2, the second value of the first inner array +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## toString @@ -424,10 +495,62 @@ alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21" ``` +<<<<<<< HEAD ## 總結 +======= +## Don't compare arrays with == + +Arrays in JavaScript, unlike some other programming languages, shouldn't be compared with operator `==`. + +This operator has no special treatment for arrays, it works with them as with any objects. + +Let's recall the rules: + +- Two objects are equal `==` only if they're references to the same object. +- If one of the arguments of `==` is an object, and the other one is a primitive, then the object gets converted to primitive, as explained in the chapter . +- ...With an exception of `null` and `undefined` that equal `==` each other and nothing else. + +The strict comparison `===` is even simpler, as it doesn't convert types. + +So, if we compare arrays with `==`, they are never the same, unless we compare two variables that reference exactly the same array. + +For example: +```js run +alert( [] == [] ); // false +alert( [0] == [0] ); // false +``` + +These arrays are technically different objects. So they aren't equal. The `==` operator doesn't do item-by-item comparison. + +Comparison with primitives may give seemingly strange results as well: + +```js run +alert( 0 == [] ); // true + +alert('0' == [] ); // false +``` + +Here, in both cases, we compare a primitive with an array object. So the array `[]` gets converted to primitive for the purpose of comparison and becomes an empty string `''`. + +Then the comparison process goes on with the primitives, as described in the chapter : + +```js run +// after [] was converted to '' +alert( 0 == '' ); // true, as '' becomes converted to number 0 + +alert('0' == '' ); // false, no type conversion, different strings +``` + +So, how to compare arrays? + +That's simple: don't use the `==` operator. Instead, compare them item-by-item in a loop or using iteration methods explained in the next chapter. + +## Summary +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 陣列是個特殊的物件,適合儲存管理有序的資料項目。 +<<<<<<< HEAD - 宣告: ```js @@ -439,11 +562,33 @@ alert( "1,2" + 1 ); // "1,21" ``` 呼叫 `new Array(number)` 會建立一個有著指定 length 的陣列,但其內都沒有元素。 +======= +The declaration: + +```js +// square brackets (usual) +let arr = [item1, item2...]; + +// new Array (exceptionally rare) +let arr = new Array(item1, item2...); +``` + +The call to `new Array(number)` creates an array with the given length, but without elements. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `length` 屬性是陣列長度,或更精準地說,是它的最大數值索引值再加上一。它由陣列的方法自動調整。 - 若我們手動縮短 `length`,則陣列會被截斷。 +<<<<<<< HEAD 我們可以將陣列視為雙端佇列來使用以下操作: +======= +Getting the elements: + +- we can get element by its index, like `arr[0]` +- also we can use `at(i)` method that allows negative indexes. For negative values of `i`, it steps back from the end of the array. If `i >= 0`, it works same as `arr[i]`. + +We can use an array as a deque with the following operations: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - `push(...items)` 增加 `items` 至末端。 - `pop()` 由末端移除元素並回傳。 @@ -457,3 +602,11 @@ alert( "1,2" + 1 ); // "1,21" 我們會在章節 中回顧陣列,並學習更多像是增加、移除、取出元素和排序陣列等的方法。 +<<<<<<< HEAD +======= +To compare arrays, don't use the `==` operator (as well as `>`, `<` and others), as they have no special treatment for arrays. They handle them as any objects, and it's not what we usually want. + +Instead you can use `for..of` loop to compare arrays item-by-item. + +We will continue with arrays and study more methods to add, remove, extract elements and sort arrays in the next chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js index 02299e307..3859aab71 100644 --- a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js @@ -8,13 +8,21 @@ describe("groupById", function() { ]; assert.deepEqual(groupById(users), { +<<<<<<< HEAD john: {id: 'john', name: "John Smith", age: 20} +======= + john: {id: 'john', name: "John Smith", age: 20}, +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ann: {id: 'ann', name: "Ann Smith", age: 24}, pete: {id: 'pete', name: "Pete Peterson", age: 31}, }); }); it("works with an empty array", function() { +<<<<<<< HEAD +======= + users = []; +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 assert.deepEqual(groupById(users), {}); }); }); diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md index 1e402ec05..0fac8d49e 100644 --- a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md @@ -4,7 +4,11 @@ importance: 4 # Create keyed object from array +<<<<<<< HEAD Let's say we received an array of users in the form `{id:..., name:..., age... }`. +======= +Let's say we received an array of users in the form `{id:..., name:..., age:... }`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values. @@ -23,7 +27,11 @@ let usersById = groupById(users); // after the call we should have: usersById = { +<<<<<<< HEAD john: {id: 'john', name: "John Smith", age: 20} +======= + john: {id: 'john', name: "John Smith", age: 20}, +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ann: {id: 'ann', name: "Ann Smith", age: 24}, pete: {id: 'pete', name: "Pete Peterson", age: 31}, } diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/task.md b/1-js/05-data-types/05-array-methods/2-filter-range/task.md index 18b2c1d9b..46e47c93d 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/task.md +++ b/1-js/05-data-types/05-array-methods/2-filter-range/task.md @@ -4,7 +4,7 @@ importance: 4 # Filter range -Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements between `a` and `b` in it and returns an array of them. +Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements with values higher or equal to `a` and lower or equal to `b` and return a result as an array. The function should not modify the array. It should return the new array. diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js index db32d9a11..241b74c6e 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js @@ -4,13 +4,13 @@ describe("filterRangeInPlace", function() { let arr = [5, 3, 8, 1]; - filterRangeInPlace(arr, 1, 4); + filterRangeInPlace(arr, 2, 5); - assert.deepEqual(arr, [3, 1]); + assert.deepEqual(arr, [5, 3]); }); it("doesn't return anything", function() { assert.isUndefined(filterRangeInPlace([1,2,3], 1, 4)); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js index 45ef1619d..f62452a5f 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js @@ -10,14 +10,14 @@ function Calculator() { let split = str.split(' '), a = +split[0], op = split[1], - b = +split[2] + b = +split[2]; if (!this.methods[op] || isNaN(a) || isNaN(b)) { return NaN; } return this.methods[op](a, b); - } + }; this.addMethod = function(name, func) { this.methods[name] = func; diff --git a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md index 5d8bf4a13..2d8d4fb0e 100644 --- a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md @@ -25,7 +25,7 @@ alert( usersMapped[0].id ); // 1 alert( usersMapped[0].fullName ); // John Smith ``` -Please note that in for the arrow functions we need to use additional brackets. +Please note that in the arrow functions we need to use additional brackets. We can't write like this: ```js diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md index 9f1ade707..cfaf9761a 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md @@ -1,6 +1,6 @@ ```js run no-beautify function sortByAge(arr) { - arr.sort((a, b) => a.age > b.age ? 1 : -1); + arr.sort((a, b) => a.age - b.age); } let john = { name: "John", age: 25 }; diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 301696440..5f7a8b8bc 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,6 +1,6 @@ # Array methods -Arrays provide a lot of methods. To make things easier, in this chapter they are split into groups. +Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. ## Add/remove items @@ -32,19 +32,19 @@ alert( arr.length ); // 3 The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`. -That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now. +That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now. So, special methods should be used. -The [arr.splice(start)](mdn:js/Array/splice) method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements. +The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements. The syntax is: ```js -arr.splice(index[, deleteCount, elem1, ..., elemN]) +arr.splice(start[, deleteCount, elem1, ..., elemN]) ``` -It starts from the position `index`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements. +It modifies `arr` starting from the index `start`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements. This method is easy to grasp by examples. @@ -62,7 +62,7 @@ alert( arr ); // ["I", "JavaScript"] Easy, right? Starting from the index `1` it removed `1` element. -In the next example we remove 3 elements and replace them with the other two: +In the next example, we remove 3 elements and replace them with the other two: ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; @@ -84,7 +84,7 @@ let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- array of removed elements ``` -The `splice` method is also able to insert the elements without any removals. For that we need to set `deleteCount` to `0`: +The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: ```js run let arr = ["I", "study", "JavaScript"]; @@ -114,7 +114,7 @@ alert( arr ); // 1,2,3,4,5 ### slice -The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking `arr.splice`. +The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. The syntax is: @@ -124,7 +124,7 @@ arr.slice([start], [end]) It returns a new array copying to it all items from index `start` to `end` (not including `end`). Both `start` and `end` can be negative, in that case position from array end is assumed. -It's similar to a string method `str.slice`, but instead of substrings it makes subarrays. +It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. For instance: @@ -206,7 +206,7 @@ The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for ever The syntax: ```js arr.forEach(function(item, index, array) { - // ... do something with item + // ... do something with an item }); ``` @@ -234,12 +234,13 @@ Now let's cover methods that search in an array. ### indexOf/lastIndexOf and includes -The methods [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) and [arr.includes](mdn:js/Array/includes) have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters: +The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/includes) have the similar syntax and do essentially the same as their string counterparts, but operate on items instead of characters: - `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`. -- `arr.lastIndexOf(item, from)` -- same, but looks for from right to left. - `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found. +Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. + For instance: ```js run @@ -252,21 +253,33 @@ alert( arr.indexOf(null) ); // -1 alert( arr.includes(1) ); // true ``` -Note that the methods use `===` comparison. So, if we look for `false`, it finds exactly `false` and not the zero. +Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero. -If we want to check for inclusion, and don't want to know the exact index, then `arr.includes` is preferred. +If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred. -Also, a very minor difference of `includes` is that it correctly handles `NaN`, unlike `indexOf/lastIndexOf`: +The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left. + +```js run +let fruits = ['Apple', 'Orange', 'Apple'] + +alert( fruits.indexOf('Apple') ); // 0 (first Apple) +alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) +``` + +````smart header="The `includes` method handles `NaN` correctly" +A minor, but noteworthy feature of `includes` is that it correctly handles `NaN`, unlike `indexOf`: ```js run const arr = [NaN]; -alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN) +alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0) alert( arr.includes(NaN) );// true (correct) ``` +That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. +```` -### find and findIndex +### find and findIndex/findLastIndex -Imagine we have an array of objects. How do we find an object with the specific condition? +Imagine we have an array of objects. How do we find an object with a specific condition? Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy. @@ -284,7 +297,7 @@ The function is called for elements of the array, one after another: - `index` is its index. - `array` is the array itself. -If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned. +If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`: @@ -300,11 +313,30 @@ let user = users.find(item => item.id == 1); alert(user.name); // John ``` -In real life arrays of objects is a common thing, so the `find` method is very useful. +In real life, arrays of objects are a common thing, so the `find` method is very useful. Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. That's typical, other arguments of this function are rarely used. -The [arr.findIndex](mdn:js/Array/findIndex) method is essentially the same, but it returns the index where the element was found instead of the element itself and `-1` is returned when nothing is found. +The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. + +The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`. + +Here's an example: + +```js run +let users = [ + {id: 1, name: "John"}, + {id: 2, name: "Pete"}, + {id: 3, name: "Mary"}, + {id: 4, name: "John"} +]; + +// Find the index of the first John +alert(users.findIndex(user => user.name == 'John')); // 0 + +// Find the index of the last John +alert(users.findLastIndex(user => user.name == 'John')); // 3 +``` ### filter @@ -389,6 +421,7 @@ Literally, all elements are converted to strings for comparisons. For strings, l To use our own sorting order, we need to supply a function as the argument of `arr.sort()`. The function should compare two arbitrary values and return: + ```js function compare(a, b) { if (a > b) return 1; // if the first value is greater than the second @@ -417,15 +450,16 @@ alert(arr); // *!*1, 2, 15*/!* Now it works as intended. -Let's step aside and think what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. +Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. -The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. +The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. -By the way, if we ever want to know which elements are compared -- nothing prevents from alerting them: +By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); + return a - b; }); ``` @@ -492,7 +526,7 @@ Here's the situation from real life. We are writing a messaging app, and the per The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`. -In the example below, we split by a comma followed by space: +In the example below, we split by a comma followed by a space: ```js run let names = 'Bilbo, Gandalf, Nazgul'; @@ -559,9 +593,13 @@ Arguments: - `index` -- is its position. - `array` -- is the array. -As function is applied, the result of the previous function call is passed to the next one as the first argument. +As the function is applied, the result of the previous function call is passed to the next one as the first argument. +<<<<<<< HEAD So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end it becomes the result of `reduce`. +======= +So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Sounds complicated? @@ -630,8 +668,7 @@ arr.reduce((sum, current) => sum + current); So it's advised to always specify the initial value. -The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left. - +The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same but goes from right to left. ## Array.isArray @@ -641,7 +678,7 @@ So `typeof` does not help to distinguish a plain object from an array: ```js run alert(typeof {}); // object -alert(typeof []); // same +alert(typeof []); // object (same) ``` ...But arrays are used so often that there's a special method for that: [Array.isArray(value)](mdn:js/Array/isArray). It returns `true` if the `value` is an array, and `false` otherwise. @@ -656,7 +693,7 @@ alert(Array.isArray([])); // true Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`. -That parameter is not explained in the sections above, because it's rarely used. But for completeness we have to cover it. +That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it. Here's the full syntax of these methods: @@ -700,7 +737,7 @@ alert(soldiers[1].age); // 23 If in the example above we used `users.filter(army.canJoin)`, then `army.canJoin` would be called as a standalone function, with `this=undefined`, thus leading to an instant error. -A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The former is used more often, as it's a bit easier to understand for most people. +A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The latter is used more often, as it's a bit easier to understand for most people. ## Summary @@ -711,12 +748,12 @@ A cheat sheet of array methods: - `pop()` -- extracts an item from the end, - `shift()` -- extracts an item from the beginning, - `unshift(...items)` -- adds items to the beginning. - - `splice(pos, deleteCount, ...items)` -- at index `pos` delete `deleteCount` elements and insert `items`. - - `slice(start, end)` -- creates a new array, copies elements from position `start` till `end` (not inclusive) into it. + - `splice(pos, deleteCount, ...items)` -- at index `pos` deletes `deleteCount` elements and inserts `items`. + - `slice(start, end)` -- creates a new array, copies elements from index `start` till `end` (not inclusive) into it. - `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken. - To search among elements: - - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, return the index or `-1` if not found. + - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found. - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. - `findIndex` is like `find`, but returns the index instead of a value. @@ -729,26 +766,40 @@ A cheat sheet of array methods: - `sort(func)` -- sorts the array in-place, then returns it. - `reverse()` -- reverses the array in-place, then returns it. - `split/join` -- convert a string to array and back. - - `reduce(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. + - `reduce/reduceRight(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. - Additionally: - - `Array.isArray(arr)` checks `arr` for being an array. + - `Array.isArray(value)` checks `value` for being an array, if so returns `true`, otherwise `false`. Please note that methods `sort`, `reverse` and `splice` modify the array itself. These methods are the most used ones, they cover 99% of use cases. But there are few others: -- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) checks the array. +- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) check the array. The function `fn` is called on each element of the array similar to `map`. If any/all results are `true`, returns `true`, otherwise `false`. + These methods behave sort of like `||` and `&&` operators: if `fn` returns a truthy value, `arr.some()` immediately returns `true` and stops iterating over the rest of items; if `fn` returns a falsy value, `arr.every()` immediately returns `false` and stops iterating over the rest of items as well. + + We can use `every` to compare arrays: + + ```js run + function arraysEqual(arr1, arr2) { + return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]); + } + + alert( arraysEqual([1, 2], [1, 2])); // true + ``` + - [arr.fill(value, start, end)](mdn:js/Array/fill) -- fills the array with repeating `value` from index `start` to `end`. - [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- copies its elements from position `start` till position `end` into *itself*, at position `target` (overwrites existing). +- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) create a new flat array from a multidimensional array. + For the full list, see the [manual](mdn:js/Array). -From the first sight it may seem that there are so many methods, quite difficult to remember. But actually that's much easier. +At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. Look through the cheat sheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 8a38516e1..8654a32bc 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -1,7 +1,11 @@ # Iterables +<<<<<<< HEAD *Iterable* objects is a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop. +======= +*Iterable* objects are a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable. @@ -26,12 +30,12 @@ let range = { // for(let num of range) ... num=1,2,3,4,5 ``` -To make the `range` iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). +To make the `range` object iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that). 1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`. 2. Onward, `for..of` works *only with that returned object*. 3. When `for..of` wants the next value, it calls `next()` on that object. -4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` is the next value. +4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the loop is finished, otherwise `value` is the next value. Here's the full implementation for `range` with remarks: @@ -45,10 +49,10 @@ let range = { range[Symbol.iterator] = function() { // ...it returns the iterator object: - // 2. Onward, for..of works only with this iterator, asking it for next values + // 2. Onward, for..of works only with the iterator object below, asking it for next values return { current: this.from, - last: this.to, + last: this.to, // 3. next() is called on each iteration by the for..of loop next() { @@ -140,7 +144,7 @@ for (let char of str) { ## Calling an iterator explicitly -For deeper understanding let's see how to use an iterator explicitly. +For deeper understanding, let's see how to use an iterator explicitly. We'll iterate over a string in exactly the same way as `for..of`, but with direct calls. This code creates a string iterator and gets values from it "manually": @@ -165,16 +169,16 @@ That is rarely needed, but gives us more control over the process than `for..of` ## Iterables and array-likes [#array-like] -There are two official terms that look similar, but are very different. Please make sure you understand them well to avoid the confusion. +Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion. - *Iterables* are objects that implement the `Symbol.iterator` method, as described above. - *Array-likes* are objects that have indexes and `length`, so they look like arrays. -When we use JavaScript for practical tasks in browser or other environments, we may meet objects that are iterables or array-likes, or both. +When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both. For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`). -But an iterable may be not array-like. And vice versa an array-like may be not iterable. +But an iterable may not be array-like. And vice versa an array-like may not be iterable. For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`. @@ -218,7 +222,7 @@ alert(arr.pop()); // World (method works) The same happens for an iterable: -```js +```js run // assuming that range is taken from the example above let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (array toString conversion works) @@ -233,7 +237,7 @@ The optional second argument `mapFn` can be a function that will be applied to e For instance: -```js +```js run // assuming that range is taken from the example above // square each number @@ -270,7 +274,7 @@ for (let char of str) { alert(chars); ``` -...But it is shorter. +...But it is shorter. We can even build surrogate-aware `slice` on it: @@ -293,7 +297,11 @@ alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs) Objects that can be used in `for..of` are called *iterable*. - Technically, iterables must implement the method named `Symbol.iterator`. +<<<<<<< HEAD - The result of `obj[Symbol.iterator]` is called an *iterator*. It handles the further iteration process. +======= + - The result of `obj[Symbol.iterator]()` is called an *iterator*. It handles further iteration process. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value. - The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly. - Built-in iterables like strings or arrays, also implement `Symbol.iterator`. @@ -304,4 +312,4 @@ Objects that have indexed properties and `length` are called *array-like*. Such If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract. -`Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. +`Array.from(obj[, mapFn, thisArg])` makes a real `Array` from an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item. diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md index 25c74bfc2..81507647f 100644 --- a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md +++ b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md @@ -4,7 +4,7 @@ importance: 5 # Iterable keys -We'd like to get an array of `map.keys()` in a variable and then do apply array-specific methods to it, e.g. `.push`. +We'd like to get an array of `map.keys()` in a variable and then apply array-specific methods to it, e.g. `.push`. But that doesn't work: diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index aaed5b454..2e9d6735a 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -1,26 +1,26 @@ # Map and Set -Now we've learned about the following complex data structures: +Till now, we've learned about the following complex data structures: -- Objects for storing keyed collections. -- Arrays for storing ordered collections. +- Objects are used for storing keyed collections. +- Arrays are used for storing ordered collections. But that's not enough for real life. That's why `Map` and `Set` also exist. ## Map -[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. Methods and properties are: -- `new Map()` -- creates the map. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element (the key/value pair) by the key. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. For instance: @@ -42,7 +42,11 @@ alert( map.size ); // 3 As we can see, unlike objects, keys are not converted to strings. Any type of key is possible. ```smart header="`map[key]` isn't the right way to use a `Map`" +<<<<<<< HEAD Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (no object keys and so on). +======= +Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (only string/symbol keys and so on). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 So we should use `map` methods: `set`, `get` and so on. ``` @@ -63,24 +67,26 @@ visitsCountMap.set(john, 123); alert( visitsCountMap.get(john) ); // 123 ``` -Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but not for object keys. +Using objects as keys is one of the most notable and important `Map` features. The same does not count for `Object`. String as a key in `Object` is fine, but we can't use another `Object` as a key in `Object`. Let's try: ```js run let john = { name: "John" }; +let ben = { name: "Ben" }; let visitsCountObj = {}; // try to use an object -visitsCountObj[john] = 123; // try to use john object as the key +visitsCountObj[ben] = 234; // try to use ben object as the key +visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced *!* // That's what got written! -alert( visitsCountObj["[object Object]"] ); // 123 +alert( visitsCountObj["[object Object]"] ); // 123 */!* ``` -As `visitsCountObj` is an object, it converts all keys, such as `john` to strings, so we've got the string key `"[object Object]"`. Definitely not what we want. +As `visitsCountObj` is an object, it converts all `Object` keys, such as `john` and `ben` above, to same string `"[object Object]"`. Definitely not what we want. ```smart header="How `Map` compares keys" To test keys for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well. @@ -98,14 +104,13 @@ map.set('1', 'str1') ``` ```` - ## Iteration over Map For looping over a `map`, there are 3 methods: -- `map.keys()` -- returns an iterable for keys, -- `map.values()` -- returns an iterable for values, -- `map.entries()` -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- returns an iterable for keys, +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- returns an iterable for values, +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. For instance: @@ -160,7 +165,7 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format. +If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) that returns an array of key/value pairs for an object exactly in that format. So we can create a map from an object like this: @@ -198,7 +203,7 @@ let prices = Object.fromEntries([ alert(prices.orange); // 2 ``` -We can use `Object.fromEntries` to get an plain object from `Map`. +We can use `Object.fromEntries` to get a plain object from `Map`. E.g. we store the data in a `Map`, but we need to pass it to a 3rd-party code that expects a plain object. @@ -220,7 +225,7 @@ let obj = Object.fromEntries(map.entries()); // make a plain object (*) alert(obj.orange); // 2 ``` -A call to `map.entries()` returns an array of key/value pairs, exactly in the right format for `Object.fromEntries`. +A call to `map.entries()` returns an iterable of key/value pairs, exactly in the right format for `Object.fromEntries`. We could also make line `(*)` shorter: ```js @@ -231,16 +236,16 @@ That's the same, because `Object.fromEntries` expects an iterable object as the ## Set -A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once. +A [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) is a special type collection - "set of values" (without keys), where each value may occur only once. Its main methods are: -- `new Set(iterable)` -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. -- `set.add(value)` -- adds a value, returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value, returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. The main feature is that repeated calls of `set.add(value)` with the same value don't do anything. That's the reason why each value appears in a `Set` only once. @@ -270,7 +275,7 @@ for (let user of set) { } ``` -The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. +The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. ## Iteration over Set @@ -289,42 +294,42 @@ set.forEach((value, valueAgain, set) => { Note the funny thing. The callback function passed in `forEach` has 3 arguments: a `value`, then *the same value* `valueAgain`, and then the target object. Indeed, the same value appears in the arguments twice. -That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But this may help to replace `Map` with `Set` in certain cases with ease, and vice versa. The same methods `Map` has for iterators are also supported: -- `set.keys()` -- returns an iterable object for values, -- `set.values()` -- same as `set.keys()`, for compatibility with `Map`, -- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- returns an iterable object for values, +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- same as `set.keys()`, for compatibility with `Map`, +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. ## Summary -`Map` -- is a collection of keyed values. +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- is a collection of keyed values. Methods and properties: -- `new Map([iterable])` -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key, returns the map itself. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. The differences from a regular `Object`: - Any keys, objects can be keys. - Additional convenient methods, the `size` property. -`Set` -- is a collection of unique values. +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- is a collection of unique values. Methods and properties: -- `new Set([iterable])` -- creates the set, with optional `iterable` (e.g. array) of values for initialization. -- `set.add(value)` -- adds a value (does nothing if `value` exists), returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, with optional `iterable` (e.g. array) of values for initialization. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value (does nothing if `value` exists), returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. Iteration over `Map` and `Set` is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number. diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md index dcf41d2c5..0f0ec3dd3 100644 --- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md @@ -25,7 +25,11 @@ messages.shift(); // 現在 readMessages 只有一個元素(技術上來說,記憶體可能會在稍後才被清理) ``` +<<<<<<< HEAD `WeakSet` 允許儲存訊息的集合,且能簡單地檢查一個訊息是否存在於集合內。 +======= +The `WeakSet` allows to store a set of messages and easily check for the existence of a message in it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 它會自動清理自己,但代價是我們不能夠迭代它,無法直接取得 "所有已讀訊息"。但我們可以透過迭代所有訊息,並過濾掉那些存在集合中的訊息來達到同樣目的。 diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index bae9bbb83..a665548f9 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,9 +1,19 @@ +<<<<<<< HEAD # WeakMap 和 WeakSet 如同我們從章節 得知的,JavaScript 引擎會將可達的(且有可能會被使用到的)值儲存在記憶體中。 舉例來說: +======= + +# WeakMap and WeakSet + +As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. + +For instance: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let john = { name: "John" }; @@ -31,8 +41,14 @@ let array = [ john ]; john = null; // 覆寫其參考 *!* +<<<<<<< HEAD // john 被儲存於陣列內,所以它不會被垃圾回收掉。 // 我們可以透過 array[0] 來存取它。 +======= +// the object previously referenced by john is stored inside the array +// therefore it won't be garbage-collected +// we can get it as array[0] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` @@ -54,13 +70,21 @@ john = null; // 覆寫其參考 */!* ``` +<<<<<<< HEAD `WeakMap` 在此方面有著根本上的不同。它並不會防止鍵值物件被垃圾回收。 +======= +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 讓我們從範例來看看這代表什麼意思。 ## WeakMap +<<<<<<< HEAD 與 `Map` 的第一個差異是,`WeakMap` 一定要是物件,不能是原生類型值: +======= +The first difference between [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is that keys must be objects, not primitive values: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let weakMap = new WeakMap(); @@ -94,16 +118,22 @@ john = null; // 覆寫參考 `WeakMap` 只有下面的方法: -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` +- [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) +- [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) +- [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) +- [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) 為什麼有這樣的限制?這是為了技術上的原因。如果一個物件喪失了其他所有的參考(如上述程式碼範例中的 `john`),那它會被自動垃圾回收掉。但技術上來說,並沒有明確指定 *何時要執行清理*。 +<<<<<<< HEAD 由 JavaScript 引擎決定。它可能選擇立即執行記憶體清掃,或是等待晚點更多的刪除發生後再執行清理。所以,技術上來說,`WeakMap` 目前的元素數量是未知的。引擎可能會清理也可能不會,或是只做一部分。出於此因,不支援能夠存取所有鍵/值的方法。 好,那麼在哪種地方我們需要這樣的資料結構呢? +======= +The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically, the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported. + +Now, where do we need such a data structure? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 使用案例: 附加的資料 @@ -141,13 +171,21 @@ function countUser(user) { // 📁 main.js let john = { name: "John" }; +<<<<<<< HEAD countUser(john); // 它的訪問次數 +======= +countUser(john); // count his visits +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // 晚點 john 離開了我們 john = null; ``` +<<<<<<< HEAD 現在 `john` 物件應該要被垃圾回收,但卻還是作為 `visitsCountMap` 中的一個鍵存在於記憶體中。 +======= +Now, `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 當我們移除使用者時,我們需要清理 `visitsCountMap`,否則記憶體會無窮擴大。在複雜的架構中,這樣的清潔可能會是一個繁瑣乏味的任務。 @@ -164,13 +202,23 @@ function countUser(user) { } ``` +<<<<<<< HEAD 現在我們不用清理 `visitsCountMap` 了。當 `john` 物件變成除了作為 `WeakMap` 的鍵值以外,其餘皆不可達的情況時,它就會連同那些從 `WeakMap` 的鍵得來的資訊,一起從記憶體中被移除。 +======= +Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable, by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 使用案例: 快取(caching) +<<<<<<< HEAD 另一個常見的範例是快取:當一個函數的結果應該要被記憶住("快取"),這樣之後呼叫相同物件時可以重複使用。 我們可以用 `Map` 來存結果,像這樣: +======= +Another common example is caching. We can store ("cache") results from a function, so that future calls on the same object can reuse it. + +To achieve that, we can use `Map` (not optimal scenario): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // 📁 cache.js @@ -182,6 +230,7 @@ function process(obj) { let result; // = obj 的計算結果 cache.set(obj, result); + return result; } return cache.get(obj); @@ -207,7 +256,11 @@ alert(cache.size); // 1(哎呦!該物件還是在快取中,佔據記憶體 針對同個物件多次呼叫 `process(obj)`,只有第一次會進行計算,之後就只從 `cache` 中取出結果。這樣做的缺點是,當物件不再被需要時,我們需要清除 `cache`。 +<<<<<<< HEAD 如果我們用 `WeakMap` 取代 `Map`,那這問題就不復存在了:快取結果會在物件被垃圾回收後,自動從記憶體中被移除。 +======= +If we replace `Map` with `WeakMap`, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // 📁 cache.js @@ -221,6 +274,7 @@ function process(obj) { let result; // = obj 的計算結果 cache.set(obj, result); + return result; } return cache.get(obj); @@ -242,6 +296,7 @@ obj = null; ## WeakSet +<<<<<<< HEAD `WeakSet` 有類似的行為: - 它類似於 `Set`,但我們只能將物件加入 `WeakSet`(原生值不行)。 @@ -249,6 +304,15 @@ obj = null; - 像是 `Set`,它支援 `add`、`has` 和 `delete`,但不支援 `size`、`keys()` 且沒有迭代。 身為 "weak",它也可作為附加的儲存空間。但不是給隨意的資料使用,而是針對 "是/否" 這類的事實陳述。`WeakSet` 中的成員關係可能代表物件的某些資訊。 +======= +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) behaves similarly: + +- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). +- An object exists in the set while it is reachable from somewhere else. +- Like `Set`, it supports [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) and [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), but not `size`, `keys()` and no iterations. + +Being "weak", it also serves as additional storage. But not for arbitrary data, rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 舉例來說,我們可以將使用者加入 `WeakSet` 來追蹤誰曾拜訪過我們的網站: @@ -276,10 +340,15 @@ john = null; // visitedSet 將會被自動清理。 ``` +<<<<<<< HEAD `WeakMap` 與 `WeakSet` 最值得注意的限制是缺乏迭代功能,以及無法一次取得目前所有的內容。這可能很不方便,但並不影響 `WeakMap/WeakSet` 執行他們的主要工作 -- 為在另一個地方被儲存/管理的物件提供一個 "附加" 的儲存空間來儲存其資料。 +======= +The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and the inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## 總結 +<<<<<<< HEAD `WeakMap` 是一個類似 `Map` 的集合,只允許用物件當作鍵,且當其關聯的值不再能夠被存取時,會跟著一同被移除。 `WeakSet` 是一個類似 `Set` 的集合,只能儲存物件,且當該物件不再能夠被存取時,會跟著一同被移除。 @@ -287,3 +356,14 @@ john = null; 它們兩個都不支援能夠存取所有鍵或是計數值的方法或屬性。只允許個別的操作。 `WeakMap` 和 `WeakSet` 被作為附加於 "主要" 物件儲存空間的 "次要" 資料結構。一但物件從主要儲存空間中被移除,如果該物件只被當作 `WeakMap` 的鍵,或是只存在於 `WeakSet` 中,那它將會自動被清除。 +======= +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. + +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. + +Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector. + +That comes at the cost of not having support for `clear`, `size`, `keys`, `values`... + +`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "primary" object storage. Once the object is removed from the primary storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index 4af192515..bef678f53 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -74,10 +74,10 @@ Usually that's convenient. But if we want symbolic keys too, then there's a sepa Objects lack many methods that exist for arrays, e.g. `map`, `filter` and others. -If we'd like to apply them, then we can use `Object.entries` followed `Object.fromEntries`: +If we'd like to apply them, then we can use `Object.entries` followed by `Object.fromEntries`: 1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`. -2. Use array methods on that array, e.g. `map`. +2. Use array methods on that array, e.g. `map`, to transform these key/value pairs. 3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object. For example, we have an object with prices, and would like to double them: @@ -91,12 +91,13 @@ let prices = { *!* let doublePrices = Object.fromEntries( - // convert to array, map, and then fromEntries gives back the object - Object.entries(prices).map(([key, value]) => [key, value * 2]) + // convert prices to array, map each key/value pair into another pair + // and then fromEntries gives back the object + Object.entries(prices).map(entry => [entry[0], entry[1] * 2]) ); */!* alert(doublePrices.meat); // 8 -``` +``` -It may look difficult from the first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. +It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. diff --git a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js index f4bd5c761..6538af42b 100644 --- a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js +++ b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js @@ -1,16 +1,14 @@ function topSalary(salaries) { - let max = 0; + let maxSalary = 0; let maxName = null; for(const [name, salary] of Object.entries(salaries)) { - if (max < salary) { - max = salary; + if (maxSalary < salary) { + maxSalary = salary; maxName = name; } } return maxName; -} - - +} \ No newline at end of file diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index 907c28cab..0c52741d1 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -2,19 +2,22 @@ The two most used data structures in JavaScript are `Object` and `Array`. -Objects allow us to create a single entity that stores data items by key, and arrays allow us to gather data items into an ordered collection. +- Objects allow us to create a single entity that stores data items by key. +- Arrays allow us to gather data items into an ordered list. -But when we pass those to a function, it may need not an object/array as a whole, but rather individual pieces. +However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties. -*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. Destructuring also works great with complex functions that have a lot of parameters, default values, and so on. +*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. + +Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. ## Array destructuring -An example of how the array is destructured into variables: +Here's an example of how an array is destructured into variables: ```js -// we have an array with the name and surname -let arr = ["Ilya", "Kantor"] +// we have an array with a name and surname +let arr = ["John", "Smith"] *!* // destructuring assignment @@ -23,20 +26,24 @@ let arr = ["Ilya", "Kantor"] let [firstName, surname] = arr; */!* -alert(firstName); // Ilya -alert(surname); // Kantor +alert(firstName); // John +alert(surname); // Smith ``` Now we can work with variables instead of array members. It looks great when combined with `split` or other array-returning methods: -```js -let [firstName, surname] = "Ilya Kantor".split(' '); +```js run +let [firstName, surname] = "John Smith".split(' '); +alert(firstName); // John +alert(surname); // Smith ``` +As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better. + ````smart header="\"Destructuring\" does not mean \"destructive\"." -It's called "destructuring assignment," because it "destructurizes" by copying items into variables. But the array itself is not modified. +It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified. It's just a shorter way to write: ```js @@ -58,7 +65,7 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic alert( title ); // Consul ``` -In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items is also skipped (as there are no variables for them). +In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them). ```` ````smart header="Works with any iterable on the right-side" @@ -69,29 +76,28 @@ In the code above, the second element of the array is skipped, the third one is let [a, b, c] = "abc"; // ["a", "b", "c"] let [one, two, three] = new Set([1, 2, 3]); ``` - +That works, because internally a destructuring assignment works by iterating over the right value. It's a kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values. ```` ````smart header="Assign to anything at the left-side" - -We can use any "assignables" at the left side. +We can use any "assignables" on the left side. For instance, an object property: ```js run let user = {}; -[user.name, user.surname] = "Ilya Kantor".split(' '); +[user.name, user.surname] = "John Smith".split(' '); -alert(user.name); // Ilya +alert(user.name); // John +alert(user.surname); // Smith ``` ```` ````smart header="Looping with .entries()" +In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method. -In the previous chapter we saw the [Object.entries(obj)](mdn:js/Object/entries) method. - -We can use it with destructuring to loop over keys-and-values of an object: +We can use it with destructuring to loop over the keys-and-values of an object: ```js run let user = { @@ -99,7 +105,7 @@ let user = { age: 30 }; -// loop over keys-and-values +// loop over the keys-and-values *!* for (let [key, value] of Object.entries(user)) { */!* @@ -107,7 +113,7 @@ for (let [key, value] of Object.entries(user)) { } ``` -...And the same for a map: +The similar code for a `Map` is simpler, as it's iterable: ```js run let user = new Map(); @@ -115,35 +121,73 @@ user.set("name", "John"); user.set("age", "30"); *!* +// Map iterates as [key, value] pairs, very convenient for destructuring for (let [key, value] of user) { */!* alert(`${key}:${value}`); // name:John, then age:30 } ``` ```` + +````smart header="Swap variables trick" +There's a well-known trick for swapping values of two variables using a destructuring assignment: + +```js run +let guest = "Jane"; +let admin = "Pete"; + +// Let's swap the values: make guest=Pete, admin=Jane +*!* +[guest, admin] = [admin, guest]; +*/!* + +alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!) +``` + +Here we create a temporary array of two variables and immediately destructure it in swapped order. + +We can swap more than two variables this way. +```` + ### The rest '...' -If we want not just to get first values, but also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`: +Usually, if the array is longer than the list at the left, the "extra" items are omitted. + +For example, here only two items are taken, and the rest is just ignored: ```js run -let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; +let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; alert(name1); // Julius alert(name2); // Caesar +// Further items aren't assigned anywhere +``` + +If we'd like also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`: + +```js run +let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* -// Note that type of `rest` is Array. +// rest is an array of items, starting from the 3rd one alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 */!* ``` -The value of `rest` is the array of the remaining array elements. We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment. +The value of `rest` is the array of the remaining array elements. + +We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment. + +```js run +let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; +// now titles = ["Consul", "of the Roman Republic"] +``` ### Default values -If there are fewer values in the array than variables in the assignment, there will be no error. Absent values are considered undefined: +If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined: ```js run *!* @@ -168,7 +212,7 @@ alert(surname); // Anonymous (default used) Default values can be more complex expressions or even function calls. They are evaluated only if the value is not provided. -For instance, here we use the `prompt` function for two defaults. But it will run only for the missing one: +For instance, here we use the `prompt` function for two defaults: ```js run // runs only prompt for surname @@ -178,7 +222,7 @@ alert(name); // Julius (from array) alert(surname); // whatever prompt gets ``` - +Please note: the `prompt` will run only for the missing value (`surname`). ## Object destructuring @@ -190,7 +234,7 @@ The basic syntax is: let {var1, var2} = {var1:…, var2:…} ``` -We have an existing object at the right side, that we want to split into variables. The left side contains a "pattern" for corresponding properties. In the simple case, that's a list of variable names in `{...}`. +We should have an existing object on the right side, that we want to split into variables. The left side contains an object-like "pattern" for corresponding properties. In the simplest case, that's a list of variable names in `{...}`. For instance: @@ -210,7 +254,9 @@ alert(width); // 100 alert(height); // 200 ``` -Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. The order does not matter. This works too: +Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. + +The order does not matter. This works too: ```js // changed the order in let {...} @@ -219,7 +265,7 @@ let {height, width, title} = { title: "Menu", height: 200, width: 100 } The pattern on the left side may be more complex and specify the mapping between properties and variables. -If we want to assign a property to a variable with another name, for instance, `options.width` to go into the variable named `w`, then we can set it using a colon: +If we want to assign a property to a variable with another name, for instance, make `options.width` go into the variable named `w`, then we can set the variable name using a colon: ```js run let options = { @@ -372,9 +418,9 @@ alert( title ); // Menu ## Nested destructuring -If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. +If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. -In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure to extract values from them: +In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern on the left side of the assignment has the same structure to extract values from them: ```js run let options = { @@ -383,7 +429,7 @@ let options = { height: 200 }, items: ["Cake", "Donut"], - extra: true + extra: true }; // destructuring assignment split in multiple lines for clarity @@ -403,7 +449,7 @@ alert(item1); // Cake alert(item2); // Donut ``` -All properties of `options` object except `extra` that is absent in the left part, are assigned to corresponding variables: +All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables: ![](destructuring-complex.svg) @@ -413,9 +459,9 @@ Note that there are no variables for `size` and `items`, as we take their conten ## Smart function parameters -There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on. +There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, an item list and so on. -Here's a bad way to write such function: +Here's a bad way to write such a function: ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -423,7 +469,7 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` -In real-life, the problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. +In real-life, the problem is how to remember the order of arguments. Usually, IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. Like this? @@ -488,7 +534,7 @@ function({ }) ``` -Then, for an object of parameters, there will be a variable `varName` for property `incomingProperty`, with `defaultValue` by default. +Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object: @@ -515,7 +561,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw - Destructuring assignment allows for instantly mapping an object or array onto many variables. - The full object syntax: ```js - let {prop : varName = default, ...rest} = object + let {prop : varName = defaultValue, ...rest} = object ``` This means that property `prop` should go into the variable `varName` and, if no such property exists, then the `default` value should be used. @@ -525,9 +571,9 @@ In the code above, the whole arguments object is `{}` by default, so there's alw - The full array syntax: ```js - let [item1 = default, item2, ...rest] = array + let [item1 = defaultValue, item2, ...rest] = array ``` - The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`. + The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`. - It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one. diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md index 9bb1d749c..18286c336 100644 --- a/1-js/05-data-types/11-date/1-new-date/solution.md +++ b/1-js/05-data-types/11-date/1-new-date/solution.md @@ -2,7 +2,17 @@ The `new Date` constructor uses the local time zone. So the only important thing So February has number 1. +Here's an example with numbers as date components: + +```js run +//new Date(year, month, date, hour, minute, second, millisecond) +let d1 = new Date(2012, 1, 20, 3, 12); +alert( d1 ); +``` +We could also create a date from a string, like this: + ```js run -let d = new Date(2012, 1, 20, 3, 12); -alert( d ); +//new Date(datastring) +let d2 = new Date("2012-02-20T03:12"); +alert( d2 ); ``` diff --git a/1-js/05-data-types/11-date/6-get-seconds-today/solution.md b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md index a483afe93..8f8e52b68 100644 --- a/1-js/05-data-types/11-date/6-get-seconds-today/solution.md +++ b/1-js/05-data-types/11-date/6-get-seconds-today/solution.md @@ -23,4 +23,6 @@ function getSecondsToday() { let d = new Date(); return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); } + +alert( getSecondsToday() ); ``` diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index a2de63ae4..6958a3a97 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -57,7 +57,7 @@ To create a new `Date` object call `new Date()` with one of the following argume `new Date(year, month, date, hours, minutes, seconds, ms)` : Create the date with the given components in the local time zone. Only the first two arguments are obligatory. - - The `year` must have 4 digits: `2013` is okay, `98` is not. + - The `year` should have 4 digits. For compatibility, 2 digits are also accepted and considered `19xx`, e.g. `98` is the same as `1998` here, but always using 4 digits is strongly encouraged. - The `month` count starts with `0` (Jan), up to `11` (Dec). - The `date` parameter is actually the day of month, if absent then `1` is assumed. - If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`. @@ -69,7 +69,7 @@ To create a new `Date` object call `new Date()` with one of the following argume new Date(2011, 0, 1); // the same, hours etc are 0 by default ``` - The minimal precision is 1 ms (1/1000 sec): + The maximal precision is 1 ms (1/1000 sec): ```js run let date = new Date(2011, 0, 1, 2, 3, 4, 567); @@ -348,7 +348,7 @@ let time1 = 0; let time2 = 0; *!* -// run bench(upperSlice) and bench(upperLoop) each 10 times alternating +// run bench(diffSubtract) and bench(diffGetTime) each 10 times alternating for (let i = 0; i < 10; i++) { time1 += bench(diffSubtract); time2 += bench(diffGetTime); @@ -376,7 +376,7 @@ for (let i = 0; i < 10; i++) { ```warn header="Be careful doing microbenchmarking" Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. -The great pack of articles about V8 can be found at . +The great pack of articles about V8 can be found at . ``` ## Date.parse from a string @@ -388,7 +388,7 @@ The string format should be: `YYYY-MM-DDTHH:mm:ss.sssZ`, where: - `YYYY-MM-DD` -- is the date: year-month-day. - The character `"T"` is used as the delimiter. - `HH:mm:ss.sss` -- is the time: hours, minutes, seconds and milliseconds. -- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` that would mean UTC+0. +- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` would mean UTC+0. Shorter variants are also possible, like `YYYY-MM-DD` or `YYYY-MM` or even `YYYY`. @@ -407,7 +407,7 @@ We can instantly create a `new Date` object from the timestamp: ```js run let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') ); -alert(date); +alert(date); ``` ## Summary @@ -427,7 +427,7 @@ Sometimes we need more precise time measurements. JavaScript itself does not hav alert(`Loading started ${performance.now()}ms ago`); // Something like: "Loading started 34731.26000000001ms ago" // .26 is microseconds (260 microseconds) -// more than 3 digits after the decimal point are precision errors, but only the first 3 are correct +// more than 3 digits after the decimal point are precision errors, only the first 3 are correct ``` Node.js has `microtime` module and other ways. Technically, almost any device and environment allows to get more precision, it's just not in `Date`. diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index a5f2974af..133ffb353 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -27,7 +27,7 @@ Luckily, there's no need to write the code to handle all this. The task has been ## JSON.stringify -The [JSON](http://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](http://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever. +The [JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](https://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever. JavaScript provides methods: @@ -41,7 +41,7 @@ let student = { age: 30, isAdmin: false, courses: ['html', 'css', 'js'], - wife: null + spouse: null }; *!* @@ -58,7 +58,7 @@ alert(json); "age": 30, "isAdmin": false, "courses": ["html", "css", "js"], - "wife": null + "spouse": null } */ */!* @@ -105,7 +105,7 @@ JSON is data-only language-independent specification, so some JavaScript-specifi Namely: - Function properties (methods). -- Symbolic properties. +- Symbolic keys and values. - Properties that store `undefined`. ```js run @@ -276,6 +276,7 @@ name: John name: Alice place: [object Object] number: 23 +occupiedBy: [object Object] */ ``` @@ -328,6 +329,8 @@ alert(JSON.stringify(user, null, 2)); */ ``` +The third argument can also be a string. In this case, the string is used for indentation instead of a number of spaces. + The `space` parameter is used solely for logging and nice-output purposes. ## Custom "toJSON" @@ -402,7 +405,7 @@ To decode a JSON-string, we need another method named [JSON.parse](mdn:js/JSON/p The syntax: ```js -let value = JSON.parse(str, [reviver]); +let value = JSON.parse(str[, reviver]); ``` str @@ -448,7 +451,7 @@ let json = `{ Besides, JSON does not support comments. Adding a comment to JSON makes it invalid. -There's another format named [JSON5](http://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. +There's another format named [JSON5](https://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. The regular JSON is that strict not because its developers are lazy, but to allow easy, reliable and very fast implementations of the parsing algorithm. diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index 3a281ef3f..11667f940 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -37,4 +37,4 @@ P.S. Naturally, the formula is the fastest solution. It uses only 3 operations f The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower. -P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory, so counting `sumTo(100000)` becomes possible. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function, with no other calculations performed, then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. diff --git a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md index 59040a2b7..09e511db5 100644 --- a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md +++ b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md @@ -1,4 +1,4 @@ -By definition, a factorial is `n!` can be written as `n * (n-1)!`. +By definition, a factorial `n!` can be written as `n * (n-1)!`. In other words, the result of `factorial(n)` can be calculated as `n` multiplied by the result of `factorial(n-1)`. And the call for `n-1` can recursively descend lower, and lower, till `1`. diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md index 4357ff208..0eb76ea1c 100644 --- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md +++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md @@ -33,7 +33,7 @@ printReverseList(list); # Using a loop -The loop variant is also a little bit more complicated then the direct output. +The loop variant is also a little bit more complicated than the direct output. There is no way to get the last value in our `list`. We also can't "go back". diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 320de62f0..5ae894474 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -61,7 +61,7 @@ When `pow(x, n)` is called, the execution splits into two branches: if n==1 = x / pow(x, n) = - \ + \ else = x * pow(x, n - 1) ``` @@ -132,7 +132,7 @@ We can sketch it as: -That's when the function starts to execute. The condition `n == 1` is false, so the flow continues into the second branch of `if`: +That's when the function starts to execute. The condition `n == 1` is falsy, so the flow continues into the second branch of `if`: ```js run function pow(x, n) { @@ -188,7 +188,7 @@ The new current execution context is on top (and bold), and previous remembered When we finish the subcall -- it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped. ```smart -Here in the picture we use the word "line", as our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`. +Here in the picture we use the word "line", as in our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`. So it would be more precise to say that the execution resumes "immediately after the subcall". ``` @@ -285,7 +285,7 @@ The iterative `pow` uses a single context changing `i` and `result` in the proce **Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.** -...But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. +...But sometimes the rewrite is non-trivial, especially when a function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used. @@ -535,7 +535,7 @@ Terms: list = { value, next -> list } ``` - Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they branch and every branch can have other branches. + Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they have branches and every branch can have other branches. Recursive functions can be used to walk them as we've seen in the `sumSalary` example. diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md index 7089a6750..feb8a1614 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -23,7 +23,7 @@ function sum(a, b) { alert( sum(1, 2, 3, 4, 5) ); ``` -There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted. +There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted, so the result in the code above is `3`. The rest of the parameters can be included in the function definition by using three dots `...` followed by the name of the array that will contain them. The dots literally mean "gather the remaining parameters into an array". @@ -225,6 +225,7 @@ But there's a subtle difference between `Array.from(obj)` and `[...obj]`: So, for the task of turning something into an array, `Array.from` tends to be more universal. +<<<<<<< HEAD ## Get a new copy of an object/array Remember when we talked about `Object.assign()` [in the past](https://javascript.info/object#cloning-and-merging-object-assign)? @@ -235,6 +236,21 @@ It is possible to do the same thing with the spread operator! let arr = [1, 2, 3]; let arrCopy = [...arr]; // spread the array into a list of parameters // then put the result into a new array +======= +## Copy an array/object + +Remember when we talked about `Object.assign()` [in the past](info:object-copy#cloning-and-merging-object-assign)? + +It is possible to do the same thing with the spread syntax. + +```js run +let arr = [1, 2, 3]; + +*!* +let arrCopy = [...arr]; // spread the array into a list of parameters + // then put the result into a new array +*/!* +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // do the arrays have the same contents? alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true @@ -252,8 +268,16 @@ Note that it is possible to do the same thing to make a copy of an object: ```js run let obj = { a: 1, b: 2, c: 3 }; +<<<<<<< HEAD let objCopy = { ...obj }; // spread the object into a list of parameters // then return the result in a new object +======= + +*!* +let objCopy = { ...obj }; // spread the object into a list of parameters + // then return the result in a new object +*/!* +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // do the objects have the same contents? alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true @@ -267,7 +291,11 @@ alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4} alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3} ``` +<<<<<<< HEAD This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj);` or for an array `let arrCopy = Object.assign([], arr);` so we prefer to use it whenever we can. +======= +This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj)` or for an array `let arrCopy = Object.assign([], arr)` so we prefer to use it whenever we can. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Summary diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg new file mode 100644 index 000000000..f8c7bd6ac --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg @@ -0,0 +1 @@ +outer<empty>makeArmy() LexicalEnvironmentwhile iteration LexicalEnvironment<empty><empty><empty>i: 10 \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg new file mode 100644 index 000000000..7611d0ef8 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg @@ -0,0 +1 @@ +outermakeArmy() LexicalEnvironmentfor iteration LexicalEnvironmenti: 0i: 1i: 2i: 10... \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg new file mode 100644 index 000000000..d83ecbe76 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg @@ -0,0 +1 @@ +outerj: 0j: 1j: 2j: 10...makeArmy() LexicalEnvironmentwhile iteration LexicalEnvironment \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md index 0fb0b4a49..d41c7a492 100644 --- a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md @@ -1,12 +1,20 @@ +<<<<<<< HEAD Let's examine what's done inside `makeArmy`, and the solution will become obvious. +======= +Let's examine what exactly happens inside `makeArmy`, and the solution will become obvious. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 1. It creates an empty array `shooters`: ```js let shooters = []; ``` +<<<<<<< HEAD 2. Fills it in the loop via `shooters.push(function...)`. +======= +2. Fills it with functions via `shooters.push(function)` in the loop. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Every element is a function, so the resulting array looks like this: @@ -26,6 +34,7 @@ Let's examine what's done inside `makeArmy`, and the solution will become obviou ``` 3. The array is returned from the function. +<<<<<<< HEAD Then, later, the call to `army[5]()` will get the element `army[5]` from the array (it will be a function) and call it. @@ -118,3 +127,106 @@ army[5](); // 5 The `while` loop, just like `for`, makes a new Lexical Environment for each run. So here we make sure that it gets the right value for a `shooter`. We copy `let j = i`. This makes a loop body local `j` and copies the value of `i` to it. Primitives are copied "by value", so we actually get a complete independent copy of `i`, belonging to the current loop iteration. +======= + + Then, later, the call to any member, e.g. `army[5]()` will get the element `army[5]` from the array (which is a function) and calls it. + + Now why do all such functions show the same value, `10`? + + That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment. + + Then, what will be the value of `i`? + + If we look at the source: + + ```js + function makeArmy() { + ... + let i = 0; + while (i < 10) { + let shooter = function() { // shooter function + alert( i ); // should show its number + }; + shooters.push(shooter); // add function to the array + i++; + } + ... + } + ``` + + We can see that all `shooter` functions are created in the lexical environment of `makeArmy()` function. But when `army[5]()` is called, `makeArmy` has already finished its job, and the final value of `i` is `10` (`while` stops at `i=10`). + + As the result, all `shooter` functions get the same value from the outer lexical environment and that is, the last value, `i=10`. + + ![](lexenv-makearmy-empty.svg) + + As you can see above, on each iteration of a `while {...}` block, a new lexical environment is created. So, to fix this, we can copy the value of `i` into a variable within the `while {...}` block, like this: + + ```js run + function makeArmy() { + let shooters = []; + + let i = 0; + while (i < 10) { + *!* + let j = i; + */!* + let shooter = function() { // shooter function + alert( *!*j*/!* ); // should show its number + }; + shooters.push(shooter); + i++; + } + + return shooters; + } + + let army = makeArmy(); + + // Now the code works correctly + army[0](); // 0 + army[5](); // 5 + ``` + + Here `let j = i` declares an "iteration-local" variable `j` and copies `i` into it. Primitives are copied "by value", so we actually get an independent copy of `i`, belonging to the current loop iteration. + + The shooters work correctly, because the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration: + + ![](lexenv-makearmy-while-fixed.svg) + + Such a problem could also be avoided if we used `for` in the beginning, like this: + + ```js run demo + function makeArmy() { + + let shooters = []; + + *!* + for(let i = 0; i < 10; i++) { + */!* + let shooter = function() { // shooter function + alert( i ); // should show its number + }; + shooters.push(shooter); + } + + return shooters; + } + + let army = makeArmy(); + + army[0](); // 0 + army[5](); // 5 + ``` + + That's essentially the same, because `for` on each iteration generates a new lexical environment, with its own variable `i`. So `shooter` generated in every iteration references its own `i`, from that very iteration. + + ![](lexenv-makearmy-for-fixed.svg) + +Now, as you've put so much effort into reading this, and the final recipe is so simple - just use `for`, you may wonder -- was it worth that? + +Well, if you could easily answer the question, you wouldn't read the solution. So, hopefully this task must have helped you to understand things a bit better. + +Besides, there are indeed cases when one prefers `while` to `for`, and other scenarios, where such problems are real. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/task.md b/1-js/06-advanced-functions/03-closure/10-make-army/task.md index 93e64f2d0..4f861b86a 100644 --- a/1-js/06-advanced-functions/03-closure/10-make-army/task.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/task.md @@ -14,6 +14,7 @@ function makeArmy() { let i = 0; while (i < 10) { +<<<<<<< HEAD let shooter = function() { // shooter function alert( i ); // should show its number }; @@ -21,15 +22,39 @@ function makeArmy() { i++; } +======= + let shooter = function() { // create a shooter function, + alert( i ); // that should show its number + }; + shooters.push(shooter); // and add it to the array + i++; + } + + // ...and return the array of shooters +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 return shooters; } let army = makeArmy(); +<<<<<<< HEAD army[0](); // the shooter number 0 shows 10 army[5](); // and number 5 also outputs 10... // ... all shooters show 10 instead of their 0, 1, 2, 3... ``` Why do all of the shooters show the same value? Fix the code so that they work as intended. +======= +*!* +// all shooters show 10 instead of their numbers 0, 1, 2, 3... +army[0](); // 10 from the shooter number 0 +army[1](); // 10 from the shooter number 1 +army[2](); // 10 ...and so on. +*/!* +``` + +Why do all of the shooters show the same value? + +Fix the code so that they work as intended. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md index d02c53b99..4e386eec5 100644 --- a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md +++ b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md @@ -1,4 +1,6 @@ +importance: 5 +--- # Function in if Look at the code. What will be the result of the call at the last line? diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md index 404bae80b..b33c6c307 100644 --- a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md @@ -15,7 +15,11 @@ function func() { func(); ``` +<<<<<<< HEAD In this example we can observe the peculiar difference between a "non-existing" and "unitialized" variable. +======= +In this example we can observe the peculiar difference between a "non-existing" and "uninitialized" variable. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 As you may have read in the article [](info:closure), a variable starts in the "uninitialized" state from the moment when the execution enters a code block (or a function). And it stays uninitalized until the corresponding `let` statement. @@ -27,7 +31,11 @@ The code above demonstrates it. function func() { *!* // the local variable x is known to the engine from the beginning of the function, +<<<<<<< HEAD // but "unitialized" (unusable) until let ("dead zone") +======= + // but "uninitialized" (unusable) until let ("dead zone") +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // hence the error */!* diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/solution.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/solution.js new file mode 100644 index 000000000..8a71c869d --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/solution.js @@ -0,0 +1,3 @@ +function byField(fieldName){ + return (a, b) => a[fieldName] > b[fieldName] ? 1 : -1; +} diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/source.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/source.js new file mode 100644 index 000000000..23b433834 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/source.js @@ -0,0 +1,5 @@ +function byField(fieldName){ + + // Your code goes here. + +} diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js new file mode 100644 index 000000000..802f28c4d --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js @@ -0,0 +1,39 @@ +describe("byField", function(){ + + let users = [ + { name: "John", age: 20, surname: "Johnson" }, + { name: "Pete", age: 18, surname: "Peterson" }, + { name: "Ann", age: 19, surname: "Hathaway" }, + ]; + + it("sorts users by name", function(){ + let nameSortedKey = [ + { name: "Ann", age: 19, surname: "Hathaway" }, + { name: "John", age: 20, surname: "Johnson"}, + { name: "Pete", age: 18, surname: "Peterson" }, + ]; + let nameSortedAnswer = users.sort(byField("name")); + assert.deepEqual(nameSortedKey, nameSortedAnswer); + }); + + it("sorts users by age", function(){ + let ageSortedKey = [ + { name: "Pete", age: 18, surname: "Peterson" }, + { name: "Ann", age: 19, surname: "Hathaway" }, + { name: "John", age: 20, surname: "Johnson"}, + ]; + let ageSortedAnswer = users.sort(byField("age")); + assert.deepEqual(ageSortedKey, ageSortedAnswer); + }); + + it("sorts users by surname", function(){ + let surnameSortedKey = [ + { name: "Ann", age: 19, surname: "Hathaway" }, + { name: "John", age: 20, surname: "Johnson"}, + { name: "Pete", age: 18, surname: "Peterson" }, + ]; + let surnameSortedAnswer = users.sort(byField("surname")); + assert.deepEqual(surnameSortedAnswer, surnameSortedKey); + }); + +}); diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md b/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md index bd57085ea..3ca5bac48 100644 --- a/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md @@ -1,4 +1,5 @@ +<<<<<<< HEAD ```js run let users = [ @@ -20,3 +21,5 @@ users.sort(byField('age')); users.forEach(user => alert(user.name)); // Pete, Ann, John ``` +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 1845482e5..c5a1d58d7 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -1,4 +1,5 @@ +<<<<<<< HEAD # Variable scope JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, passed as an argument to another function and called from a totally different place of code later. @@ -19,6 +20,32 @@ In JavaScript, there are 3 ways to declare a variable: `let`, `const` (the moder If a variable is declared inside a code block `{...}`, it's only visible inside that block. +======= +# Variable scope, closure + +JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created at any moment, passed as an argument to another function, and then called from a totally different place of code later. + +We already know that a function can access variables outside of it ("outer" variables). + +But what happens if outer variables change since a function is created? Will the function get newer values or the old ones? + +And what if a function is passed along as an argument and called from another place of code, will it get access to outer variables at the new place? + +Let's expand our knowledge to understand these scenarios and more complex ones. + +```smart header="We'll talk about `let/const` variables here" +In JavaScript, there are 3 ways to declare a variable: `let`, `const` (the modern ones), and `var` (the remnant of the past). + +- In this article we'll use `let` variables in examples. +- Variables, declared with `const`, behave the same, so this article is about `const` too. +- The old `var` has some notable differences, they will be covered in the article . +``` + +## Code blocks + +If a variable is declared inside a code block `{...}`, it's only visible inside that block. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 For example: ```js run @@ -142,7 +169,11 @@ Despite being simple, slightly modified variants of that code have practical use How does this work? If we create multiple counters, will they be independent? What's going on with the variables here? +<<<<<<< HEAD Undestanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. +======= +Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Lexical Environment @@ -183,7 +214,11 @@ Rectangles on the right-hand side demonstrate how the global Lexical Environment 1. When the script starts, the Lexical Environment is pre-populated with all declared variables. - Initially, they are in the "Uninitialized" state. That's a special internal state, it means that the engine knows about the variable, but it cannot be referenced until it has been declared with `let`. It's almost the same as if the variable didn't exist. +<<<<<<< HEAD 2. Then `let phrase` definition appears. There's no assignment yet, so its value is `undefined`. We can use the variable since this moment. +======= +2. Then `let phrase` definition appears. There's no assignment yet, so its value is `undefined`. We can use the variable from this point forward. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 3. `phrase` is assigned a value. 4. `phrase` changes the value. @@ -286,7 +321,11 @@ Later, when `counter()` is called, a new Lexical Environment is created for the ![](closure-makecounter-nested-call.svg) +<<<<<<< HEAD Now when the code inside `counter()` looks for `count` variable, it first searches its own Lexical Environment (empty, as there are no local variables there), then the Lexical Environment of the outer `makeCounter()` call, where finds it and changes. +======= +Now when the code inside `counter()` looks for `count` variable, it first searches its own Lexical Environment (empty, as there are no local variables there), then the Lexical Environment of the outer `makeCounter()` call, where it finds and changes it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **A variable is updated in the Lexical Environment where it lives.** @@ -310,7 +349,11 @@ When on an interview, a frontend developer gets a question about "what's a closu Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That's because there are no references to it. As any JavaScript object, it's only kept in memory while it's reachable. +<<<<<<< HEAD ...But if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment. +======= +However, if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive. @@ -329,7 +372,7 @@ let g = f(); // g.[[Environment]] stores a reference to the Lexical Environment // of the corresponding f() call ``` -Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below: +Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. In the code below, all 3 of them: ```js function f() { @@ -367,7 +410,7 @@ As we've seen, in theory while a function is alive, all outer variables are also But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it's obvious from the code that an outer variable is not used -- it is removed. -**An important side effect in V8 (Chrome, Opera) is that such variable will become unavailable in debugging.** +**An important side effect in V8 (Chrome, Edge, Opera) is that such variable will become unavailable in debugging.** Try running the example below in Chrome with the Developer Tools open. @@ -409,6 +452,12 @@ let g = f(); g(); ``` +<<<<<<< HEAD This feature of V8 is good to know. If you are debugging with Chrome/Opera, sooner or later you will meet it. That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You always can check for it by running the examples on this page. +======= +This feature of V8 is good to know. If you are debugging with Chrome/Edge/Opera, sooner or later you will meet it. + +That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You can always check for it by running the examples on this page. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 2a9dbc636..02eaaa78d 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -4,7 +4,11 @@ ```smart header="This article is for understanding old scripts" The information in this article is useful for understanding old scripts. +<<<<<<< HEAD That's not how we write a new code. +======= +That's not how we write new code. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration: @@ -13,31 +17,22 @@ In the very first chapter about [variables](info:variables), we mentioned three 2. `const` 3. `var` -`let` and `const` behave exactly the same way in terms of Lexical Environments. - -But `var` is a very different beast, that originates from very old times. It's generally not used in modern scripts, but still lurks in the old ones. - -If you don't plan on meeting such scripts you may even skip this chapter or postpone it, but then there's a chance that it bites you later. - -From the first sight, `var` behaves similar to `let`. That is, declares a variable: +The `var` declaration is similar to `let`. Most of the time we can replace `let` by `var` or vice-versa and expect things to work: ```js run -function sayHi() { - var phrase = "Hello"; // local variable, "var" instead of "let" - - alert(phrase); // Hello -} +var message = "Hi"; +alert(message); // Hi +``` -sayHi(); +But internally `var` is a very different beast, that originates from very old times. It's generally not used in modern scripts, but still lurks in the old ones. -alert(phrase); // Error, phrase is not defined -``` +If you don't plan on meeting such scripts you may even skip this chapter or postpone it. -...But here are the differences. +On the other hand, it's important to understand differences when migrating old scripts from `var` to `let`, to avoid odd errors. ## "var" has no block scope -Variables, declared with `var`, are either function-wide or global. They are visible through blocks. +Variables, declared with `var`, are either function-scoped or global-scoped. They are visible through blocks. For instance: @@ -61,19 +56,21 @@ if (true) { } *!* -alert(test); // Error: test is not defined +alert(test); // ReferenceError: test is not defined */!* ``` The same thing for loops: `var` cannot be block- or loop-local: -```js +```js run for (var i = 0; i < 10; i++) { + var one = 1; // ... } *!* -alert(i); // 10, "i" is visible after loop, it's a global variable +alert(i); // 10, "i" is visible after loop, it's a global variable +alert(one); // 1, "one" is visible after loop, it's a global variable */!* ``` @@ -89,12 +86,32 @@ function sayHi() { } sayHi(); -alert(phrase); // Error: phrase is not defined (Check the Developer Console) +alert(phrase); // ReferenceError: phrase is not defined ``` -As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript blocks had no Lexical Environments. And `var` is a remnant of that. +As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript, blocks had no Lexical Environments, and `var` is a remnant of that. + +## "var" tolerates redeclarations + +If we declare the same variable with `let` twice in the same scope, that's an error: + +```js run +let user; +let user; // SyntaxError: 'user' has already been declared +``` + +With `var`, we can redeclare a variable any number of times. If we use `var` with an already-declared variable, it's just ignored: + +```js run +var user = "Pete"; + +var user = "John"; // this "var" does nothing (already declared) +// ...it doesn't trigger an error + +alert(user); // John +``` -## "var" declarations are processed at the function start +## "var" variables can be declared below their use `var` declarations are processed when the function starts (or script starts for globals). @@ -157,7 +174,7 @@ That's best demonstrated with an example: ```js run function sayHi() { - alert(phrase); + alert(phrase); *!* var phrase = "Hello"; @@ -192,7 +209,75 @@ sayHi(); Because all `var` declarations are processed at the function start, we can reference them at any place. But variables are undefined until the assignments. -In both examples above `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`. +In both examples above, `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`. + +## IIFE + +In the past, as there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE). + +That's not something we should use nowadays, but you can find them in old scripts. + +An IIFE looks like this: + +```js run +(function() { + + var message = "Hello"; + + alert(message); // Hello + +})(); +``` + +Here, a Function Expression is created and immediately called. So the code executes right away and has its own private variables. + +The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript engine encounters `"function"` in the main code, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error: + +```js run +// Tries to declare and immediately call a function +function() { // <-- SyntaxError: Function statements require a function name + + var message = "Hello"; + + alert(message); // Hello + +}(); +``` + +Even if we say: "okay, let's add a name", that won't work, as JavaScript does not allow Function Declarations to be called immediately: + +```js run +// syntax error because of parentheses below +function go() { + +}(); // <-- can't call Function Declaration immediately +``` + +So, the parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression: it needs no name and can be called immediately. + +There exist other ways besides parentheses to tell JavaScript that we mean a Function Expression: + +```js run +// Ways to create IIFE + +*!*(*/!*function() { + alert("Parentheses around the function"); +}*!*)*/!*(); + +*!*(*/!*function() { + alert("Parentheses around the whole thing"); +}()*!*)*/!*; + +*!*!*/!*function() { + alert("Bitwise NOT operator starts the expression"); +}(); + +*!*+*/!*function() { + alert("Unary plus starts the expression"); +}(); +``` + +In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code. ### IIFE @@ -266,7 +351,7 @@ In all the above cases we declare a Function Expression and run it immediately. There are two main differences of `var` compared to `let/const`: -1. `var` variables have no block scope, they are visible minimum at the function level. +1. `var` variables have no block scope, their visibility is scoped to current function, or global, if declared outside function. 2. `var` declarations are processed at function start (script start for globals). There's one more very minor difference related to the global object, that we'll cover in the next chapter. diff --git a/1-js/06-advanced-functions/05-global-object/article.md b/1-js/06-advanced-functions/05-global-object/article.md index 3d195a978..cf4839d94 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -5,7 +5,7 @@ The global object provides variables and functions that are available anywhere. In a browser it is named `window`, for Node.js it is `global`, for other environments it may have another name. -Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. In some browsers, namely non-Chromium Edge, `globalThis` is not yet supported, but can be easily polyfilled. +Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers. We'll use `window` here, assuming that our environment is a browser. If your script may run in other environments, it's better to use `globalThis` instead. @@ -25,7 +25,9 @@ var gVar = 5; alert(window.gVar); // 5 (became a property of the global object) ``` -Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such thing doesn't happen. +Function declarations have the same effect (statements with `function` keyword in the main code flow, not function expressions). + +Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such a thing doesn't happen. If we used `let` instead, such thing wouldn't happen: @@ -81,7 +83,7 @@ if (!window.Promise) { That includes JavaScript built-ins, such as `Array` and environment-specific values, such as `window.innerHeight` -- the window height in the browser. - The global object has a universal name `globalThis`. - ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). As `globalThis` is a recent proposal, it's not supported in non-Chromium Edge (but can be polyfilled). + ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). - We should store values in the global object only if they're truly global for our project. And keep their number at minimum. - In-browser, unless we're using [modules](info:modules), global functions and variables declared with `var` become a property of the global object. - To make our code future-proof and easier to understand, we should access properties of the global object directly, as `window.x`. diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/solution.js b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/solution.js new file mode 100644 index 000000000..c7d7d734e --- /dev/null +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/solution.js @@ -0,0 +1,15 @@ +function sum(a) { + + let currentSum = a; + + function f(b) { + currentSum += b; + return f; + } + + f.toString = function() { + return currentSum; + }; + + return f; +} diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/source.js b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/source.js new file mode 100644 index 000000000..f10dca5dc --- /dev/null +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/source.js @@ -0,0 +1,12 @@ +function sum(a){ + // Your code goes here. + +} + +/* +sum(1)(2) == 3; // 1 + 2 +sum(1)(2)(3) == 6; // 1 + 2 + 3 +sum(5)(-1)(2) == 6 +sum(6)(-1)(-2)(-3) == 0 +sum(0)(1)(2)(3)(4)(5) == 15 +*/ diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/test.js b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/test.js new file mode 100644 index 000000000..ed567d330 --- /dev/null +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/_js.view/test.js @@ -0,0 +1,19 @@ +describe("sum", function(){ + + it("sum(1)(2) == 3", function(){ + assert.equal(3, sum(1)(2)); + }); + + it("sum(5)(-1)(2) == 6", function(){ + assert.equal(6, sum(5)(-1)(2)); + }); + + it("sum(6)(-1)(-2)(-3) == 0", function(){ + assert.equal(0, sum(6)(-1)(-2)(-3)); + }); + + it("sum(0)(1)(2)(3)(4)(5) == 15", function(){ + assert.equal(15, sum(0)(1)(2)(3)(4)(5)); + }); +}); + diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md index 5c9326912..e97039f72 100644 --- a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md +++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md @@ -5,7 +5,7 @@ Now the code: -```js run +```js demo run function sum(a) { let currentSum = a; @@ -52,4 +52,4 @@ function f(b) { } ``` -This `f` will be used in the next call, again return itself, so many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. +This `f` will be used in the next call, again return itself, as many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion. diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md index ed848c0c5..c84f4e52f 100644 --- a/1-js/06-advanced-functions/06-function-object/article.md +++ b/1-js/06-advanced-functions/06-function-object/article.md @@ -326,7 +326,7 @@ welcome(); // Hello, Guest (nested call works) Now it works, because the name `"func"` is function-local. It is not taken from outside (and not visible there). The specification guarantees that it will always reference the current function. -The outer code still has it's variable `sayHi` or `welcome`. And `func` is an "internal function name", how the function can call itself internally. +The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to can call itself reliably. ```smart header="There's no such thing for Function Declaration" The "internal name" feature described here is only available for Function Expressions, not for Function Declarations. For Function Declarations, there is no syntax for adding an "internal" name. @@ -347,7 +347,7 @@ If the function is declared as a Function Expression (not in the main code flow) Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature. -They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. +They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want to learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. So, a function can do a useful job by itself and also carry a bunch of other functionality in properties. diff --git a/1-js/06-advanced-functions/07-new-function/article.md b/1-js/06-advanced-functions/07-new-function/article.md index 3214ba376..ffe264a4e 100644 --- a/1-js/06-advanced-functions/07-new-function/article.md +++ b/1-js/06-advanced-functions/07-new-function/article.md @@ -92,7 +92,7 @@ What if it could access the outer variables? The problem is that before JavaScript is published to production, it's compressed using a *minifier* -- a special program that shrinks code by removing extra comments, spaces and -- what's important, renames local variables into shorter ones. -For instance, if a function has `let userName`, minifier replaces it `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace. +For instance, if a function has `let userName`, minifier replaces it with `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace. So if `new Function` had access to outer variables, it would be unable to find renamed `userName`. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md index 95fddea65..f96959988 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -27,7 +27,7 @@ Usually, that's a function. For historical reasons, a string of code can be pass : The delay before run, in milliseconds (1000 ms = 1 second), by default 0. `arg1`, `arg2`... -: Arguments for the function (not supported in IE9-) +: Arguments for the function For instance, this code calls `sayHi()` after one second: @@ -102,7 +102,7 @@ As we can see from `alert` output, in a browser the timer identifier is a number Again, there is no universal specification for these methods, so that's fine. -For browsers, timers are described in the [timers section](https://www.w3.org/TR/html5/webappapis.html#timers) of HTML5 standard. +For browsers, timers are described in the [timers section](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) of HTML Living Standard. ## setInterval @@ -129,7 +129,7 @@ setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000); ```smart header="Time goes on while `alert` is shown" In most browsers, including Chrome and Firefox the internal timer continues "ticking" while showing `alert/confirm/prompt`. -So if you run the code above and don't dismiss the `alert` window for some time, then in the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds. +So if you run the code above and don't dismiss the `alert` window for some time, then the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds. ``` ## Nested setTimeout @@ -232,7 +232,7 @@ setTimeout(function() {...}, 100); For `setInterval` the function stays in memory until `clearInterval` is called. -There's a side-effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small. +There's a side effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small. ```` ## Zero delay setTimeout @@ -256,7 +256,7 @@ The first line "puts the call into calendar after 0ms". But the scheduler will o There are also advanced browser-related use cases of zero-delay timeout, that we'll discuss in the chapter . ````smart header="Zero delay is in fact not zero (in a browser)" -In the browser, there's a limitation of how often nested timers can run. The [HTML5 standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". +In the browser, there's a limitation of how often nested timers can run. The [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". Let's demonstrate what it means with the example below. The `setTimeout` call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the `times` array. What do the real delays look like? Let's see: @@ -281,7 +281,7 @@ The similar thing happens if we use `setInterval` instead of `setTimeout`: `setI That limitation comes from ancient times and many scripts rely on it, so it exists for historical reasons. -For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html) for Node.js. So this note is browser-specific. +For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args) for Node.js. So this note is browser-specific. ```` ## Summary @@ -290,13 +290,13 @@ For server-side JavaScript, that limitation does not exist, and there exist othe - To cancel the execution, we should call `clearTimeout/clearInterval` with the value returned by `setTimeout/setInterval`. - Nested `setTimeout` calls are a more flexible alternative to `setInterval`, allowing us to set the time *between* executions more precisely. - Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current script is complete". -- The browser limits the minimal delay for five or more nested call of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons. +- The browser limits the minimal delay for five or more nested calls of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons. Please note that all scheduling methods do not *guarantee* the exact delay. For example, the in-browser timer may slow down for a lot of reasons: - The CPU is overloaded. - The browser tab is in the background mode. -- The laptop is on battery. +- The laptop is on battery saving mode. All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and OS-level performance settings. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js index 065a77d1f..661dd0cf4 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/solution.js @@ -1,15 +1,7 @@ -function debounce(f, ms) { - - let isCooldown = false; - +function debounce(func, ms) { + let timeout; return function() { - if (isCooldown) return; - - f.apply(this, arguments); - - isCooldown = true; - - setTimeout(() => isCooldown = false, ms); + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, arguments), ms); }; - -} \ No newline at end of file +} diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js index 8136b873c..750e649f8 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/_js.view/test.js @@ -1,50 +1,48 @@ -describe("debounce", function() { - before(function() { +describe('debounce', function () { + before(function () { this.clock = sinon.useFakeTimers(); }); - after(function() { + after(function () { this.clock.restore(); }); - it("trigger the fuction execution immediately", function () { - let mode; - const f = () => mode='leading'; - - debounce(f, 1000)(); // runs without a delay - - assert.equal(mode, 'leading'); + it('for one call - runs it after given ms', function () { + const f = sinon.spy(); + const debounced = debounce(f, 1000); + + debounced('test'); + assert(f.notCalled, 'not called immediately'); + this.clock.tick(1000); + assert(f.calledOnceWith('test'), 'called after 1000ms'); }); - - it("calls the function at maximum once in ms milliseconds", function() { - let log = ''; - function f(a) { - log += a; - } + it('for 3 calls - runs the last one after given ms', function () { + const f = sinon.spy(); + const debounced = debounce(f, 1000); - f = debounce(f, 1000); + debounced('a'); + setTimeout(() => debounced('b'), 200); // ignored (too early) + setTimeout(() => debounced('c'), 500); // runs (1000 ms passed) + this.clock.tick(1000); - f(1); // runs at once - f(2); // ignored + assert(f.notCalled, 'not called after 1000ms'); - setTimeout(() => f(3), 100); // ignored (too early) - setTimeout(() => f(4), 1100); // runs (1000 ms passed) - setTimeout(() => f(5), 1500); // ignored (less than 1000 ms from the last run) + this.clock.tick(500); - this.clock.tick(5000); - assert.equal(log, "14"); + assert(f.calledOnceWith('c'), 'called after 1500ms'); }); - it("keeps the context of the call", function() { + it('keeps the context of the call', function () { let obj = { f() { assert.equal(this, obj); - } + }, }; obj.f = debounce(obj.f, 1000); - obj.f("test"); + obj.f('test'); + this.clock.tick(5000); }); - + }); diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg new file mode 100644 index 000000000..e624ce020 --- /dev/null +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.svg @@ -0,0 +1 @@ +200ms1500ms1000ms0cf(a)f(b)f(c)500mstimecalls: after 1000ms \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html new file mode 100644 index 000000000..e3b4d5842 --- /dev/null +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/debounce.view/index.html @@ -0,0 +1,24 @@ + + + +Function handler is called on this input: +
+ + +

+ +Debounced function debounce(handler, 1000) is called on this input: +
+ + +

+ + + \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md index 4f5867ded..83e75f315 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md @@ -1,28 +1,13 @@ ```js demo -function debounce(f, ms) { - - let isCooldown = false; - +function debounce(func, ms) { + let timeout; return function() { - if (isCooldown) return; - - f.apply(this, arguments); - - isCooldown = true; - - setTimeout(() => isCooldown = false, ms); + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, arguments), ms); }; - } -``` - -A call to `debounce` returns a wrapper. There may be two states: -- `isCooldown = false` -- ready to run. -- `isCooldown = true` -- waiting for the timeout. - -In the first call `isCooldown` is falsy, so the call proceeds, and the state changes to `true`. +``` -While `isCooldown` is true, all other calls are ignored. +A call to `debounce` returns a wrapper. When called, it schedules the original function call after given `ms` and cancels the previous such timeout. -Then `setTimeout` reverts it to `false` after the given delay. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 2620f1c71..5b0fcc5f8 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -4,21 +4,48 @@ importance: 5 # Debounce decorator -The result of `debounce(f, ms)` decorator should be a wrapper that passes the call to `f` at maximum once per `ms` milliseconds. +The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments. -In other words, when we call a "debounced" function, it guarantees that all future calls to the function made less than `ms` milliseconds after the previous call will be ignored. +In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`). -For instance: +For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`. -```js no-beautify -let f = debounce(alert, 1000); +Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call. -f(1); // runs immediately -f(2); // ignored +![](debounce.svg) -setTimeout( () => f(3), 100); // ignored ( only 100 ms passed ) -setTimeout( () => f(4), 1100); // runs -setTimeout( () => f(5), 1500); // ignored (less than 1000 ms from the last run) +...And it will get the arguments of the very last call, other calls are ignored. + +Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)): + +```js +let f = _.debounce(alert, 1000); + +f("a"); +setTimeout( () => f("b"), 200); +setTimeout( () => f("c"), 500); +// debounced function waits 1000ms after the last call and then runs: alert("c") +``` + +Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished. + +There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result. + +In a web-browser, we can setup an event handler -- a function that's called on every change of an input field. Normally, an event handler is called very often, for every typed key. But if we `debounce` it by 1000ms, then it will be only called once, after 1000ms after the last input. + +```online + +In this live example, the handler puts the result into a box below, try it: + +[iframe border=1 src="debounce" height=200] + +See? The second input calls the debounced function, so its content is processed after 1000ms from the last input. ``` -In practice `debounce` is useful for functions that retrieve/update something when we know that nothing new can be done in such a short period of time, so it's better not to waste resources. +So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else. + +It waits the given time after the last call, and then runs its function, that can process the result. + +The task is to implement `debounce` decorator. + +Hint: that's just a few lines if you think about it :) diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js index d2cf8e151..e671438f6 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js @@ -7,8 +7,8 @@ describe("throttle(f, 1000)", function() { } before(function() { - f1000 = throttle(f, 1000); this.clock = sinon.useFakeTimers(); + f1000 = throttle(f, 1000); }); it("the first call runs now", function() { diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index cf851f771..6950664be 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -12,11 +12,10 @@ function throttle(func, ms) { savedThis = this; return; } + isThrottled = true; func.apply(this, arguments); // (1) - isThrottled = true; - setTimeout(function() { isThrottled = false; // (3) if (savedArgs) { diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index 9e08874af..cbd473196 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -4,16 +4,21 @@ importance: 5 # Throttle decorator -Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper, passing the call to `f` at maximum once per `ms` milliseconds. Those calls that fall into the "cooldown" period, are ignored. +Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper. -**The difference with `debounce` -- if an ignored call is the last during the cooldown, then it executes at the end of the delay.** +When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. + +Compared to the debounce decorator, the behavior is completely different: +- `debounce` runs the function once after the "cooldown" period. Good for processing the final result. +- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often. + +In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds. Let's check the real-life application to better understand that requirement and to see where it comes from. **For instance, we want to track mouse movements.** In a browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms). - **We'd like to update some information on the web-page when the pointer moves.** ...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index c0f34850c..5e2f48d71 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -36,11 +36,11 @@ function cachingDecorator(func) { slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1) is cached -alert( "Again: " + slow(1) ); // the same +alert( slow(1) ); // slow(1) is cached and the result returned +alert( "Again: " + slow(1) ); // slow(1) result returned from cache -alert( slow(2) ); // slow(2) is cached -alert( "Again: " + slow(2) ); // the same as the previous line +alert( slow(2) ); // slow(2) is cached and the result returned +alert( "Again: " + slow(2) ); // slow(2) result returned from cache ``` In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior. @@ -209,7 +209,7 @@ To make it all clear, let's see more deeply how `this` is passed along: 2. So when `worker.slow(2)` is executed, the wrapper gets `2` as an argument and `this=worker` (it's the object before dot). 3. Inside the wrapper, assuming the result is not yet cached, `func.call(this, x)` passes the current `this` (`=worker`) and the current argument (`=2`) to the original method. -## Going multi-argument with "func.apply" +## Going multi-argument Now let's make `cachingDecorator` even more universal. Till now it was working only with single-argument functions. @@ -236,7 +236,7 @@ There are many solutions possible: For many practical applications, the 3rd variant is good enough, so we'll stick to it. -Also we need to replace `func.call(this, x)` with `func.call(this, ...arguments)`, to pass all arguments to the wrapped function call, not just the first one. +Also we need to pass not just `x`, but all arguments in `func.call`. Let's recall that in a `function()` we can get a pseudo-array of its arguments as `arguments`, so `func.call(this, x)` should be replaced with `func.call(this, ...arguments)`. Here's a more powerful `cachingDecorator`: @@ -284,6 +284,8 @@ There are two changes: - In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions. - Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function. +## func.apply + Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`. The syntax of built-in method [func.apply](mdn:js/Function/apply) is: @@ -299,18 +301,26 @@ The only syntax difference between `call` and `apply` is that `call` expects a l So these two calls are almost equivalent: ```js +<<<<<<< HEAD func.call(context, ...args); // pass an array as list with spread syntax func.apply(context, args); // is same as using call +======= +func.call(context, ...args); +func.apply(context, args); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` -There's only a minor difference: +They perform the same call of `func` with given context and arguments. + +<<<<<<< HEAD +======= +There's only a subtle difference regarding `args`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. - The `apply` accepts only *array-like* `args`. -So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works. - -And for objects that are both iterable and array-like, like a real array, we technically could use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. +...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. Passing all arguments along with the context to another function is called *call forwarding*. @@ -344,7 +354,7 @@ function hash(args) { } ``` -...Unfortunately, that won't work. Because we are calling `hash(arguments)` and `arguments` object is both iterable and array-like, but not a real array. +...Unfortunately, that won't work. Because we are calling `hash(arguments)`, and `arguments` object is both iterable and array-like, but not a real array. So calling `join` on it would fail, as we can see below: diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md index 403107ca6..4a381c0b4 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md @@ -1,5 +1,5 @@ -The error occurs because `ask` gets functions `loginOk/loginFail` without the object. +The error occurs because `askPassword` gets functions `loginOk/loginFail` without the object. When it calls them, they naturally assume `this=undefined`. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 787c7d68e..6d65e7dd1 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -167,7 +167,7 @@ sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John! // even if the value of user changes within 1 second -// sayHi uses the pre-bound value +// sayHi uses the pre-bound value which is reference to the old user object user = { sayHi() { alert("Another user in setTimeout!"); } }; @@ -187,8 +187,8 @@ let user = { let say = user.say.bind(user); -say("Hello"); // Hello, John ("Hello" argument is passed to say) -say("Bye"); // Bye, John ("Bye" is passed to say) +say("Hello"); // Hello, John! ("Hello" argument is passed to say) +say("Bye"); // Bye, John! ("Bye" is passed to say) ``` ````smart header="Convenience method: `bindAll`" @@ -202,7 +202,7 @@ for (let key in user) { } ``` -JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash. +JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) in lodash. ```` ## Partial functions @@ -247,7 +247,7 @@ The call to `mul.bind(null, 2)` creates a new function `double` that passes call That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one. -Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. +Please note that we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. The function `triple` in the code below triples the value: diff --git a/1-js/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md index f5caeaece..8730277ad 100644 --- a/1-js/06-advanced-functions/12-arrow-functions/article.md +++ b/1-js/06-advanced-functions/12-arrow-functions/article.md @@ -52,7 +52,7 @@ let group = { *!* this.students.forEach(function(student) { // Error: Cannot read property 'title' of undefined - alert(this.title + ': ' + student) + alert(this.title + ': ' + student); }); */!* } @@ -87,7 +87,7 @@ For instance, `defer(f, ms)` gets a function and returns a wrapper around it tha ```js run function defer(f, ms) { return function() { - setTimeout(() => f.apply(this, arguments), ms) + setTimeout(() => f.apply(this, arguments), ms); }; } diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index 3593bffae..0a945b377 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -19,7 +19,7 @@ We didn't see them yet, because generally they do not show up. When we create a First, let's see how to get those flags. -The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. +The method [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. The syntax is: ```js @@ -54,7 +54,7 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` -To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty). +To change the flags, we can use [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). The syntax is: @@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name' Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours. ```smart header="Errors appear only in strict mode" -In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. ``` Here's the same example, but the property is created from scratch: @@ -194,7 +194,7 @@ alert(Object.keys(user)); // name The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties. -A non-configurable property can not be deleted. +A non-configurable property can't be deleted, its attributes can't be modified. For instance, `Math.PI` is non-writable, non-enumerable and non-configurable: @@ -214,49 +214,67 @@ alert( JSON.stringify(descriptor, null, 2 ) ); So, a programmer is unable to change the value of `Math.PI` or overwrite it. ```js run -Math.PI = 3; // Error +Math.PI = 3; // Error, because it has writable: false // delete Math.PI won't work either ``` +We also can't change `Math.PI` to be `writable` again: + +```js run +// Error, because of configurable: false +Object.defineProperty(Math, "PI", { writable: true }); +``` + +There's absolutely nothing we can do with `Math.PI`. + Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`. -To be precise, non-configurability imposes several restrictions on `defineProperty`: -1. Can't change `configurable` flag. -2. Can't change `enumerable` flag. -3. Can't change `writable: false` to `true` (the other way round works). -4. Can't change `get/set` for an accessor property (but can assign them if absent). +**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.** -Here we are making `user.name` a "forever sealed" constant: +Here `user.name` is non-configurable, but we can still change it (as it's writable): ```js run -let user = { }; +let user = { + name: "John" +}; + +Object.defineProperty(user, "name", { + configurable: false +}); + +user.name = "Pete"; // works fine +delete user.name; // Error +``` + +And here we make `user.name` a "forever sealed" constant, just like the built-in `Math.PI`: + +```js run +let user = { + name: "John" +}; Object.defineProperty(user, "name", { - value: "John", writable: false, configurable: false }); -*!* // won't be able to change user.name or its flags // all this won't work: -// user.name = "Pete" -// delete user.name -// defineProperty(user, "name", { value: "Pete" }) -Object.defineProperty(user, "name", {writable: true}); // Error -*/!* +user.name = "Pete"; +delete user.name; +Object.defineProperty(user, "name", { value: "Pete" }); ``` -```smart header="\"Non-configurable\" doesn't mean \"non-writable\"" -Notable exception: a value of non-configurable, but writable property can be changed. +```smart header="The only attribute change possible: writable true -> false" +There's a minor exception about changing flags. -The idea of `configurable: false` is to prevent changes to property flags and its deletion, not changes to its value. +We can change `writable: true` to `false` for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though. ``` ## Object.defineProperties -There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once. +There's a method [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) that allows to define many properties at once. The syntax is: @@ -282,7 +300,7 @@ So, we can set many properties at once. ## Object.getOwnPropertyDescriptors -To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors). +To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors). Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object: @@ -300,7 +318,7 @@ for (let key in user) { ...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred. -Another difference is that `for..in` ignores symbolic properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic ones. +Another difference is that `for..in` ignores symbolic and non-enumerable properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic and non-enumerable ones. ## Sealing an object globally @@ -308,24 +326,24 @@ Property descriptors work at the level of individual properties. There are also methods that limit access to the *whole* object: -[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) +[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) : Forbids the addition of new properties to the object. -[Object.seal(obj)](mdn:js/Object/seal) +[Object.seal(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) : Forbids adding/removing of properties. Sets `configurable: false` for all existing properties. -[Object.freeze(obj)](mdn:js/Object/freeze) +[Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) : Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties. And also there are tests for them: -[Object.isExtensible(obj)](mdn:js/Object/isExtensible) +[Object.isExtensible(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) : Returns `false` if adding properties is forbidden, otherwise `true`. -[Object.isSealed(obj)](mdn:js/Object/isSealed) +[Object.isSealed(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed) : Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`. -[Object.isFrozen(obj)](mdn:js/Object/isFrozen) +[Object.isFrozen(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen) : Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`. These methods are rarely used in practice. diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md index 889994cf3..c2aa35d53 100644 --- a/1-js/07-object-properties/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -1,11 +1,11 @@ # Property getters and setters -There are two kinds of properties. +There are two kinds of object properties. The first kind is *data properties*. We already know how to work with them. All properties that we've been using until now were data properties. -The second type of properties is something new. It's *accessor properties*. They are essentially functions that work on getting and setting a value, but look like regular properties to an external code. +The second type of property is something new. It's an *accessor property*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code. ## Getters and setters @@ -53,7 +53,7 @@ alert(user.fullName); // John Smith */!* ``` -From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes. +From the outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes. As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error: diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index e04e09434..ef6c7ffeb 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -12,7 +12,7 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named ![prototype](object-prototype-empty.svg) -The prototype is a little bit "magical". When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it. +When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. The property `[[Prototype]]` is internal and hidden, but there are many ways to set it. @@ -27,19 +27,11 @@ let rabbit = { }; *!* -rabbit.__proto__ = animal; +rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal */!* ``` -```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" -Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. - -It exists for historical reasons. In modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later. - -By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples. -``` - -If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`. +Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`. For instance: @@ -62,7 +54,7 @@ alert( rabbit.eats ); // true (**) alert( rabbit.jumps ); // true ``` -Here the line `(*)` sets `animal` to be a prototype of `rabbit`. +Here the line `(*)` sets `animal` to be the prototype of `rabbit`. Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up): @@ -130,6 +122,8 @@ alert(longEar.jumps); // true (from rabbit) ![](proto-animal-rabbit-chain.svg) +Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`. + There are only two limitations: 1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle. @@ -137,6 +131,18 @@ There are only two limitations: Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others. +```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" +It's a common mistake of novice developers not to know the difference between these two. + +Please note that `__proto__` is *not the same* as the internal `[[Prototype]]` property. It's a getter/setter for `[[Prototype]]`. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language. + +The `__proto__` property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use `Object.getPrototypeOf/Object.setPrototypeOf` functions instead that get/set the prototype. We'll also cover these functions later. + +By the specification, `__proto__` must only be supported by browsers. In fact though, all environments including server-side support `__proto__`, so we're quite safe using it. + +As the `__proto__` notation is a bit more intuitively obvious, we use it in the examples. +``` + ## Writing doesn't use prototype The prototype is only used for reading properties. @@ -197,6 +203,9 @@ alert(admin.fullName); // John Smith (*) // setter triggers! admin.fullName = "Alice Cooper"; // (**) + +alert(admin.fullName); // Alice Cooper, state of admin modified +alert(user.fullName); // John Smith, state of user protected ``` Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called. @@ -277,7 +286,7 @@ for(let prop in rabbit) alert(prop); // jumps, then eats */!* ``` -If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. +If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. So we can filter out inherited properties (or do something else with them): diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md index 0073e252e..372d50dd6 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md @@ -38,7 +38,12 @@ Why `user2.name` is `undefined`? Here's how `new user.constructor('Pete')` works: 1. First, it looks for `constructor` in `user`. Nothing. -2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing. -3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. +2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!). +3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`. +4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used. -At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object, similar to `let user2 = {}`, that's what we have in `user2` after all. +Finally, at the end, we have `let user2 = new Object('Pete')`. + +Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`. + +(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all). \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index 378936c9a..bdfc86dd8 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -2,7 +2,7 @@ The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it. -First we'll see at the details, and then how to use it for adding new capabilities to built-in objects. +First we'll look at the details, and then how to use it for adding new capabilities to built-in objects. ## Object.prototype @@ -33,7 +33,9 @@ We can check it like this: let obj = {}; alert(obj.__proto__ === Object.prototype); // true -// obj.toString === obj.__proto__.toString == Object.prototype.toString + +alert(obj.toString === obj.__proto__.toString); //true +alert(obj.toString === Object.prototype.toString); //true ``` Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`: diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md index a92e17900..f3c9cf0e5 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md @@ -28,4 +28,4 @@ alert(dictionary); // "apple,__proto__" When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable. -See the the chapter [](info:property-descriptors) for review. +See the chapter [](info:property-descriptors) for review. diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index dea718b08..52e974185 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -3,15 +3,18 @@ In the first chapter of this section, we mentioned that there are modern methods to setup a prototype. -The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard). +Setting or reading the prototype with `obj.__proto__` is considered outdated and somewhat deprecated (moved to the so-called "Annex B" of the JavaScript standard, meant for browsers only). -The modern methods are: +The modern methods to get/set a prototype are: -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`. - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`. -These should be used instead of `__proto__`. +The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`. + +Although, there's a special method for this too: + +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. For instance: @@ -22,7 +25,7 @@ let animal = { // create a new object with animal as a prototype *!* -let rabbit = Object.create(animal); +let rabbit = Object.create(animal); // same as {__proto__: animal} */!* alert(rabbit.eats); // true @@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {} */!* ``` -`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this: +The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors. + +We can provide additional properties to the new object there, like this: ```js run let animal = { @@ -57,27 +62,40 @@ The descriptors are in the same format as described in the chapter >>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 That's for historical reasons. -- The `"prototype"` property of a constructor function has worked since very ancient times. -- Later, in the year 2012, `Object.create` appeared in the standard. It gave the ability to create objects with a given prototype, but did not provide the ability to get/set it. So browsers implemented the non-standard `__proto__` accessor that allowed the user to get/set a prototype at any time. +The prototypal inheritance was in the language since its dawn, but the ways to manage it evolved over time. + +- The `prototype` property of a constructor function has worked since very ancient times. It's the oldest way to create objects with a given prototype. +- Later, in the year 2012, `Object.create` appeared in the standard. It gave the ability to create objects with a given prototype, but did not provide the ability to get/set it. Some browsers implemented the non-standard `__proto__` accessor that allowed the user to get/set a prototype at any time, to give more flexibility to developers. - Later, in the year 2015, `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, to perform the same functionality as `__proto__`. As `__proto__` was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is: optional for non-browser environments. +- Later, in the year 2022, it was officially allowed to use `__proto__` in object literals `{...}` (moved out of Annex B), but not as a getter/setter `obj.__proto__` (still in Annex B). + +Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`? -As of now we have all these ways at our disposal. +Why was `__proto__` partially rehabilitated and its usage allowed in `{...}`, but not as a getter/setter? -Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`? That's an interesting question, requiring us to understand why `__proto__` is bad. Read on to get the answer. +That's an interesting question, requiring us to understand why `__proto__` is bad. + +And soon we'll get the answer. ```warn header="Don't change `[[Prototype]]` on existing objects if speed matters" Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time and don't modify it anymore: `rabbit` inherits from `animal`, and that is not going to change. @@ -102,25 +120,36 @@ obj[key] = "some value"; alert(obj[key]); // [object Object], not "some value"! ``` -Here, if the user types in `__proto__`, the assignment is ignored! +Here, if the user types in `__proto__`, the assignment in line 4 is ignored! -That shouldn't surprise us. The `__proto__` property is special: it must be either an object or `null`. A string can not become a prototype. +That could surely be surprising for a non-developer, but pretty understandable for us. The `__proto__` property is special: it must be either an object or `null`. A string can not become a prototype. That's why assigning a string to `__proto__` is ignored. But we didn't *intend* to implement such behavior, right? We want to store key/value pairs, and the key named `"__proto__"` was not properly saved. So that's a bug! -Here the consequences are not terrible. But in other cases we may be assigning object values, and then the prototype may indeed be changed. As a result, the execution will go wrong in totally unexpected ways. +Here the consequences are not terrible. But in other cases we may be storing objects instead of strings in `obj`, and then the prototype will indeed be changed. As a result, the execution will go wrong in totally unexpected ways. What's worse -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side. -Unexpected things also may happen when assigning to `toString`, which is a function by default, and to other built-in methods. +Unexpected things also may happen when assigning to `obj.toString`, as it's a built-in object method. How can we avoid this problem? -First, we can just switch to using `Map`, then everything's fine. +First, we can just switch to using `Map` for storage instead of plain objects, then everything's fine: -But `Object` can also serve us well here, because language creators gave thought to that problem long ago. +```js run +let map = new Map(); -`__proto__` is not a property of an object, but an accessor property of `Object.prototype`: +let key = prompt("What's the key?", "__proto__"); +map.set(key, "some value"); + +alert(map.get(key)); // "some value" (as intended) +``` + +...But `Object` syntax is often more appealing, as it's more concise. + +Fortunately, we *can* use objects, because language creators gave thought to that problem long ago. + +As we know, `__proto__` is not a property of an object, but an accessor property of `Object.prototype`: ![](object-prototype-2.svg) @@ -128,11 +157,12 @@ So, if `obj.__proto__` is read or set, the corresponding getter/setter is called As it was said in the beginning of this tutorial section: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself. -Now, if we want to use an object as an associative array, we can do it with a little trick: +Now, if we intend to use an object as an associative array and be free of such problems, we can do it with a little trick: ```js run *!* let obj = Object.create(null); +// or: obj = { __proto__: null } */!* let key = prompt("What's the key?", "__proto__"); @@ -174,32 +204,26 @@ alert(Object.keys(chineseDictionary)); // hello,bye ## Summary -Modern methods to set up and directly access the prototype are: - -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with a given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors. -- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter). -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter). +- To create an object with the given prototype, use: -The built-in `__proto__` getter/setter is unsafe if we'd want to put user-generated keys into an object. Just because a user may enter `"__proto__"` as the key, and there'll be an error, with hopefully light, but generally unpredictable consequences. + - literal syntax: `{ __proto__: ... }`, allows to specify multiple properties + - or [Object.create(proto[, descriptors])](mdn:js/Object/create), allows to specify property descriptors. -So we can either use `Object.create(null)` to create a "very plain" object without `__proto__`, or stick to `Map` objects for that. + The `Object.create` provides an easy way to shallow-copy an object with all descriptors: -Also, `Object.create` provides an easy way to shallow-copy an object with all descriptors: + ```js + let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); + ``` -```js -let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); -``` +- Modern methods to get/set the prototype are: -We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just like other methods. + - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter). + - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter). -We can create an object without a prototype by `Object.create(null)`. Such objects are used as "pure dictionaries", they have no issues with `"__proto__"` as the key. +- Getting/setting the prototype using the built-in `__proto__` getter/setter isn't recommended, it's now in the Annex B of the specification. -Other methods: +- We also covered prototype-less objects, created with `Object.create(null)` or `{__proto__: null}`. -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of enumerable own string property names/values/key-value pairs. -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic keys. -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string keys. -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own keys. -- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): returns `true` if `obj` has its own (not inherited) key named `key`. + These objects are used as dictionaries, to store any (possibly user-generated) keys. -All methods that return object properties (like `Object.keys` and others) -- return "own" properties. If we want inherited ones, we can use `for..in`. + Normally, objects inherit built-in methods and `__proto__` getter/setter from `Object.prototype`, making corresponding keys "occupied" and potentially causing side effects. With `null` prototype, objects are truly empty. diff --git a/1-js/09-classes/01-class/1-rewrite-to-class/task.md b/1-js/09-classes/01-class/1-rewrite-to-class/task.md index 05365e410..4477de679 100644 --- a/1-js/09-classes/01-class/1-rewrite-to-class/task.md +++ b/1-js/09-classes/01-class/1-rewrite-to-class/task.md @@ -4,6 +4,6 @@ importance: 5 # Rewrite to class -The `Clock` class is written in functional style. Rewrite it the "class" syntax. +The `Clock` class (see the sandbox) is written in functional style. Rewrite it in the "class" syntax. P.S. The clock ticks in the console, open it to see. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index b495d45f3..c17addcd6 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -51,7 +51,7 @@ user.sayHi(); When `new User("John")` is called: 1. A new object is created. -2. The `constructor` runs with the given argument and assigns `this.name` to it. +2. The `constructor` runs with the given argument and assigns it to `this.name`. ...Then we can call object methods, such as `user.sayHi()`. @@ -110,7 +110,7 @@ alert(typeof User); // function alert(User === User.prototype.constructor); // true // The methods are in User.prototype, e.g: -alert(User.prototype.sayHi); // alert(this.name); +alert(User.prototype.sayHi); // the code of the sayHi method // there are exactly two methods in the prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi @@ -118,7 +118,7 @@ alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi ## Not just a syntactic sugar -Sometimes people say that `class` is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same without `class` keyword at all: +Sometimes people say that `class` is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same thing without using the `class` keyword at all: ```js run // rewriting class User in pure functions @@ -127,7 +127,7 @@ Sometimes people say that `class` is a "syntactic sugar" (syntax that is designe function User(name) { this.name = name; } -// any function prototype has constructor property by default, +// a function prototype has "constructor" property by default, // so we don't need to create it // 2. Add the method to prototype @@ -144,9 +144,9 @@ The result of this definition is about the same. So, there are indeed reasons wh Still, there are important differences. -1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually. +1. First, a function created by `class` is labelled by a special internal property `[[IsClassConstructor]]: true`. So it's not entirely the same as creating it manually. - And unlike a regular function, a class constructor must be called with `new`: + The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with `new`: ```js run class User { @@ -166,6 +166,7 @@ Still, there are important differences. alert(User); // class User { ... } ``` + There are other differences, we'll see them soon. 2. Class methods are non-enumerable. A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. @@ -209,7 +210,6 @@ new User().sayHi(); // works, shows MyClass definition alert(MyClass); // error, MyClass name isn't visible outside of the class ``` - We can even make classes dynamically "on-demand", like this: ```js run @@ -218,7 +218,7 @@ function makeClass(phrase) { return class { sayHi() { alert(phrase); - }; + } }; } @@ -229,7 +229,7 @@ new User().sayHi(); // Hello ``` -## Getters/setters, other shorthands +## Getters/setters Just like literal objects, classes may include getters/setters, computed properties etc. @@ -267,22 +267,11 @@ alert(user.name); // John user = new User(""); // Name is too short. ``` -The class declaration creates getters and setters in `User.prototype`, like this: +Technically, such class declaration works by creating getters and setters in `User.prototype`. -```js -Object.defineProperties(User.prototype, { - name: { - get() { - return this._name - }, - set(name) { - // ... - } - } -}); -``` +## Computed names [...] -Here's an example with a computed property name in brackets `[...]`: +Here's an example with a computed method name using brackets `[...]`: ```js run class User { @@ -298,13 +287,22 @@ class User { new User().sayHi(); ``` +<<<<<<< HEAD +======= +Such features are easy to remember, as they resemble that of literal objects. + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Class fields ```warn header="Old browsers may need a polyfill" Class fields are a recent addition to the language. ``` +<<<<<<< HEAD Previously, classes only had methods. +======= +Previously, our classes only had methods. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 "Class fields" is a syntax that allows to add any properties. @@ -313,7 +311,7 @@ For instance, let's add `name` property to `class User`: ```js run class User { *!* - name = "Anonymous"; + name = "John"; */!* sayHi() { @@ -321,15 +319,44 @@ class User { } } -new User().sayHi(); - -alert(User.prototype.sayHi); // placed in User.prototype -alert(User.prototype.name); // undefined, not placed in User.prototype +new User().sayHi(); // Hello, John! ``` +<<<<<<< HEAD The important thing about class fields is that they are set on individual objects, not `User.prototype`. Technically, they are processed after the constructor has done it's job. +======= +So, we just write " = " in the declaration, and that's it. + +The important difference of class fields is that they are set on individual objects, not `User.prototype`: + +```js run +class User { +*!* + name = "John"; +*/!* +} + +let user = new User(); +alert(user.name); // John +alert(User.prototype.name); // undefined +``` + +We can also assign values using more complex expressions and function calls: + +```js run +class User { +*!* + name = prompt("Name, please?", "John"); +*/!* +} + +let user = new User(); +alert(user.name); // John +``` + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### Making bound methods with class fields @@ -362,6 +389,7 @@ The problem is called "losing `this`". There are two approaches to fixing it, as discussed in the chapter : 1. Pass a wrapper-function, such as `setTimeout(() => button.click(), 1000)`. +<<<<<<< HEAD 2. Bind the method to object, e.g. in the constructor: ```js run @@ -386,6 +414,11 @@ setTimeout(button.click, 1000); // hello ``` Class fields provide a more elegant syntax for the latter solution: +======= +2. Bind the method to object, e.g. in the constructor. + +Class fields provide another, quite elegant syntax: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run class Button { @@ -404,9 +437,15 @@ let button = new Button("hello"); setTimeout(button.click, 1000); // hello ``` +<<<<<<< HEAD The class field `click = () => {...}` creates an independent function on each `Button` object, with `this` bound to the object. Then we can pass `button.click` around anywhere, and it will be called with the right `this`. That's especially useful in browser environment, when we need to setup a method as an event listener. +======= +The class field `click = () => {...}` is created on a per-object basis, there's a separate function for each `Button` object, with `this` inside it referencing that object. We can pass `button.click` around anywhere, and the value of `this` will always be correct. + +That's especially useful in browser environment, for event listeners. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Summary diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 3d3c145eb..464042d82 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -55,7 +55,7 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides! ``` -Object of `Rabbit` class have access to both `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. +Object of `Rabbit` class have access both to `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. Internally, `extends` keyword works using the good old prototype mechanics. It sets `Rabbit.prototype.[[Prototype]]` to `Animal.prototype`. So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. @@ -76,8 +76,8 @@ For instance, a function call that generates the parent class: ```js run function f(phrase) { return class { - sayHi() { alert(phrase) } - } + sayHi() { alert(phrase); } + }; } *!* @@ -106,7 +106,7 @@ class Rabbit extends Animal { } ``` -Usually we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. +Usually, however, we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. Classes provide `"super"` keyword for that. @@ -151,7 +151,7 @@ class Rabbit extends Animal { let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stands still. White rabbit hides! +rabbit.stop(); // White Rabbit stands still. White Rabbit hides! ``` Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process. @@ -160,6 +160,7 @@ Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the p As was mentioned in the chapter , arrow functions do not have `super`. If accessed, it's taken from the outer function. For instance: + ```js class Rabbit extends Animal { stop() { @@ -176,7 +177,6 @@ setTimeout(function() { super.stop() }, 1000); ``` ```` - ## Overriding constructor With constructors it gets a little bit tricky. @@ -230,7 +230,9 @@ let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. Whoops! We've got an error. Now we can't create rabbits. What went wrong? -The short answer is: constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`. +The short answer is: + +- **Constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`.** ...But why? What's going on here? Indeed, the requirement seems strange. @@ -243,7 +245,7 @@ That label affects its behavior with `new`. - When a regular function is executed with `new`, it creates an empty object and assigns it to `this`. - But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job. -So a derived constructor must call `super` in order to execute its parent (non-derived) constructor, otherwise the object for `this` won't be created. And we'll get an error. +So a derived constructor must call `super` in order to execute its parent (base) constructor, otherwise the object for `this` won't be created. And we'll get an error. For the `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here: @@ -278,6 +280,99 @@ alert(rabbit.earLength); // 10 */!* ``` +### Overriding class fields: a tricky note + +```warn header="Advanced note" +This note assumes you have a certain experience with classes, maybe in other programming languages. + +It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often). + +If you find it difficult to understand, just go on, continue reading, then return to it some time later. +``` + +We can override not only methods, but also class fields. + +Although, there's a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages. + +Consider this example: + +```js run +class Animal { + name = 'animal'; + + constructor() { + alert(this.name); // (*) + } +} + +class Rabbit extends Animal { + name = 'rabbit'; +} + +new Animal(); // animal +*!* +new Rabbit(); // animal +*/!* +``` + +Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value. + +There's no own constructor in `Rabbit`, so `Animal` constructor is called. + +What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`. + +**In other words, the parent constructor always uses its own field value, not the overridden one.** + +What's odd about it? + +If it's not clear yet, please compare with methods. + +Here's the same code, but instead of `this.name` field we call `this.showName()` method: + +```js run +class Animal { + showName() { // instead of this.name = 'animal' + alert('animal'); + } + + constructor() { + this.showName(); // instead of alert(this.name); + } +} + +class Rabbit extends Animal { + showName() { + alert('rabbit'); + } +} + +new Animal(); // animal +*!* +new Rabbit(); // rabbit +*/!* +``` + +Please note: now the output is different. + +And that's what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method. + +...But for class fields it's not so. As said, the parent constructor always uses the parent field. + +Why is there a difference? + +Well, the reason is the field initialization order. The class field is initialized: +- Before constructor for the base class (that doesn't extend anything), +- Immediately after `super()` for the derived class. + +In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`. + +So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used. + +This subtle difference between fields and methods is specific to JavaScript. + +Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here. + +If it becomes a problem, one can fix it by using methods or getters/setters instead of fields. ## Super: internals, [[HomeObject]] @@ -447,7 +542,7 @@ Here's the demo of a wrong `super` result after copying: ```js run let animal = { sayHi() { - console.log(`I'm an animal`); + alert(`I'm an animal`); } }; @@ -461,7 +556,7 @@ let rabbit = { let plant = { sayHi() { - console.log("I'm a plant"); + alert("I'm a plant"); } }; diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg similarity index 59% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg index 34d783b4d..915ab9aa6 100644 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg @@ -1 +1 @@ -call: function bind: function ...Function.prototypeconstructorObjectRabbit[[Prototype]][[Prototype]]constructorcall: function bind: function ...Function.prototypeRabbit[[Prototype]]constructorclass Rabbitclass Rabbit extends Object \ No newline at end of file +call: function bind: function ...Function.prototypeconstructorObjectRabbit[[Prototype]][[Prototype]]constructorcall: function bind: function ...Function.prototypeRabbit[[Prototype]]constructorclass Rabbitclass Rabbit extends Object \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md similarity index 79% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md index ca9e80601..cb9829ce0 100644 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md @@ -21,14 +21,14 @@ alert( rabbit.hasOwnProperty('name') ); // true But that's not all yet. -Even after the fix, there's still important difference in `"class Rabbit extends Object"` versus `class Rabbit`. +Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`. As we know, the "extends" syntax sets up two prototypes: 1. Between `"prototype"` of the constructor functions (for methods). 2. Between the constructor functions themselves (for static methods). -In our case, for `class Rabbit extends Object` it means: +In the case of `class Rabbit extends Object` it means: ```js run class Rabbit extends Object {} @@ -37,7 +37,7 @@ alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true ``` -So `Rabbit` now provides access to static methods of `Object` via `Rabbit`, like this: +So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this: ```js run class Rabbit extends Object {} @@ -67,7 +67,7 @@ alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error So `Rabbit` doesn't provide access to static methods of `Object` in that case. -By the way, `Function.prototype` has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`. +By the way, `Function.prototype` also has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`. Here's the picture: diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md similarity index 100% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index ab08f2ded..4b493a5e8 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -1,9 +1,9 @@ # Static properties and methods -We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*. +We can also assign a method to the class as a whole. Such methods are called *static*. -In a class, they are prepended by `static` keyword, like this: +In a class declaration, they are prepended by `static` keyword, like this: ```js run class User { @@ -31,9 +31,11 @@ User.staticMethod(); // true The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule). -Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it. +Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it. -For instance, we have `Article` objects and need a function to compare them. A natural solution would be to add `Article.compare` method, like this: +For instance, we have `Article` objects and need a function to compare them. + +A natural solution would be to add `Article.compare` static method: ```js run class Article { @@ -63,9 +65,11 @@ articles.sort(Article.compare); alert( articles[0].title ); // CSS ``` -Here `Article.compare` stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class. +Here `Article.compare` method stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class. + +Another example would be a so-called "factory" method. -Another example would be a so-called "factory" method. Imagine, we need few ways to create an article: +Let's say, we need multiple ways to create an article: 1. Create by given parameters (`title`, `date` etc). 2. Create an empty article with today's date. @@ -73,7 +77,7 @@ Another example would be a so-called "factory" method. Imagine, we need few ways The first way can be implemented by the constructor. And for the second one we can make a static method of the class. -Like `Article.createTodays()` here: +Such as `Article.createTodays()` here: ```js run class Article { @@ -101,10 +105,21 @@ Static methods are also used in database-related classes to search/save/remove e ```js // assuming Article is a special class for managing articles -// static method to remove the article: +// static method to remove the article by id: Article.remove({id: 12345}); ``` +````warn header="Static methods aren't available for individual objects" +Static methods are callable on classes, not on individual objects. + +E.g. such code won't work: + +```js +// ... +article.createTodays(); /// Error: article.createTodays is not a function +``` +```` + ## Static properties [recent browser=Chrome] @@ -125,7 +140,7 @@ That is the same as a direct assignment to `Article`: Article.publisher = "Ilya Kantor"; ``` -## Inheritance of static properties and methods +## Inheritance of static properties and methods [#statics-and-inheritance] Static properties and methods are inherited. diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 60ed0ef1b..91efb89ee 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -96,7 +96,9 @@ class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) { + value = 0; + } this._waterAmount = value; } @@ -114,10 +116,10 @@ class CoffeeMachine { let coffeeMachine = new CoffeeMachine(100); // add water -coffeeMachine.waterAmount = -10; // Error: Negative water +coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10 ``` -Now the access is under control, so setting the water below zero fails. +Now the access is under control, so setting the water amount below zero becomes impossible. ## Read-only "power" @@ -159,7 +161,7 @@ class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { - if (value < 0) throw new Error("Negative water"); + if (value < 0) value = 0; this._waterAmount = value; } @@ -190,7 +192,7 @@ There's a finished JavaScript proposal, almost in the standard, that provides la Privates should start with `#`. They are only accessible from inside the class. -For instance, here's a private `#waterLimit` property and the water-checking private method `#checkWater`: +For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`: ```js run class CoffeeMachine { @@ -199,19 +201,23 @@ class CoffeeMachine { */!* *!* - #checkWater(value) { - if (value < 0) throw new Error("Negative water"); - if (value > this.#waterLimit) throw new Error("Too much water"); + #fixWaterAmount(value) { + if (value < 0) return 0; + if (value > this.#waterLimit) return this.#waterLimit; } */!* + setWaterAmount(value) { + this.#waterLimit = this.#fixWaterAmount(value); + } + } let coffeeMachine = new CoffeeMachine(); *!* // can't access privates from outside of the class -coffeeMachine.#checkWater(); // Error +coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error */!* ``` @@ -232,7 +238,7 @@ class CoffeeMachine { } set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) value = 0; this.#waterAmount = value; } } diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index aa973da06..f9db989ca 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -2,7 +2,7 @@ The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account. -Such a check may be necessary in many cases. Here we'll use it for building a *polymorphic* function, the one that treats arguments differently depending on their type. +Such a check may be necessary in many cases. For example, it can be used for building a *polymorphic* function, the one that treats arguments differently depending on their type. ## The instanceof operator [#ref-instanceof] @@ -93,7 +93,7 @@ The algorithm of `obj instanceof Class` works roughly as follows: alert(rabbit instanceof Animal); // true */!* - // rabbit.__proto__ === Rabbit.prototype + // rabbit.__proto__ === Animal.prototype (no match) *!* // rabbit.__proto__.__proto__ === Animal.prototype (match!) */!* @@ -190,7 +190,7 @@ For most environment-specific objects, there is such a property. Here are some b ```js run // toStringTag for the environment-specific object and class: -alert( window[Symbol.toStringTag]); // window +alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 2ec196105..526b832ef 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -69,7 +69,7 @@ let sayMixin = { }; let sayHiMixin = { - __proto__: sayMixin, // (or we could use Object.create to set the prototype here) + __proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here) sayHi() { *!* @@ -103,7 +103,7 @@ Here's the diagram (see the right part): That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above. -As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. +As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`. ## EventMixin @@ -140,7 +140,7 @@ let eventMixin = { * menu.off('select', handler) */ off(eventName, handler) { - let handlers = this._eventHandlers && this._eventHandlers[eventName]; + let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for (let i = 0; i < handlers.length; i++) { if (handlers[i] === handler) { @@ -154,7 +154,7 @@ let eventMixin = { * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { - if (!this._eventHandlers || !this._eventHandlers[eventName]) { + if (!this._eventHandlers?.[eventName]) { return; // no handlers for that event name } diff --git a/1-js/09-classes/07-mixins/head.html b/1-js/09-classes/07-mixins/head.html index 77ea38b20..20e3a6354 100644 --- a/1-js/09-classes/07-mixins/head.html +++ b/1-js/09-classes/07-mixins/head.html @@ -18,7 +18,7 @@ * menu.off('select', handler) */ off(eventName, handler) { - let handlers = this._eventHandlers && this._eventHandlers[eventName]; + let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for(let i = 0; i < handlers.length; i++) { if (handlers[i] == handler) { diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md index 303431d6d..ec0dabc9a 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md @@ -1,8 +1,8 @@ The difference becomes obvious when we look at the code inside a function. -The behavior is different if there's a "jump out" of `try..catch`. +The behavior is different if there's a "jump out" of `try...catch`. -For instance, when there's a `return` inside `try..catch`. The `finally` clause works in case of *any* exit from `try..catch`, even via the `return` statement: right after `try..catch` is done, but before the calling code gets the control. +For instance, when there's a `return` inside `try...catch`. The `finally` clause works in case of *any* exit from `try...catch`, even via the `return` statement: right after `try...catch` is done, but before the calling code gets the control. ```js run function f() { @@ -11,7 +11,7 @@ function f() { *!* return "result"; */!* - } catch (e) { + } catch (err) { /// ... } finally { alert('cleanup!'); @@ -28,11 +28,11 @@ function f() { try { alert('start'); throw new Error("an error"); - } catch (e) { + } catch (err) { // ... if("can't handle the error") { *!* - throw e; + throw err; */!* } diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md index c573cc232..b6dc81326 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md @@ -6,12 +6,12 @@ importance: 5 Compare the two code fragments. -1. The first one uses `finally` to execute the code after `try..catch`: +1. The first one uses `finally` to execute the code after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } finally { *!* @@ -19,12 +19,12 @@ Compare the two code fragments. */!* } ``` -2. The second fragment puts the cleaning right after `try..catch`: +2. The second fragment puts the cleaning right after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index 162fc33a0..a4a33a840 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -1,14 +1,18 @@ -# Error handling, "try..catch" +# Error handling, "try...catch" No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons. Usually, a script "dies" (immediately stops) in case of an error, printing it to console. +<<<<<<< HEAD But there's a syntax construct `try..catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. +======= +But there's a syntax construct `try...catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -## The "try..catch" syntax +## The "try...catch" syntax -The `try..catch` construct has two main blocks: `try`, and then `catch`: +The `try...catch` construct has two main blocks: `try`, and then `catch`: ```js try { @@ -25,12 +29,21 @@ try { It works like this: 1. First, the code in `try {...}` is executed. +<<<<<<< HEAD 2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. 3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch(err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. ![](try-catch-flow.svg) So, an error inside the `try {…}` block does not kill the script -- we have a chance to handle it in `catch`. +======= +2. If there were no errors, then `catch (err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. +3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch (err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. + +![](try-catch-flow.svg) + +So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Let's look at some examples. @@ -45,7 +58,7 @@ Let's look at some examples. alert('End of try runs'); // *!*(2) <--*/!* - } catch(err) { + } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) @@ -64,7 +77,7 @@ Let's look at some examples. alert('End of try (never reached)'); // (2) - } catch(err) { + } catch (err) { alert(`Error has occurred!`); // *!*(3) <--*/!* @@ -72,45 +85,49 @@ Let's look at some examples. ``` -````warn header="`try..catch` only works for runtime errors" -For `try..catch` to work, the code must be runnable. In other words, it should be valid JavaScript. +````warn header="`try...catch` only works for runtime errors" +For `try...catch` to work, the code must be runnable. In other words, it should be valid JavaScript. It won't work if the code is syntactically wrong, for instance it has unmatched curly braces: ```js run try { {{{{{{{{{{{{ -} catch(e) { +} catch (err) { alert("The engine can't understand this code, it's invalid"); } ``` The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. +<<<<<<< HEAD So, `try..catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". +======= +So, `try...catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` -````warn header="`try..catch` works synchronously" -If an exception happens in "scheduled" code, like in `setTimeout`, then `try..catch` won't catch it: +````warn header="`try...catch` works synchronously" +If an exception happens in "scheduled" code, like in `setTimeout`, then `try...catch` won't catch it: ```js run try { setTimeout(function() { noSuchVariable; // script will die here }, 1000); -} catch (e) { +} catch (err) { alert( "won't work" ); } ``` -That's because the function itself is executed later, when the engine has already left the `try..catch` construct. +That's because the function itself is executed later, when the engine has already left the `try...catch` construct. -To catch an exception inside a scheduled function, `try..catch` must be inside that function: +To catch an exception inside a scheduled function, `try...catch` must be inside that function: ```js run setTimeout(function() { try { - noSuchVariable; // try..catch handles the error! + noSuchVariable; // try...catch handles the error! } catch { alert( "error is caught here!" ); } @@ -125,7 +142,7 @@ When an error occurs, JavaScript generates an object containing the details abou ```js try { // ... -} catch(err) { // <-- the "error object", could use another word instead of err +} catch (err) { // <-- the "error object", could use another word instead of err // ... } ``` @@ -150,7 +167,7 @@ try { *!* lalala; // error, variable is not defined! */!* -} catch(err) { +} catch (err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...call stack) @@ -175,9 +192,9 @@ try { } ``` -## Using "try..catch" +## Using "try...catch" -Let's explore a real-life use case of `try..catch`. +Let's explore a real-life use case of `try...catch`. As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) method to read JSON-encoded values. @@ -205,7 +222,7 @@ Should we be satisfied with that? Of course not! This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message. -Let's use `try..catch` to handle the error: +Let's use `try...catch` to handle the error: ```js run let json = "{ bad json }"; @@ -217,12 +234,12 @@ try { */!* alert( user.name ); // doesn't work -} catch (e) { +} catch (err) { *!* // ...the execution jumps here alert( "Our apologies, the data has errors, we'll try to request it one more time." ); - alert( e.name ); - alert( e.message ); + alert( err.name ); + alert( err.message ); */!* } ``` @@ -245,7 +262,7 @@ try { alert( user.name ); // no name! */!* -} catch (e) { +} catch (err) { alert( "doesn't execute" ); } ``` @@ -294,11 +311,15 @@ Let's see what kind of error `JSON.parse` generates: ```js run try { JSON.parse("{ bad json o_O }"); -} catch(e) { +} catch (err) { *!* - alert(e.name); // SyntaxError + alert(err.name); // SyntaxError */!* +<<<<<<< HEAD alert(e.message); // Unexpected token b in JSON at position 2 +======= + alert(err.message); // Unexpected token b in JSON at position 2 +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 } ``` @@ -323,8 +344,8 @@ try { alert( user.name ); -} catch(e) { - alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name +} catch (err) { + alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name } ``` @@ -334,7 +355,7 @@ Now `catch` became a single place for all error handling: both for `JSON.parse` ## Rethrowing -In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing. +In the example above we use `try...catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing. For example: @@ -345,7 +366,7 @@ try { user = JSON.parse(json); // <-- forgot to put "let" before user // ... -} catch(err) { +} catch (err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (no JSON Error actually) } @@ -353,29 +374,33 @@ try { Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a bug may be discovered that leads to terrible hacks. -In our case, `try..catch` is meant to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. +In our case, `try...catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. + +To avoid such problems, we can employ the "rethrowing" technique. The rule is simple: -Fortunately, we can find out which error we get, for instance from its `name`: +**Catch should only process errors that it knows and "rethrow" all others.** + +The "rethrowing" technique can be explained in more detail as: + +1. Catch gets all errors. +2. In the `catch (err) {...}` block we analyze the error object `err`. +3. If we don't know how to handle it, we do `throw err`. + +Usually, we can check the error type using the `instanceof` operator: ```js run try { user = { /*...*/ }; -} catch(e) { +} catch (err) { *!* - alert(e.name); // "ReferenceError" for accessing an undefined variable + if (err instanceof ReferenceError) { */!* + alert('ReferenceError'); // "ReferenceError" for accessing an undefined variable + } } ``` -The rule is simple: - -**Catch should only process errors that it knows and "rethrow" all others.** - -The "rethrowing" technique can be explained in more detail as: - -1. Catch gets all errors. -2. In the `catch(err) {...}` block we analyze the error object `err`. -2. If we don't know how to handle it, we do `throw err`. +We can also get the error class name from `err.name` property. All native errors have it. Another option is to read `err.constructor.name`. In the code below, we use rethrowing so that `catch` only handles `SyntaxError`: @@ -395,24 +420,24 @@ try { alert( user.name ); -} catch(e) { +} catch (err) { *!* - if (e.name == "SyntaxError") { - alert( "JSON Error: " + e.message ); + if (err instanceof SyntaxError) { + alert( "JSON Error: " + err.message ); } else { - throw e; // rethrow (*) + throw err; // rethrow (*) } */!* } ``` -The error throwing on line `(*)` from inside `catch` block "falls out" of `try..catch` and can be either caught by an outer `try..catch` construct (if it exists), or it kills the script. +The error throwing on line `(*)` from inside `catch` block "falls out" of `try...catch` and can be either caught by an outer `try...catch` construct (if it exists), or it kills the script. So the `catch` block actually handles only errors that it knows how to deal with and "skips" all others. -The example below demonstrates how such errors can be caught by one more level of `try..catch`: +The example below demonstrates how such errors can be caught by one more level of `try...catch`: ```js run function readData() { @@ -423,11 +448,11 @@ function readData() { *!* blabla(); // error! */!* - } catch (e) { + } catch (err) { // ... - if (e.name != 'SyntaxError') { + if (!(err instanceof SyntaxError)) { *!* - throw e; // rethrow (don't know how to deal with it) + throw err; // rethrow (don't know how to deal with it) */!* } } @@ -435,20 +460,20 @@ function readData() { try { readData(); -} catch (e) { +} catch (err) { *!* - alert( "External catch got: " + e ); // caught it! + alert( "External catch got: " + err ); // caught it! */!* } ``` -Here `readData` only knows how to handle `SyntaxError`, while the outer `try..catch` knows how to handle everything. +Here `readData` only knows how to handle `SyntaxError`, while the outer `try...catch` knows how to handle everything. -## try..catch..finally +## try...catch...finally Wait, that's not all. -The `try..catch` construct may have one more code clause: `finally`. +The `try...catch` construct may have one more code clause: `finally`. If it exists, it runs in all cases: @@ -460,7 +485,7 @@ The extended syntax looks like this: ```js *!*try*/!* { ... try to execute the code ... -} *!*catch*/!*(e) { +} *!*catch*/!* (err) { ... handle errors ... } *!*finally*/!* { ... execute always ... @@ -473,7 +498,7 @@ Try running this code: try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); -} catch (e) { +} catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); @@ -509,7 +534,7 @@ let start = Date.now(); try { result = fib(num); -} catch (e) { +} catch (err) { result = 0; *!* } finally { @@ -527,14 +552,14 @@ You can check by running the code with entering `35` into `prompt` -- it execute In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases. -```smart header="Variables are local inside `try..catch..finally`" -Please note that `result` and `diff` variables in the code above are declared *before* `try..catch`. +```smart header="Variables are local inside `try...catch...finally`" +Please note that `result` and `diff` variables in the code above are declared *before* `try...catch`. Otherwise, if we declared `let` in `try` block, it would only be visible inside of it. ``` ````smart header="`finally` and `return`" -The `finally` clause works for *any* exit from `try..catch`. That includes an explicit `return`. +The `finally` clause works for *any* exit from `try...catch`. That includes an explicit `return`. In the example below, there's a `return` in `try`. In this case, `finally` is executed just before the control returns to the outer code. @@ -546,7 +571,7 @@ function func() { return 1; */!* - } catch (e) { + } catch (err) { /* ... */ } finally { *!* @@ -559,9 +584,9 @@ alert( func() ); // first works alert from finally, and then this one ``` ```` -````smart header="`try..finally`" +````smart header="`try...finally`" -The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. +The `try...finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. ```js function func() { @@ -582,7 +607,7 @@ In the code above, an error inside `try` always falls out, because there's no `c The information from this section is not a part of the core JavaScript. ``` -Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error or some other terrible thing. +Let's imagine we've got a fatal error outside of `try...catch`, and the script died. Like a programming error or some other terrible thing. Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages), etc. @@ -628,7 +653,7 @@ For instance: The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers. -There are also web-services that provide error-logging for such cases, like or . +There are also web-services that provide error-logging for such cases, like or . They work like this: @@ -639,14 +664,14 @@ They work like this: ## Summary -The `try..catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. +The `try...catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. The syntax is: ```js try { // run this code -} catch(err) { +} catch (err) { // if an error happened, then jump here // err is the error object } finally { @@ -654,7 +679,7 @@ try { } ``` -There may be no `catch` section or no `finally`, so shorter constructs `try..catch` and `try..finally` are also valid. +There may be no `catch` section or no `finally`, so shorter constructs `try...catch` and `try...finally` are also valid. Error objects have following properties: @@ -662,10 +687,10 @@ Error objects have following properties: - `name` -- the string with error name (error constructor name). - `stack` (non-standard, but well-supported) -- the stack at the moment of error creation. -If an error object is not needed, we can omit it by using `catch {` instead of `catch(err) {`. +If an error object is not needed, we can omit it by using `catch {` instead of `catch (err) {`. We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter. *Rethrowing* is a very important pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know. -Even if we don't have `try..catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`. +Even if we don't have `try...catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`. diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md index ff2e4c529..d28b07439 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -21,9 +21,9 @@ Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it thr Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field. -Our `ValidationError` class should inherit from the built-in `Error` class. +Our `ValidationError` class should inherit from the `Error` class. -That class is built-in, but here's its approximate code so we can understand what we're extending: +The `Error` class is built-in, but here's its approximate code so we can understand what we're extending: ```js // The "pseudocode" for the built-in Error class defined by JavaScript itself @@ -38,7 +38,7 @@ class Error { Now let's inherit `ValidationError` from it and try it in action: -```js run untrusted +```js run *!* class ValidationError extends Error { */!* @@ -117,15 +117,15 @@ We could also look at `err.name`, like this: // instead of (err instanceof SyntaxError) } else if (err.name == "SyntaxError") { // (*) // ... -``` +``` The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof. -Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or other unknown ones) should fall through. +Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (caused by a typo in the code or other unknown reasons) should fall through. ## Further inheritance -The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age`). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. +The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age` instead of a number). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. ```js run class ValidationError extends Error { diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index 74943b2ea..372352260 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -3,6 +3,7 @@ # 介紹: 回呼 ```warn header="We use browser methods in examples here" +<<<<<<< HEAD 為了示範回呼、promise 及其他抽象概念的使用,我們將會使用一些瀏覽器的函式:更具體地說,載入腳本以及執行簡單的文件操作。 如果你並不熟悉這些方法,亦或是對於範例中的使用方式感到困惑,你可能會想要閱讀[下一部分](/document)教程中的一些章節。 @@ -13,6 +14,18 @@ 許多函式是由 JavaScript 的執行環境所提供,這些函式允許你安排*非同步*的動作。換句話說,我們現在啟動的動作,將在未來的某一刻完成。 舉例來說, `setTimeout` 就是一個這樣的函式。 +======= +To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods: specifically, loading scripts and performing simple document manipulations. + +If you're not familiar with these methods, and their usage in the examples is confusing, you may want to read a few chapters from the [next part](/document) of the tutorial. + +Although, we'll try to make things clear anyway. There won't be anything really complex browser-wise. +``` + +Many functions are provided by JavaScript host environments that allow you to schedule *asynchronous* actions. In other words, actions that we initiate now, but they finish later. + +For instance, one such function is the `setTimeout` function. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 真實世界中,還有其它非同步動作的例子。像是載入腳本及模組(我們會在後續的章節中介紹它們)。 @@ -20,15 +33,24 @@ ```js function loadScript(src) { +<<<<<<< HEAD // 建立一個 ``` -If we really need to make a window-level global variable, we can explicitly assign it to `window` and access as `window.user`. But that's an exception requiring a good reason. +```smart +In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`. + +Then all scripts will see it, both with `type="module"` and without it. + +That said, making such global variables is frowned upon. Please try to avoid them. +``` ### A module code is evaluated only the first time when imported -If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers. +If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers. +<<<<<<< HEAD That has important consequences. Let's look at them using examples: +======= +The one-time evaluation has important consequences, that we should be aware of. + +Let's see a couple of examples. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time: @@ -129,9 +153,11 @@ import `./alert.js`; // Module is evaluated! import `./alert.js`; // (shows nothing) ``` -In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable -- export it. +The second import shows nothing, because the module has already been evaluated. + +There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above. -Now, a more advanced example. +Now, let's consider a deeper example. Let's say, a module exports an object: @@ -156,54 +182,71 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* -// Both 1.js and 2.js imported the same object +// Both 1.js and 2.js reference the same admin object // Changes made in 1.js are visible in 2.js */!* ``` -So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that. +As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`. +<<<<<<< HEAD Such behavior allows us to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready. +======= +That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other importers will see that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -For instance, the `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside: +**Such behavior is actually very convenient, because it allows us to *configure* modules.** + +In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it. + +Here's the classical pattern: +1. A module exports some means of configuration, e.g. a configuration object. +2. On the first import we initialize it, write to its properties. The top-level application script may do that. +3. Further imports use the module. + +For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside: ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` -In `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself: +Here, `admin.js` exports the `config` object (initially empty, but may have default properties too). + +Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`: ```js // 📁 init.js -import {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` -Another module can also see `admin.name`: +...Now the module `admin.js` is configured. -```js -// 📁 other.js -import {admin, sayHi} from './admin.js'; +Further importers can call it, and it correctly shows the current user: -alert(admin.name); // *!*Pete*/!* +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta The object `import.meta` contains the information about the current module. -Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML: +Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML: ```html run height=0 ``` @@ -229,7 +272,7 @@ Compare it to non-module scripts, where `this` is a global object: There are also several browser-specific differences of scripts with `type="module"` compared to regular ones. -You may want skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. +You may want to skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. ### Module scripts are deferred @@ -240,7 +283,7 @@ In other words: - module scripts wait until the HTML document is fully ready (even if they are tiny and load faster than HTML), and then run. - relative order of scripts is maintained: scripts that go first in the document, execute first. -As a side-effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them. +As a side effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them. For instance: @@ -256,7 +299,7 @@ Compare to regular script below: diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 4bd41a168..447d54d39 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -46,7 +46,7 @@ Also, we can put `export` separately. Here we first declare, and then export: -```js +```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); @@ -93,25 +93,14 @@ At first sight, "import everything" seems such a cool thing, short to write, why Well, there are few reasons. -1. Modern build tools ([webpack](http://webpack.github.io) and others) bundle modules together and optimize them to speedup loading and remove unused stuff. - - Let's say, we added a 3rd-party library `say.js` to our project with many functions: - ```js - // 📁 say.js - export function sayHi() { ... } - export function sayBye() { ... } - export function becomeSilent() { ... } - ``` +1. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. +2. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. - Now if we only use one of `say.js` functions in our project: - ```js - // 📁 main.js - import {sayHi} from './say.js'; - ``` - ...Then the optimizer will see that and remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking". +```smart header="Don't be afraid to import too much" +Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also remove unused imports. -2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. -3. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. +For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimized bundle. +``` ## Import "as" @@ -224,7 +213,7 @@ Without `default`, such an export would give an error: export class { // Error! (non-default export needs a name) constructor() {} } -``` +``` ### The "default" name @@ -321,12 +310,16 @@ export {default as User} from './user.js'; // re-export default Why would that be needed? Let's see a practical use case. +<<<<<<< HEAD Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages), and many modules are just "helpers", for internal use in other package modules. +======= +Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages, but we don't have to use them), and many modules are just "helpers", for internal use in other package modules. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 The file structure could be like this: ``` auth/ - index.js + index.js user.js helpers.js tests/ @@ -337,13 +330,19 @@ auth/ ... ``` -We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this: +We'd like to expose the package functionality via a single entry point. + +In other words, a person who would like to use our package, should import only from the "main file" `auth/index.js`. + +Like this: ```js import {login, logout} from 'auth/index.js' ``` -The idea is that outsiders, developers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. +The "main file", `auth/index.js` exports all the functionality that we'd like to provide in our package. + +The idea is that outsiders, other programmers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. As the actual exported functionality is scattered among the package, we can import it into `auth/index.js` and export from it: @@ -366,19 +365,21 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo ```js // 📁 auth/index.js -// import login/logout and immediately export them +// re-export login/logout export {login, logout} from './helpers.js'; -// import default as User and export it +// re-export the default export as User export {default as User} from './user.js'; ... ``` +The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. + ### Re-exporting the default export The default export needs separate handling when re-exporting. -Let's say we have `user.js`, and we'd like to re-export class `User` from it: +Let's say we have `user.js` with the `export default class User` and would like to re-export it: ```js // 📁 user.js @@ -387,19 +388,21 @@ export default class User { } ``` -1. `export User from './user.js'` won't work. What can go wrong?... But that's a syntax error! +We can come across two problems with it: + +1. `export User from './user.js'` won't work. That would lead to a syntax error. - To re-export the default export, we have to write `export {default as User}`, as in the example above. + To re-export the default export, we have to write `export {default as User}`, as in the example above. 2. `export * from './user.js'` re-exports only named exports, but ignores the default one. - If we'd like to re-export both named and the default export, then two statements are needed: + If we'd like to re-export both named and default exports, then two statements are needed: ```js export * from './user.js'; // to re-export named exports export {default} from './user.js'; // to re-export the default export ``` -Such oddities of re-exporting the default export are one of the reasons why some developers don't like them. +Such oddities of re-exporting a default export are one of the reasons why some developers don't like default exports and prefer named ones. ## Summary @@ -418,14 +421,14 @@ You can check yourself by reading them and recalling what they mean: Import: -- Named exports from module: +- Importing named exports: - `import {x [as y], ...} from "module"` -- Default export: +- Importing the default export: - `import x from "module"` - `import {default as x} from "module"` -- Everything: +- Import all: - `import * as obj from "module"` -- Import the module (its code runs), but do not assign it to a variable: +- Import the module (its code runs), but do not assign any of its exports to variables: - `import "module"` We can put `import/export` statements at the top or at the bottom of a script, that doesn't matter. diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md index 357a57313..17c94a9c1 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md @@ -19,5 +19,9 @@ function wrap(target) { user = wrap(user); alert(user.name); // John +<<<<<<< HEAD alert(user.age); // ReferenceError: Property doesn't exist "age" +======= +alert(user.age); // ReferenceError: Property doesn't exist: "age" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md index 827cf35e3..9307b90ef 100644 --- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md +++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md @@ -1,8 +1,8 @@ -# Error on reading non-existant property +# Error on reading non-existent property -Usually, an attempt to read a non-existant property returns `undefined`. +Usually, an attempt to read a non-existent property returns `undefined`. -Create a proxy that throws an error for an attempt to read of a non-existant property instead. +Create a proxy that throws an error for an attempt to read of a non-existent property instead. That can help to detect programming mistakes early. @@ -27,6 +27,10 @@ user = wrap(user); alert(user.name); // John *!* +<<<<<<< HEAD alert(user.age); // ReferenceError: Property doesn't exist "age" +======= +alert(user.age); // ReferenceError: Property doesn't exist: "age" +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* ``` diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index ae06a580e..1f84912e5 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -39,7 +39,7 @@ As there are no traps, all operations on `proxy` are forwarded to `target`. As we can see, without any traps, `proxy` is a transparent wrapper around `target`. -![](proxy.svg) +![](proxy.svg) `Proxy` is a special "exotic object". It doesn't have own properties. With an empty `handler` it transparently forwards operations to `target`. @@ -61,13 +61,13 @@ For every internal method, there's a trap in this table: the name of the method | `[[Delete]]` | `deleteProperty` | `delete` operator | | `[[Call]]` | `apply` | function call | | `[[Construct]]` | `construct` | `new` operator | -| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) | -| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) | -| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) | -| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) | -| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) | -| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | -| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object/keys/values/entries` | +| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) | +| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) | +| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](mdn:/JavaScript/Reference/Global_Objects/Object/isExtensible) | +| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](mdn:/JavaScript/Reference/Global_Objects/Object/preventExtensions) | +| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperties) | +| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` | +| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object.keys/values/entries` | ```warn header="Invariants" JavaScript enforces some invariants -- conditions that must be fulfilled by internal methods and traps. @@ -335,7 +335,7 @@ let user = { _password: "secret" }; -alert(user._password); // secret +alert(user._password); // secret ``` Let's use proxies to prevent any access to properties starting with `_`. @@ -376,7 +376,7 @@ user = new Proxy(user, { }, *!* deleteProperty(target, prop) { // to intercept property deletion -*/!* +*/!* if (prop.startsWith('_')) { throw new Error("Access denied"); } else { @@ -437,7 +437,7 @@ user = { ``` -A call to `user.checkPassword()` call gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error. +A call to `user.checkPassword()` gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error. So we bind the context of object methods to the original object, `target`, in the line `(*)`. Then their future calls will use `target` as `this`, without any traps. @@ -662,7 +662,7 @@ In most cases we can do the same without `Reflect`, for instance, reading a prop ### Proxying a getter -Let's see an example that demonstrates why `Reflect.get` is better. And we'll also see why `get/set` have the fourth argument `receiver`, that we didn't use before. +Let's see an example that demonstrates why `Reflect.get` is better. And we'll also see why `get/set` have the third argument `receiver`, that we didn't use before. We have an object `user` with `_name` property and a getter for it. @@ -840,7 +840,7 @@ So there's no such problem when proxying an array. ### Private fields -The similar thing happens with private class fields. +A similar thing happens with private class fields. For example, `getName()` method accesses the private `#name` property and breaks after proxying: @@ -963,9 +963,13 @@ revoke(); alert(proxy.data); // Error ``` -A call to `revoke()` removes all internal references to the target object from the proxy, so they are no more connected. The target object can be garbage-collected after that. +A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected. + +Initially, `revoke` is separate from `proxy`, so that we can pass `proxy` around while leaving `revoke` in the current scope. -We can also store `revoke` in a `WeakMap`, to be able to easily find it by a proxy object: +We can also bind `revoke` method to proxy by setting `proxy.revoke = revoke`. + +Another option is to create a `WeakMap` that has `proxy` as the key and the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy: ```js run *!* @@ -980,21 +984,19 @@ let {proxy, revoke} = Proxy.revocable(object, {}); revokes.set(proxy, revoke); -// ..later in our code.. +// ..somewhere else in our code.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Error (revoked) ``` -The benefit of such an approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needed. - We use `WeakMap` instead of `Map` here because it won't block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more. ## References - Specification: [Proxy](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots). -- MDN: [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). +- MDN: [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). ## Summary @@ -1016,13 +1018,13 @@ We can trap: - Reading (`get`), writing (`set`), deleting (`deleteProperty`) a property (even a non-existing one). - Calling a function (`apply` trap). - The `new` operator (`construct` trap). -- Many other operations (the full list is at the beginning of the article and in the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)). +- Many other operations (the full list is at the beginning of the article and in the [docs](mdn:/JavaScript/Reference/Global_Objects/Proxy)). That allows us to create "virtual" properties and methods, implement default values, observable objects, function decorators and so much more. We can also wrap an object multiple times in different proxies, decorating it with various aspects of functionality. -The [Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects. +The [Reflect](mdn:/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects. Proxies have some limitations: diff --git a/1-js/99-js-misc/03-currying-partials/article.md b/1-js/99-js-misc/03-currying-partials/article.md index bb308847c..166804dcc 100644 --- a/1-js/99-js-misc/03-currying-partials/article.md +++ b/1-js/99-js-misc/03-currying-partials/article.md @@ -155,7 +155,7 @@ function curried(...args) { if (args.length >= func.length) { // (1) return func.apply(this, args); } else { - return function pass(...args2) { // (2) + return function(...args2) { // (2) return curried.apply(this, args.concat(args2)); } } @@ -164,9 +164,10 @@ function curried(...args) { When we run it, there are two `if` execution branches: -1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it. -2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result. +1. If passed `args` count is the same or more than the original function has in its definition (`func.length`) , then just pass the call to it using `func.apply`. +2. Otherwise, get a partial: we don't call `func` just yet. Instead, another wrapper is returned, that will re-apply `curried` providing previous arguments together with the new ones. +<<<<<<< HEAD For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`. For the call `curried(1)(2)(3)`: @@ -176,6 +177,9 @@ For the call `curried(1)(2)(3)`: 3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function. If that's still not obvious, just trace the calls sequence in your mind or on paper. +======= +Then, if we call it, again, we'll get either a new partial (if not enough arguments) or, finally, the result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Fixed-length functions only" The currying requires the function to have a fixed number of arguments. diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md b/1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md similarity index 73% rename from 1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md rename to 1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md index b89047db6..95372d060 100644 --- a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md +++ b/1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md @@ -34,4 +34,8 @@ let user = { (user.go)() // John ``` +<<<<<<< HEAD:1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md 請注意在圍繞著 `(user.go)` 的括號在這時根本沒作用。通常它們用在設定操作的順序,但此處的句點 `.` 無論如何都會先運行,所以沒效果。只有分號這件事較重要。 +======= +Please note that parentheses around `(user.go)` do nothing here. Usually they setup the order of operations, but here the dot `.` works first anyway, so there's no effect. Only the semicolon thing matters. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/99-js-misc/04-reference-type/2-check-syntax/solution.md diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/task.md b/1-js/99-js-misc/04-reference-type/2-check-syntax/task.md similarity index 100% rename from 1-js/04-object-basics/04-object-methods/2-check-syntax/task.md rename to 1-js/99-js-misc/04-reference-type/2-check-syntax/task.md diff --git a/1-js/04-object-basics/04-object-methods/3-why-this/solution.md b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md similarity index 54% rename from 1-js/04-object-basics/04-object-methods/3-why-this/solution.md rename to 1-js/99-js-misc/04-reference-type/3-why-this/solution.md index bc9fb4d4f..20d43e9f1 100644 --- a/1-js/04-object-basics/04-object-methods/3-why-this/solution.md +++ b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md @@ -4,7 +4,13 @@ 2. 同樣地,括號在此不變更操作順序,句點無論如何都會先執行。 +<<<<<<< HEAD:1-js/04-object-basics/04-object-methods/3-why-this/solution.md 3. 此處我們有個更為複雜的呼叫 `(expression).method()`。這個呼叫就像被拆成兩行一樣: +======= +2. The same, parentheses do not change the order of operations here, the dot is first anyway. + +3. Here we have a more complex call `(expression)()`. The call works as if it were split into two lines: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/99-js-misc/04-reference-type/3-why-this/solution.md ```js no-beautify f = obj.go; // 計算表達式 @@ -13,7 +19,11 @@ 此處 `f()` 被視為函式執行,且不帶有 `this`。 +<<<<<<< HEAD:1-js/04-object-basics/04-object-methods/3-why-this/solution.md 4. 與 `(3)` 是類似的事情,在句點 `.` 左側我們有個表達式。 +======= +4. The similar thing as `(3)`, to the left of the parentheses `()` we have an expression. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9:1-js/99-js-misc/04-reference-type/3-why-this/solution.md 要解釋 `(3)` 和 `(4)` 的行為,我們需要回想屬性存取運算子(句點或方括號)回傳的是參考類型的值。 diff --git a/1-js/04-object-basics/04-object-methods/3-why-this/task.md b/1-js/99-js-misc/04-reference-type/3-why-this/task.md similarity index 100% rename from 1-js/04-object-basics/04-object-methods/3-why-this/task.md rename to 1-js/99-js-misc/04-reference-type/3-why-this/task.md diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md new file mode 100644 index 000000000..894db8fc6 --- /dev/null +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -0,0 +1,108 @@ + +# Reference Type + +```warn header="In-depth language feature" +This article covers an advanced topic, to understand certain edge-cases better. + +It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood. +``` + +A dynamically evaluated method call can lose `this`. + +For instance: + +```js run +let user = { + name: "John", + hi() { alert(this.name); }, + bye() { alert("Bye"); } +}; + +user.hi(); // works + +// now let's call user.hi or user.bye depending on the name +*!* +(user.name == "John" ? user.hi : user.bye)(); // Error! +*/!* +``` + +On the last line there is a conditional operator that chooses either `user.hi` or `user.bye`. In this case the result is `user.hi`. + +Then the method is immediately called with parentheses `()`. But it doesn't work correctly! + +As you can see, the call results in an error, because the value of `"this"` inside the call becomes `undefined`. + +This works (object dot method): +```js +user.hi(); +``` + +This doesn't (evaluated method): +```js +(user.name == "John" ? user.hi : user.bye)(); // Error! +``` + +Why? If we want to understand why it happens, let's get under the hood of how `obj.method()` call works. + +## Reference type explained + +Looking closely, we may notice two operations in `obj.method()` statement: + +1. First, the dot `'.'` retrieves the property `obj.method`. +2. Then parentheses `()` execute it. + +So, how does the information about `this` get passed from the first part to the second one? + +If we put these operations on separate lines, then `this` will be lost for sure: + +```js run +let user = { + name: "John", + hi() { alert(this.name); } +}; + +*!* +// split getting and calling the method in two lines +let hi = user.hi; +hi(); // Error, because this is undefined +*/!* +``` + +Here `hi = user.hi` puts the function into the variable, and then on the last line it is completely standalone, and so there's no `this`. + +**To make `user.hi()` calls work, JavaScript uses a trick -- the dot `'.'` returns not a function, but a value of the special [Reference Type](https://tc39.github.io/ecma262/#sec-reference-specification-type).** + +The Reference Type is a "specification type". We can't explicitly use it, but it is used internally by the language. + +The value of Reference Type is a three-value combination `(base, name, strict)`, where: + +- `base` is the object. +- `name` is the property name. +- `strict` is true if `use strict` is in effect. + +The result of a property access `user.hi` is not a function, but a value of Reference Type. For `user.hi` in strict mode it is: + +```js +// Reference Type value +(user, "hi", true) +``` + +When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`user` in this case). + +Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`. + +Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`. + +So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). There are various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). + +## Summary + +Reference Type is an internal type of the language. + +Reading a property, such as with dot `.` in `obj.method()` returns not exactly the property value, but a special "reference type" value that stores both the property value and the object it was taken from. + +That's for the subsequent method call `()` to get the object and set `this` to it. + +For all other operations, the reference type automatically becomes the property value (a function in our case). + +The whole mechanics is hidden from our eyes. It only matters in subtle cases, such as when a method is obtained dynamically from the object, using an expression. diff --git a/1-js/99-js-misc/05-bigint/article.md b/1-js/99-js-misc/05-bigint/article.md index 72f06d0c7..0071fe0fe 100644 --- a/1-js/99-js-misc/05-bigint/article.md +++ b/1-js/99-js-misc/05-bigint/article.md @@ -50,7 +50,11 @@ The conversion operations are always silent, never give errors, but if the bigin ````smart header="The unary plus is not supported on bigints" The unary plus operator `+value` is a well-known way to convert `value` to a number. +<<<<<<< HEAD On bigints it's not supported, to avoid confusion: +======= +In order to avoid confusion, it's not supported on bigints: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let bigint = 1n; @@ -126,5 +130,9 @@ We can use such JSBI code "as is" for engines that don't support bigints and for ## References +<<<<<<< HEAD - [MDN docs on BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt). +======= +- [MDN docs on BigInt](mdn:/JavaScript/Reference/Global_Objects/BigInt). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - [Specification](https://tc39.es/ecma262/#sec-bigint-objects). diff --git a/1-js/99-js-misc/06-unicode/article.md b/1-js/99-js-misc/06-unicode/article.md new file mode 100644 index 000000000..4f144f824 --- /dev/null +++ b/1-js/99-js-misc/06-unicode/article.md @@ -0,0 +1,172 @@ + +# Unicode, String internals + +```warn header="Advanced knowledge" +The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters, or other rare symbols. +``` + +As we already know, JavaScript strings are based on [Unicode](https://en.wikipedia.org/wiki/Unicode): each character is represented by a byte sequence of 1-4 bytes. + +JavaScript allows us to insert a character into a string by specifying its hexadecimal Unicode code with one of these three notations: + +- `\xXX` + + `XX` must be two hexadecimal digits with a value between `00` and `FF`, then `\xXX` is the character whose Unicode code is `XX`. + + Because the `\xXX` notation supports only two hexadecimal digits, it can be used only for the first 256 Unicode characters. + + These first 256 characters include the Latin alphabet, most basic syntax characters, and some others. For example, `"\x7A"` is the same as `"z"` (Unicode `U+007A`). + + ```js run + alert( "\x7A" ); // z + alert( "\xA9" ); // ©, the copyright symbol + ``` + +- `\uXXXX` + `XXXX` must be exactly 4 hex digits with the value between `0000` and `FFFF`, then `\uXXXX` is the character whose Unicode code is `XXXX`. + + Characters with Unicode values greater than `U+FFFF` can also be represented with this notation, but in this case, we will need to use a so called surrogate pair (we will talk about surrogate pairs later in this chapter). + + ```js run + alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation + alert( "\u044F" ); // я, the Cyrillic alphabet letter + alert( "\u2191" ); // ↑, the arrow up symbol + ``` + +- `\u{X…XXXXXX}` + + `X…XXXXXX` must be a hexadecimal value of 1 to 6 bytes between `0` and `10FFFF` (the highest code point defined by Unicode). This notation allows us to easily represent all existing Unicode characters. + + ```js run + alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode) + alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) + ``` + +## Surrogate pairs + +All frequently used characters have 2-byte codes (4 hex digits). Letters in most European languages, numbers, and the basic unified CJK ideographic sets (CJK -- from Chinese, Japanese, and Korean writing systems), have a 2-byte representation. + +Initially, JavaScript was based on UTF-16 encoding that only allowed 2 bytes per character. But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol of Unicode. + +So rare symbols that require more than 2 bytes are encoded with a pair of 2-byte characters called "a surrogate pair". + +As a side effect, the length of such symbols is `2`: + +```js run +alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X +alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY +alert( '𩷶'.length ); // 2, a rare Chinese character +``` + +That's because surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! + +We actually have a single symbol in each of the strings above, but the `length` property shows a length of `2`. + +Getting a symbol can also be tricky, because most language features treat surrogate pairs as two characters. + +For example, here we can see two odd characters in the output: + +```js run +alert( '𝒳'[0] ); // shows strange symbols... +alert( '𝒳'[1] ); // ...pieces of the surrogate pair +``` + +Pieces of a surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. + +Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. + +So the methods [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) and [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) were added in JavaScript to deal with surrogate pairs. + +They are essentially the same as [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt), but they treat surrogate pairs correctly. + +One can see the difference here: + +```js run +// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of 𝒳: + +alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 + +// codePointAt is surrogate-pair aware +alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair +``` + +That said, if we take from position 1 (and that's rather incorrect here), then they both return only the 2nd part of the pair: + +```js run +alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 +alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 +// meaningless 2nd half of the pair +``` + +You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. + +````warn header="Takeaway: splitting strings at an arbitrary point is dangerous" +We can't just split a string at an arbitrary position, e.g. take `str.slice(0, 4)` and expect it to be a valid string, e.g.: + +```js run +alert( 'hi 😂'.slice(0, 4) ); // hi [?] +``` + +Here we can see a garbage character (first half of the smile surrogate pair) in the output. + +Just be aware of it if you intend to reliably work with surrogate pairs. May not be a big problem, but at least you should understand what happens. +```` + +## Diacritical marks and normalization + +In many languages, there are symbols that are composed of the base character with a mark above/under it. + +For instance, the letter `a` can be the base character for these characters: `àáâäãåā`. + +Most common "composite" characters have their own code in the Unicode table. But not all of them, because there are too many possible combinations. + +To support arbitrary compositions, the Unicode standard allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it. + +For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. + +```js run +alert( 'S\u0307' ); // Ṡ +``` + +If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. + +For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. + +For example: + +```js run +alert( 'S\u0307\u0323' ); // Ṩ +``` + +This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. + +For instance: + +```js run +let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below +let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above + +alert( `s1: ${s1}, s2: ${s2}` ); + +alert( s1 == s2 ); // false though the characters look identical (?!) +``` + +To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. + +It is implemented by [str.normalize()](mdn:js/String/normalize). + +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` + +It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +In reality, this is not always the case. The reason is that the symbol `Ṩ` is "common enough", so Unicode creators included it in the main table and gave it the code. + +If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md new file mode 100644 index 000000000..777bf703c --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md @@ -0,0 +1,483 @@ + +# WeakRef and FinalizationRegistry + +```warn header="\"Hidden\" features of the language" +This article covers a very narrowly focused topic, that most developers extremely rarely encounter in practice (and may not even be aware of its existence). + +We recommend skipping this chapter if you have just started learning JavaScript. +``` + +Recalling the basic concept of the *reachability principle* from the chapter, +we can note that the JavaScript engine is guaranteed to keep values in memory that are accessible or in use. + +For example: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// let's overwrite the value of the user variable +user = null; + +// the reference is lost and the object will be deleted from memory + +``` + +Or a similar, but slightly more complicated code with two strong references: + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// copied the strong reference to the object into the admin variable +*!* +let admin = user; +*/!* + +// let's overwrite the value of the user variable +user = null; + +// the object is still reachable through the admin variable +``` +The object `{ name: "John" }` would only be deleted from memory if there were no strong references to it (if we also overwrote the value of the `admin` variable). + +In JavaScript, there is a concept called `WeakRef`, which behaves slightly differently in this case. + + +````smart header="Terms: \"Strong reference\", \"Weak reference\"" +**Strong reference** - is a reference to an object or value, that prevents them from being deleted by the garbage collector. Thereby, keeping the object or value in memory, to which it points. + +This means, that the object or value remains in memory and is not collected by the garbage collector as long, as there are active strong references to it. + +In JavaScript, ordinary references to objects are strong references. For example: + +```js +// the user variable holds a strong reference to this object +let user = { name: "John" }; +``` +**Weak reference** - is a reference to an object or value, that does *not* prevent them from being deleted by the garbage collector. +An object or value can be deleted by the garbage collector if, the only remaining references to them are weak references. +```` + +## WeakRef + + +````warn header="Note of caution" +Before we dive into it, it is worth noting that the correct use of the structures discussed in this article requires very careful thought, and they are best avoided if possible. +```` + +`WeakRef` - is an object, that contains a weak reference to another object, called `target` or `referent`. + +The peculiarity of `WeakRef` is that it does not prevent the garbage collector from deleting its referent-object. In other words, a `WeakRef` object does not keep the `referent` object alive. + +Now let's take the `user` variable as the "referent" and create a weak reference from it to the `admin` variable. +To create a weak reference, you need to use the `WeakRef` constructor, passing in the target object (the object you want a weak reference to). + +In our case — this is the `user` variable: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// the admin variable holds a weak reference to the object +*!* +let admin = new WeakRef(user); +*/!* + +``` + +The diagram below depicts two types of references: a strong reference using the `user` variable and a weak reference using the `admin` variable: + +![](weakref-finalizationregistry-01.svg) + +Then, at some point, we stop using the `user` variable - it gets overwritten, goes out of scope, etc., while keeping the `WeakRef` instance in the `admin` variable: + +```js +// let's overwrite the value of the user variable +user = null; +``` + +A weak reference to an object is not enough to keep it "alive". When the only remaining references to a referent-object are weak references, the garbage collector is free to destroy this object and use its memory for something else. + +However, until the object is actually destroyed, the weak reference may return it, even if there are no more strong references to this object. +That is, our object becomes a kind of "[Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat)" – we cannot know for sure whether it's "alive" or "dead": + +![](weakref-finalizationregistry-02.svg) + +At this point, to get the object from the `WeakRef` instance, we will use its `deref()` method. + +The `deref()` method returns the referent-object that the `WeakRef` points to, if the object is still in memory. If the object has been deleted by the garbage collector, then the `deref()` method will return `undefined`: + + +```js +let ref = admin.deref(); + +if (ref) { + // the object is still accessible: we can perform any manipulations with it +} else { + // the object has been collected by the garbage collector +} +``` + +## WeakRef use cases + +`WeakRef` is typically used to create caches or [associative arrays](https://en.wikipedia.org/wiki/Associative_array) that store resource-intensive objects. +This allows one to avoid preventing these objects from being collected by the garbage collector solely based on their presence in the cache or associative array. + +One of the primary examples - is a situation when we have numerous binary image objects (for instance, represented as `ArrayBuffer` or `Blob`), and we want to associate a name or path with each image. +Existing data structures are not quite suitable for these purposes: + +- Using `Map` to create associations between names and images, or vice versa, will keep the image objects in memory since they are present in the `Map` as keys or values. +- `WeakMap` is ineligible for this goal either: because the objects represented as `WeakMap` keys use weak references, and are not protected from deletion by the garbage collector. + +But, in this situation, we need a data structure that would use weak references in its values. + +For this purpose, we can use a `Map` collection, whose values are `WeakRef` instances referring to the large objects we need. +Consequently, we will not keep these large and unnecessary objects in memory longer than they should be. + +Otherwise, this is a way to get the image object from the cache if it is still reachable. +If it has been garbage collected, we will re-generate or re-download it again. + +This way, less memory is used in some situations. + +## Example №1: using WeakRef for caching + +Below is a code snippet that demonstrates the technique of using `WeakRef`. + +In short, we use a `Map` with string keys and `WeakRef` objects as their values. +If the `WeakRef` object has not been collected by the garbage collector, we get it from the cache. +Otherwise, we re-download it again and put it in the cache for further possible reuse: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { // (1) + const imgCache = new Map(); // (2) + + return (imgName) => { // (3) + const cachedImg = imgCache.get(imgName); // (4) + + if (cachedImg?.deref()) { // (5) + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); // (6) + imgCache.set(imgName, new WeakRef(newImg)); // (7) + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +Let's delve into the details of what happened here: +1. `weakRefCache` - is a higher-order function that takes another function, `fetchImg`, as an argument. In this example, we can neglect a detailed description of the `fetchImg` function, since it can be any logic for downloading images. +2. `imgCache` - is a cache of images, that stores cached results of the `fetchImg` function, in the form of string keys (image name) and `WeakRef` objects as their values. +3. Return an anonymous function that takes the image name as an argument. This argument will be used as a key for the cached image. +4. Trying to get the cached result from the cache, using the provided key (image name). +5. If the cache contains a value for the specified key, and the `WeakRef` object has not been deleted by the garbage collector, return the cached result. +6. If there is no entry in the cache with the requested key, or `deref()` method returns `undefined` (meaning that the `WeakRef` object has been garbage collected), the `fetchImg` function downloads the image again. +7. Put the downloaded image into the cache as a `WeakRef` object. + +Now we have a `Map` collection, where the keys - are image names as strings, and values - are `WeakRef` objects containing the images themselves. + +This technique helps to avoid allocating a large amount of memory for resource-intensive objects, that nobody uses anymore. +It also saves memory and time in case of reusing cached objects. + +Here is a visual representation of what this code looks like: + +![](weakref-finalizationregistry-03.svg) + +But, this implementation has its drawbacks: over time, `Map` will be filled with strings as keys, that point to a `WeakRef`, whose referent-object has already been garbage collected: + +![](weakref-finalizationregistry-04.svg) + +One way to handle this problem - is to periodically scavenge the cache and clear out "dead" entries. +Another way - is to use finalizers, which we will explore next. + + +## Example №2: Using WeakRef to track DOM objects + +Another use case for `WeakRef` - is tracking DOM objects. + +Let's imagine a scenario where some third-party code or library interacts with elements on our page as long as they exist in the DOM. +For example, it could be an external utility for monitoring and notifying about the system's state (commonly so-called "logger" – a program that sends informational messages called "logs"). + +Interactive example: + +[codetabs height=420 src="weakref-dom"] + +When the "Start sending messages" button is clicked, in the so-called "logs display window" (an element with the `.window__body` class), messages (logs) start to appear. + +But, as soon as this element is deleted from the DOM, the logger should stop sending messages. +To reproduce the removal of this element, just click the "Close" button in the top right corner. + +In order not to complicate our work, and not to notify third-party code every time our DOM-element is available, and when it is not, it will be enough to create a weak reference to it using `WeakRef`. + +Once the element is removed from the DOM, the logger will notice it and stop sending messages. + +Now let's take a closer look at the source code (*tab `index.js`*): + +1. Get the DOM-element of the "Start sending messages" button. +2. Get the DOM-element of the "Close" button. +3. Get the DOM-element of the logs display window using the `new WeakRef()` constructor. This way, the `windowElementRef` variable holds a weak reference to the DOM-element. +4. Add an event listener on the "Start sending messages" button, responsible for starting the logger when clicked. +5. Add an event listener on the "Close" button, responsible for closing the logs display window when clicked. +6. Use `setInterval` to start displaying a new message every second. +7. If the DOM-element of the logs display window is still accessible and kept in memory, create and send a new message. +8. If the `deref()` method returns `undefined`, it means that the DOM-element has been deleted from memory. In this case, the logger stops displaying messages and clears the timer. +9. `alert`, which will be called, after the DOM-element of the logs display window is deleted from memory (i.e. after clicking the "Close" button). **Note, that deletion from memory may not happen immediately, as it depends only on the internal mechanisms of the garbage collector.** + + We cannot control this process directly from the code. However, despite this, we still have the option to force garbage collection from the browser. + + In Google Chrome, for example, to do this, you need to open the developer tools (`key:Ctrl` + `key:Shift` + `key:J` on Windows/Linux or `key:Option` + `key:⌘` + `key:J` on macOS), go to the "Performance" tab, and click on the bin icon button – "Collect garbage": + + ![](google-chrome-developer-tools.png) + +
+ This functionality is supported in most modern browsers. After the actions are taken, the alert will trigger immediately. + +## FinalizationRegistry + +Now it is time to talk about finalizers. Before we move on, let's clarify the terminology: + +**Cleanup callback (finalizer)** - is a function that is executed, when an object, registered in the `FinalizationRegistry`, is deleted from memory by the garbage collector. + +Its purpose - is to provide the ability to perform additional operations, related to the object, after it has been finally deleted from memory. + +**Registry** (or `FinalizationRegistry`) - is a special object in JavaScript that manages the registration and unregistration of objects and their cleanup callbacks. + +This mechanism allows registering an object to track and associate a cleanup callback with it. +Essentially it is a structure that stores information about registered objects and their cleanup callbacks, and then automatically invokes those callbacks when the objects are deleted from memory. + +To create an instance of the `FinalizationRegistry`, it needs to call its constructor, which takes a single argument - the cleanup callback (finalizer). + +Syntax: + +```js +function cleanupCallback(heldValue) { + // cleanup callback code +} + +const registry = new FinalizationRegistry(cleanupCallback); +``` + +Here: + +- `cleanupCallback` - a cleanup callback that will be automatically called when a registered object is deleted from memory. +- `heldValue` - the value that is passed as an argument to the cleanup callback. If `heldValue` is an object, the registry keeps a strong reference to it. +- `registry` - an instance of `FinalizationRegistry`. + +`FinalizationRegistry` methods: + +- `register(target, heldValue [, unregisterToken])` - used to register objects in the registry. + + `target` - the object being registered for tracking. If the `target` is garbage collected, the cleanup callback will be called with `heldValue` as its argument. + + Optional `unregisterToken` – an unregistration token. It can be passed to unregister an object before the garbage collector deletes it. Typically, the `target` object is used as `unregisterToken`, which is the standard practice. +- `unregister(unregisterToken)` - the `unregister` method is used to unregister an object from the registry. It takes one argument - `unregisterToken` (the unregister token that was obtained when registering the object). + +Now let's move on to a simple example. Let's use the already-known `user` object and create an instance of `FinalizationRegistry`: + +```js +let user = { name: "John" }; + +const registry = new FinalizationRegistry((heldValue) => { + console.log(`${heldValue} has been collected by the garbage collector.`); +}); +``` + +Then, we will register the object, that requires a cleanup callback by calling the `register` method: + +```js +registry.register(user, user.name); +``` + +The registry does not keep a strong reference to the object being registered, as this would defeat its purpose. If the registry kept a strong reference, then the object would never be garbage collected. + +If the object is deleted by the garbage collector, our cleanup callback may be called at some point in the future, with the `heldValue` passed to it: + +```js +// When the user object is deleted by the garbage collector, the following message will be printed in the console: +"John has been collected by the garbage collector." +``` + +There are also situations where, even in implementations that use a cleanup callback, there is a chance that it will not be called. + +For example: +- When the program fully terminates its operation (for example, when closing a tab in a browser). +- When the `FinalizationRegistry` instance itself is no longer reachable to JavaScript code. + If the object that creates the `FinalizationRegistry` instance goes out of scope or is deleted, the cleanup callbacks registered in that registry might also not be invoked. + +## Caching with FinalizationRegistry + +Returning to our *weak* cache example, we can notice the following: +- Even though the values wrapped in the `WeakRef` have been collected by the garbage collector, there is still an issue of "memory leakage" in the form of the remaining keys, whose values have been collected by the garbage collector. + +Here is an improved caching example using `FinalizationRegistry`: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { + const imgCache = new Map(); + + *!* + const registry = new FinalizationRegistry((imgName) => { // (1) + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); + }); + */!* + + return (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref()) { + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); + imgCache.set(imgName, new WeakRef(newImg)); + *!* + registry.register(newImg, imgName); // (2) + */!* + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +1. To manage the cleanup of "dead" cache entries, when the associated `WeakRef` objects are collected by the garbage collector, we create a `FinalizationRegistry` cleanup registry. + + The important point here is, that in the cleanup callback, it should be checked, if the entry was deleted by the garbage collector and not re-added, in order not to delete a "live" entry. +2. Once the new value (image) is downloaded and put into the cache, we register it in the finalizer registry to track the `WeakRef` object. + +This implementation contains only actual or "live" key/value pairs. +In this case, each `WeakRef` object is registered in the `FinalizationRegistry`. +And after the objects are cleaned up by the garbage collector, the cleanup callback will delete all `undefined` values. + +Here is a visual representation of the updated code: + +![](weakref-finalizationregistry-05.svg) + +A key aspect of the updated implementation is that finalizers allow parallel processes to be created between the "main" program and cleanup callbacks. +In the context of JavaScript, the "main" program - is our JavaScript-code, that runs and executes in our application or web page. + +Hence, from the moment an object is marked for deletion by the garbage collector, and to the actual execution of the cleanup callback, there may be a certain time gap. +It is important to understand that during this time gap, the main program can make any changes to the object or even bring it back to memory. + +That's why, in the cleanup callback, we must check to see if an entry has been added back to the cache by the main program to avoid deleting "live" entries. +Similarly, when searching for a key in the cache, there is a chance that the value has been deleted by the garbage collector, but the cleanup callback has not been executed yet. + +Such situations require special attention if you are working with `FinalizationRegistry`. + +## Using WeakRef and FinalizationRegistry in practice + +Moving from theory to practice, imagine a real-life scenario, where a user synchronizes their photos on a mobile device with some cloud service +(such as [iCloud](https://en.wikipedia.org/wiki/ICloud) or [Google Photos](https://en.wikipedia.org/wiki/Google_Photos)), +and wants to view them from other devices. In addition to the basic functionality of viewing photos, such services offer a lot of additional features, for example: + +- Photo editing and video effects. +- Creating "memories" and albums. +- Video montage from a series of photos. +- ...and much more. + +Here, as an example, we will use a fairly primitive implementation of such a service. +The main point - is to show a possible scenario of using `WeakRef` and `FinalizationRegistry` together in real life. + +Here is what it looks like: + +![](weakref-finalizationregistry-demo-01.png) + +
+On the left side, there is a cloud library of photos (they are displayed as thumbnails). +We can select the images we need and create a collage, by clicking the "Create collage" button on the right side of the page. +Then, the resulting collage can be downloaded as an image. +

+ +To increase page loading speed, it would be reasonable to download and display photo thumbnails in *compressed* quality. +But, to create a collage from selected photos, download and use them in *full-size* quality. + +Below, we can see, that the intrinsic size of the thumbnails is 240x240 pixels. +The size was chosen on purpose to increase loading speed. +Moreover, we do not need full-size photos in preview mode. + +![](weakref-finalizationregistry-demo-02.png) + +
+Let's assume, that we need to create a collage of 4 photos: we select them, and then click the "Create collage" button. +At this stage, the already known to us weakRefCache function checks whether the required image is in the cache. +If not, it downloads it from the cloud and puts it in the cache for further use. +This happens for each selected image: +

+ +![](weakref-finalizationregistry-demo-03.gif) + +
+ +Paying attention to the output in the console, you can see, which of the photos were downloaded from the cloud - this is indicated by FETCHED_IMAGE. +Since this is the first attempt to create a collage, this means, that at this stage the "weak cache" was still empty, and all the photos were downloaded from the cloud and put in it. + +But, along with the process of downloading images, there is also a process of memory cleanup by the garbage collector. +This means, that the object stored in the cache, which we refer to, using a weak reference, is deleted by the garbage collector. +And our finalizer executes successfully, thereby deleting the key, by which the image was stored in the cache. +CLEANED_IMAGE notifies us about it: + +![](weakref-finalizationregistry-demo-04.jpg) + +
+Next, we realize that we do not like the resulting collage, and decide to change one of the images and create a new one. +To do this, just deselect the unnecessary image, select another one, and click the "Create collage" button again: +

+ +![](weakref-finalizationregistry-demo-05.gif) + +
+But this time not all images were downloaded from the network, and one of them was taken from the weak cache: the CACHED_IMAGE message tells us about it. +This means that at the time of collage creation, the garbage collector had not yet deleted our image, and we boldly took it from the cache, +thereby reducing the number of network requests and speeding up the overall time of the collage creation process: +

+ +![](weakref-finalizationregistry-demo-06.jpg) + +
+Let's "play around" a little more, by replacing one of the images again and creating a new collage: +

+ +![](weakref-finalizationregistry-demo-07.gif) + +
+This time the result is even more impressive. Of the 4 images selected, 3 of them were taken from the weak cache, and only one had to be downloaded from the network. +The reduction in network load was about 75%. Impressive, isn't it? +

+ +![](weakref-finalizationregistry-demo-08.jpg) + +
+ +Of course, it is important to remember, that such behavior is not guaranteed, and depends on the specific implementation and operation of the garbage collector. + +Based on this, a completely logical question immediately arises: why do not we use an ordinary cache, where we can manage its entities ourselves, instead of relying on the garbage collector? +That's right, in the vast majority of cases there is no need to use `WeakRef` and `FinalizationRegistry`. + +Here, we simply demonstrated an alternative implementation of similar functionality, using a non-trivial approach with interesting language features. +Still, we cannot rely on this example, if we need a constant and predictable result. + +You can [open this example in the sandbox](sandbox:weakref-finalizationregistry). + +## Summary + +`WeakRef` - designed to create weak references to objects, allowing them to be deleted from memory by the garbage collector if there are no longer strong references to them. +This is beneficial for addressing excessive memory usage and optimizing the utilization of system resources in applications. + +`FinalizationRegistry` - is a tool for registering callbacks, that are executed when objects that are no longer strongly referenced, are destroyed. +This allows releasing resources associated with the object or performing other necessary operations before deleting the object from memory. \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png new file mode 100644 index 000000000..021637342 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/google-chrome-developer-tools.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css new file mode 100644 index 000000000..f6df812d0 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.css @@ -0,0 +1,49 @@ +.app { + display: flex; + flex-direction: column; + gap: 16px; +} + +.start-messages { + width: fit-content; +} + +.window { + width: 100%; + border: 2px solid #464154; + overflow: hidden; +} + +.window__header { + position: sticky; + padding: 8px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: #736e7e; +} + +.window__title { + margin: 0; + font-size: 24px; + font-weight: 700; + color: white; + letter-spacing: 1px; +} + +.window__button { + padding: 4px; + background: #4f495c; + outline: none; + border: 2px solid #464154; + color: white; + font-size: 16px; + cursor: pointer; +} + +.window__body { + height: 250px; + padding: 16px; + overflow: scroll; + background-color: #736e7e33; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html new file mode 100644 index 000000000..7f93af4c7 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.html @@ -0,0 +1,28 @@ + + + + + + + WeakRef DOM Logger + + + + +

+ +
+
+

Messages:

+ +
+
+ No messages. +
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js new file mode 100644 index 000000000..ea55b4478 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js @@ -0,0 +1,24 @@ +const startMessagesBtn = document.querySelector('.start-messages'); // (1) +const closeWindowBtn = document.querySelector('.window__button'); // (2) +const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) + +startMessagesBtn.addEventListener('click', () => { // (4) + startMessages(windowElementRef); + startMessagesBtn.disabled = true; +}); + +closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) + + +const startMessages = (element) => { + const timerId = setInterval(() => { // (6) + if (element.deref()) { // (7) + const payload = document.createElement("p"); + payload.textContent = `Message: System status OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("The element has been deleted."); // (9) + clearInterval(timerId); + } + }, 1000); +}; \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg new file mode 100644 index 000000000..2a507dbcd --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg @@ -0,0 +1,32 @@ + + + + + + + + user + + name: "John" + Object + + <global> + + + + + + + + + + + + + + + + admin + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg new file mode 100644 index 000000000..6cc199a12 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg @@ -0,0 +1,33 @@ + + + + + + + + + + <global> + + + name: "John" + Object + + + + + + + + + + + + admin + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg new file mode 100644 index 000000000..949a14f9f --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg new file mode 100644 index 000000000..1177d6580 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg @@ -0,0 +1,77 @@ + + + + + + + name: "John" + Object + + admin + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + + + + + + + + + + + + + + + WeakRef object + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg new file mode 100644 index 000000000..e738f8e7e --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + image-02.jpg + image-03.jpg + + key + value + image-01.jpg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + Deleted by FinalizationRegistry cleanup callback + + + + + + + + + + + + + + + WeakRef object + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png new file mode 100644 index 000000000..fc33a023a Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png new file mode 100644 index 000000000..7d8bb01e8 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif new file mode 100644 index 000000000..b81966dda Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg new file mode 100644 index 000000000..ba60f1e86 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif new file mode 100644 index 000000000..d34bda4d7 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg new file mode 100644 index 000000000..b2655540f Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif new file mode 100644 index 000000000..51f874518 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg new file mode 100644 index 000000000..5f98aec14 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css new file mode 100644 index 000000000..e6c9e3960 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css @@ -0,0 +1,285 @@ +:root { + --mineralGreen: 60, 98, 85; + --viridianGreen: 97, 135, 110; + --swampGreen: 166, 187, 141; + --fallGreen: 234, 231, 177; + --brinkPink: #FA7070; + --silverChalice: 178, 178, 178; + --white: 255, 255, 255; + --black: 0, 0, 0; + + --topBarHeight: 64px; + --itemPadding: 32px; + --containerGap: 8px; +} + +@keyframes zoom-in { + 0% { + transform: scale(1, 1); + } + + 100% { + transform: scale(1.30, 1.30); + } +} + +body, html { + margin: 0; + padding: 0; +} + +.app { + min-height: 100vh; + background-color: rgba(var(--viridianGreen), 0.5); +} + +.header { + height: var(--topBarHeight); + padding: 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(var(--mineralGreen), 1); +} + +.header-text { + color: white; +} + +.container { + display: flex; + gap: 24px; + padding: var(--itemPadding); +} + +.item { + width: 50%; +} + +.item--scrollable { + overflow-y: scroll; + height: calc(100vh - var(--topBarHeight) - (var(--itemPadding) * 2)); +} + +.thumbnails-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + align-items: center; +} + +.thumbnail-item { + width: calc(25% - var(--containerGap)); + cursor: pointer; + position: relative; +} + +.thumbnail-item:hover { + z-index: 1; + animation: zoom-in 0.1s forwards; +} + +.thumbnail-item--selected { + outline: 3px solid rgba(var(--fallGreen), 1); + outline-offset: -3px; +} + +.badge { + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + padding: 4px; + position: absolute; + right: 8px; + bottom: 8px; + border-radius: 50%; + border: 2px solid rgba(var(--fallGreen), 1); + background-color: rgba(var(--swampGreen), 1); +} + +.check { + display: inline-block; + transform: rotate(45deg); + border-bottom: 2px solid white; + border-right: 2px solid white; + width: 6px; + height: 12px; +} + +.img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + padding: 0 0 16px 0; + gap: 8px; +} + +.select { + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--swampGreen), 0.5); + background-color: rgba(var(--swampGreen), 1); +} + +.select:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.btn { + outline: none; + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--black), 0.5); +} + +.btn--primary { + background-color: rgba(var(--mineralGreen), 1); +} + +.btn--primary:hover:not([disabled]) { + background-color: rgba(var(--mineralGreen), 0.85); +} + +.btn--secondary { + background-color: rgba(var(--viridianGreen), 0.5); +} + +.btn--secondary:hover:not([disabled]) { + background-color: rgba(var(--swampGreen), 0.25); +} + +.btn--success { + background-color: rgba(var(--fallGreen), 1); +} + +.btn--success:hover:not([disabled]) { + background-color: rgba(var(--fallGreen), 0.85); +} + +.btn:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.previewContainer { + margin-bottom: 16px; + display: flex; + width: 100%; + height: 40vh; + overflow: scroll; + border: 3px solid rgba(var(--black), 1); +} + +.previewContainer--disabled { + background-color: rgba(var(--black), 0.1); + cursor: not-allowed; +} + +.canvas { + margin: auto; + display: none; +} + +.canvas--ready { + display: block; +} + +.spinnerContainer { + display: flex; + gap: 8px; + flex-direction: column; + align-content: center; + align-items: center; + margin: auto; +} + +.spinnerContainer--hidden { + display: none; +} + +.spinnerText { + margin: 0; + color: rgba(var(--mineralGreen), 1); +} + +.spinner { + display: inline-block; + width: 50px; + height: 50px; + margin: auto; + border: 3px solid rgba(var(--mineralGreen), 0.3); + border-radius: 50%; + border-top-color: rgba(var(--mineralGreen), 0.9); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loggerContainer { + display: flex; + flex-direction: column; + gap: 8px; + padding: 0 8px 8px 8px; + width: 100%; + min-height: 30vh; + max-height: 30vh; + overflow: scroll; + border-left: 3px solid rgba(var(--black), 0.25); +} + +.logger-title { + display: flex; + align-items: center; + padding: 8px; + position: sticky; + height: 40px; + min-height: 40px; + top: 0; + left: 0; + background-color: rgba(var(--viridianGreen), 1); + font-size: 24px; + font-weight: 700; + margin: 0; +} + +.logger-item { + font-size: 14px; + padding: 8px; + border: 2px solid #5a5a5a; + color: white; +} + +.logger--primary { + background-color: #13315a; +} + +.logger--success { + background-color: #385a4e; +} + +.logger--error { + background-color: #5a1a24; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html new file mode 100644 index 000000000..7ce52f927 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html @@ -0,0 +1,49 @@ + + + + + + + Photo Library Collage + + + + +
+
+

+ Photo Library Collage +

+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+

+
+ +
+
+

Logger:

+
+
+
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js new file mode 100644 index 000000000..983b34d9a --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js @@ -0,0 +1,228 @@ +import { + createImageFile, + loadImage, + weakRefCache, + LAYOUTS, + images, + THUMBNAIL_PARAMS, + stateObj, +} from "./utils.js"; + +export const state = new Proxy(stateObj, { + set(target, property, value) { + const previousValue = target[property]; + + target[property] = value; + + if (previousValue !== value) { + handleStateChange(target); + } + + return true; + }, +}); + +// Elements. +const thumbnailsContainerEl = document.querySelector(".thumbnails-container"); +const selectEl = document.querySelector(".select"); +const previewContainerEl = document.querySelector(".previewContainer"); +const canvasEl = document.querySelector(".canvas"); +const createCollageBtn = document.querySelector(".btn-create-collage"); +const startOverBtn = document.querySelector(".btn-start-over"); +const downloadBtn = document.querySelector(".btn-download"); +const spinnerContainerEl = document.querySelector(".spinnerContainer"); +const spinnerTextEl = document.querySelector(".spinnerText"); +const loggerContainerEl = document.querySelector(".loggerContainer"); + +// Renders. +// Render thumbnails previews. +images.forEach((img) => { + const thumbnail = document.createElement("div"); + thumbnail.classList.add("thumbnail-item"); + + thumbnail.innerHTML = ` + + `; + + thumbnail.addEventListener("click", (e) => handleSelection(e, img)); + + thumbnailsContainerEl.appendChild(thumbnail); +}); +// Render layouts select. +LAYOUTS.forEach((layout) => { + const option = document.createElement("option"); + option.value = JSON.stringify(layout); + option.innerHTML = layout.name; + selectEl.appendChild(option); +}); + +const handleStateChange = (state) => { + if (state.loading) { + selectEl.disabled = true; + createCollageBtn.disabled = true; + startOverBtn.disabled = true; + downloadBtn.disabled = true; + previewContainerEl.classList.add("previewContainer--disabled"); + spinnerContainerEl.classList.remove("spinnerContainer--hidden"); + spinnerTextEl.innerText = "Loading..."; + canvasEl.classList.remove("canvas--ready"); + } else if (!state.loading) { + selectEl.disabled = false; + createCollageBtn.disabled = false; + startOverBtn.disabled = false; + downloadBtn.disabled = false; + previewContainerEl.classList.remove("previewContainer--disabled"); + spinnerContainerEl.classList.add("spinnerContainer--hidden"); + canvasEl.classList.add("canvas--ready"); + } + + if (!state.selectedImages.size) { + createCollageBtn.disabled = true; + document.querySelectorAll(".badge").forEach((item) => item.remove()); + } else if (state.selectedImages.size && !state.loading) { + createCollageBtn.disabled = false; + } + + if (!state.collageRendered) { + downloadBtn.disabled = true; + } else if (state.collageRendered) { + downloadBtn.disabled = false; + } +}; +handleStateChange(state); + +const handleSelection = (e, imgName) => { + const imgEl = e.currentTarget; + + imgEl.classList.toggle("thumbnail-item--selected"); + + if (state.selectedImages.has(imgName)) { + state.selectedImages.delete(imgName); + state.selectedImages = new Set(state.selectedImages); + imgEl.querySelector(".badge")?.remove(); + } else { + state.selectedImages = new Set(state.selectedImages.add(imgName)); + + const badge = document.createElement("div"); + badge.classList.add("badge"); + badge.innerHTML = ` +
+ `; + imgEl.prepend(badge); + } +}; + +// Make a wrapper function. +let getCachedImage; +(async () => { + getCachedImage = await weakRefCache(loadImage); +})(); + +const calculateGridRows = (blobsLength) => + Math.ceil(blobsLength / state.currentLayout.columns); + +const drawCollage = (images) => { + state.drawing = true; + + let context = canvasEl.getContext("2d"); + + /** + * Calculate canvas dimensions based on the current layout. + * */ + context.canvas.width = + state.currentLayout.itemWidth * state.currentLayout.columns; + context.canvas.height = + calculateGridRows(images.length) * state.currentLayout.itemHeight; + + let currentRow = 0; + let currentCanvasDx = 0; + let currentCanvasDy = 0; + + for (let i = 0; i < images.length; i++) { + /** + * Get current row of the collage. + * */ + if (i % state.currentLayout.columns === 0) { + currentRow += 1; + currentCanvasDx = 0; + + if (currentRow > 1) { + currentCanvasDy += state.currentLayout.itemHeight; + } + } + + context.drawImage( + images[i], + 0, + 0, + images[i].width, + images[i].height, + currentCanvasDx, + currentCanvasDy, + state.currentLayout.itemWidth, + state.currentLayout.itemHeight, + ); + + currentCanvasDx += state.currentLayout.itemWidth; + } + + state.drawing = false; + state.collageRendered = true; +}; + +const createCollage = async () => { + state.loading = true; + + const images = []; + + for (const image of state.selectedImages.values()) { + const blobImage = await getCachedImage(image.img); + + const url = URL.createObjectURL(blobImage); + const img = await createImageFile(url); + + images.push(img); + URL.revokeObjectURL(url); + } + + state.loading = false; + + drawCollage(images); +}; + +/** + * Clear all settled data to start over. + * */ +const startOver = () => { + state.selectedImages = new Set(); + state.collageRendered = false; + const context = canvasEl.getContext("2d"); + context.clearRect(0, 0, canvasEl.width, canvasEl.height); + + document + .querySelectorAll(".thumbnail-item--selected") + .forEach((item) => item.classList.remove("thumbnail-item--selected")); + + loggerContainerEl.innerHTML = '

Logger:

'; +}; + +const downloadCollage = () => { + const date = new Date(); + const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`; + const img = canvasEl.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = fileName; + link.href = img; + link.click(); + link.remove(); +}; + +const changeLayout = ({ target }) => { + state.currentLayout = JSON.parse(target.value); +}; + +// Listeners. +selectEl.addEventListener("change", changeLayout); +createCollageBtn.addEventListener("click", createCollage); +startOverBtn.addEventListener("click", startOver); +downloadBtn.addEventListener("click", downloadCollage); diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 000000000..f0140c116 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js @@ -0,0 +1,321 @@ +const loggerContainerEl = document.querySelector(".loggerContainer"); + +export const images = [ + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + }, + { + img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60", + }, + { + img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552", + }, + { + img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b", + }, + { + img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470", + }, + { + img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318", + }, + { + img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9", + }, + { + img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3", + }, + { + img: "https://images.unsplash.com/photo-1533105079780-92b9be482077", + }, + { + img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963", + }, + { + img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7", + }, + { + img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007", + }, + { + img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd", + }, + { + img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd", + }, + { + img: "https://images.unsplash.com/photo-1518684079-3c830dcef090", + }, + { + img: "https://images.unsplash.com/photo-1505832018823-50331d70d237", + }, + { + img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1", + }, + { + img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17", + }, + { + img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af", + }, + { + img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a", + }, + { + img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1", + }, + { + img: "https://images.unsplash.com/photo-1499363536502-87642509e31b", + }, + { + img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7", + }, + { + img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5", + }, + { + img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48", + }, + { + img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6", + }, + { + img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f", + }, + { + img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053", + }, + { + img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc", + }, + { + img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d", + }, + { + img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6", + }, + { + img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169", + }, + { + img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96", + }, + { + img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f", + }, + { + img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3", + }, + { + img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af", + }, + { + img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226", + }, + { + img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45", + }, + { + img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83", + }, + { + img: "https://images.unsplash.com/photo-1545389336-cf090694435e", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4", + }, + { + img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a", + }, + { + img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40", + }, + { + img: "https://images.unsplash.com/photo-1506125840744-167167210587", + }, + { + img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b", + }, + { + img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535", + }, + { + img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7", + }, + { + img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c", + }, + { + img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7", + }, + { + img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59", + }, + { + img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f", + }, + { + img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc", + }, + { + img: "https://images.unsplash.com/photo-1510662145379-13537db782dc", + }, + { + img: "https://images.unsplash.com/photo-1573790387438-4da905039392", + }, + { + img: "https://images.unsplash.com/photo-1512757776214-26d36777b513", + }, + { + img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b", + }, + { + img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7", + }, + { + img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a", + }, +]; +export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format"; + +// Console styles. +export const CONSOLE_BASE_STYLES = [ + "font-size: 12px", + "padding: 4px", + "border: 2px solid #5a5a5a", + "color: white", +].join(";"); +export const CONSOLE_PRIMARY = [ + CONSOLE_BASE_STYLES, + "background-color: #13315a", +].join(";"); +export const CONSOLE_SUCCESS = [ + CONSOLE_BASE_STYLES, + "background-color: #385a4e", +].join(";"); +export const CONSOLE_ERROR = [ + CONSOLE_BASE_STYLES, + "background-color: #5a1a24", +].join(";"); + +// Layouts. +export const LAYOUT_4_COLUMNS = { + name: "Layout 4 columns", + columns: 4, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUT_8_COLUMNS = { + name: "Layout 8 columns", + columns: 8, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS]; + +export const createImageFile = async (src) => + new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to construct image.")); + }); + +export const loadImage = async (url) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(String(response.status)); + } + + return await response.blob(); + } catch (e) { + console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR); + } +}; + +export const weakRefCache = (fetchImg) => { + const imgCache = new Map(); + const registry = new FinalizationRegistry(({ imgName, size, type }) => { + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) { + imgCache.delete(imgName); + console.log( + `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`, + CONSOLE_ERROR, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--error"); + logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + } + }); + + return async (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref() !== undefined) { + console.log( + `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`, + CONSOLE_SUCCESS, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--success"); + logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + return cachedImg?.deref(); + } + + const newImg = await fetchImg(imgName); + console.log( + `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`, + CONSOLE_PRIMARY, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--primary"); + logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + imgCache.set(imgName, new WeakRef(newImg)); + registry.register(newImg, { + imgName, + size: newImg.size, + type: newImg.type, + }); + + return newImg; + }; +}; + +export const stateObj = { + loading: false, + drawing: true, + collageRendered: false, + currentLayout: LAYOUTS[0], + selectedImages: new Set(), +}; diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index f680554dd..eedc28fb3 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -1,12 +1,12 @@ # Browser environment, specs -The JavaScript language was initially created for web browsers. Since then it has evolved and become a language with many uses and platforms. +The JavaScript language was initially created for web browsers. Since then, it has evolved into a language with many uses and platforms. -A platform may be a browser, or a web-server or another *host*, even a coffee machine. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*. +A platform may be a browser, or a web-server or another *host*, or even a "smart" coffee machine if it can run JavaScript. Each of these provides platform-specific functionality. The JavaScript specification calls that a *host environment*. -A host environment provides own objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. +A host environment provides its own objects and functions in addition to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. -Here's a bird's-eye view of what we have when JavaScript runs in a web-browser: +Here's a bird's-eye view of what we have when JavaScript runs in a web browser: ![](windowObjects.svg) @@ -15,9 +15,9 @@ There's a "root" object called `window`. It has two roles: 1. First, it is a global object for JavaScript code, as described in the chapter . 2. Second, it represents the "browser window" and provides methods to control it. -For instance, here we use it as a global object: +For instance, we can use it as a global object: -```js run +```js run global function sayHi() { alert("Hello"); } @@ -26,17 +26,17 @@ function sayHi() { window.sayHi(); ``` -And here we use it as a browser window, to see the window height: +And we can use it as a browser window, to show the window height: ```js run alert(window.innerHeight); // inner window height ``` -There are more window-specific methods and properties, we'll cover them later. +There are more window-specific methods and properties, which we'll cover later. ## DOM (Document Object Model) -Document Object Model, or DOM for short, represents all page content as objects that can be modified. +The Document Object Model, or DOM for short, represents all page content as objects that can be modified. The `document` object is the main "entry point" to the page. We can change or create anything on the page using it. @@ -49,20 +49,18 @@ document.body.style.background = "red"; setTimeout(() => document.body.style.background = "", 1000); ``` -Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: - -- **DOM Living Standard** at +Here, we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org). ```smart header="DOM is not only for browsers" The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use DOM too. -For instance, server-side scripts that download HTML pages and process them can also use DOM. They may support only a part of the specification though. +For instance, server-side scripts that download HTML pages and process them can also use the DOM. They may support only a part of the specification though. ``` ```smart header="CSSOM for styling" -CSS rules and stylesheets are structured in a different way than HTML. There's a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/), that explains how they are represented as objects, and how to read and write them. +There's also a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/) for CSS rules and stylesheets, that explains how they are represented as objects, and how to read and write them. -CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because usually CSS rules are static. We rarely need to add/remove CSS rules from JavaScript, but that's also possible. +The CSSOM is used together with the DOM when we modify style rules for the document. In practice though, the CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible. ``` ## BOM (Browser Object Model) @@ -71,7 +69,7 @@ The Browser Object Model (BOM) represents additional objects provided by the bro For instance: -- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc). +- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differentiate between Windows/Linux/Mac etc). - The [location](mdn:api/Window/location) object allows us to read the current URL and can redirect the browser to a new one. Here's how we can use the `location` object: @@ -83,12 +81,12 @@ if (confirm("Go to Wikipedia?")) { } ``` -Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user. +The functions `alert/confirm/prompt` are also a part of the BOM: they are not directly related to the document, but represent pure browser methods for communicating with the user. ```smart header="Specifications" -BOM is the part of the general [HTML specification](https://html.spec.whatwg.org). +The BOM is a part of the general [HTML specification](https://html.spec.whatwg.org). -Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . +Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods, and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . ``` ## Summary @@ -96,20 +94,20 @@ Yes, you heard that right. The HTML spec at is no Talking about standards, we have: DOM specification -: Describes the document structure, manipulations and events, see . +: Describes the document structure, manipulations, and events, see . CSSOM specification -: Describes stylesheets and style rules, manipulations with them and their binding to documents, see . +: Describes stylesheets and style rules, manipulations with them, and their binding to documents, see . HTML specification : Describes the HTML language (e.g. tags) and also the BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see . It takes the DOM specification and extends it with many additional properties and methods. Additionally, some classes are described separately at . -Please note these links, as there's so much stuff to learn it's impossible to cover and remember everything. +Please note these links, as there's so much to learn that it's impossible to cover everything and remember it all. -When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. +When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. To find something, it's often convenient to use an internet search "WHATWG [term]" or "MDN [term]", e.g , . -Now we'll get down to learning DOM, because the document plays the central role in the UI. +Now, we'll get down to learning the DOM, because the document plays the central role in the UI. diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 019398be9..f7f2be91d 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -51,7 +51,7 @@ The DOM represents HTML as a tree structure of tags. Here's how it looks:
@@ -143,7 +143,7 @@ drawHtmlTree(node4, 'div.domtree', 690, 360); ````warn header="Tables always have ``" -An interesting "special case" is tables. By the DOM specification they must have ``, but HTML text may (officially) omit it. Then the browser creates `` in the DOM automatically. +An interesting "special case" is tables. By DOM specification they must have `` tag, but HTML text may omit it. Then the browser creates `` in the DOM automatically. For the HTML: @@ -160,7 +160,7 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType": drawHtmlTree(node5, 'div.domtree', 600, 200); -You see? The `` appeared out of nowhere. You should keep this in mind while working with tables to avoid surprises. +You see? The `` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises. ```` ## Other node types @@ -188,7 +188,7 @@ For example, comments:
@@ -199,7 +199,7 @@ We may think -- why is a comment added to the DOM? It doesn't affect the visual **Everything in HTML, even comments, becomes a part of the DOM.** -Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. We are not going to touch that node, we even don't draw it on diagrams for that reason, but it's there. +Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there. The `document` object that represents the whole document is, formally, a DOM node as well. @@ -212,7 +212,7 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu ## See it for yourself -To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index f7123d70d..b5f03098c 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -214,7 +214,7 @@ alert( document.body.previousSibling ); // HTMLHeadElement ## Element-only navigation -Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist. +Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist. But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page. diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index f5ab0b785..405129694 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -55,7 +55,7 @@ Also, there's a global variable named by `id` that references the element: ``` ```warn header="Please don't use id-named global variables to access elements" -This behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), so it's kind of standard. But it is supported mainly for compatibility. +This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from. @@ -71,7 +71,7 @@ If there are multiple elements with the same `id`, then the behavior of methods ``` ```warn header="Only `document.getElementById`, not `anyElem.getElementById`" -The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document. +The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document. ``` ## querySelectorAll [#querySelectorAll] @@ -116,7 +116,7 @@ In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but t Previous methods were searching the DOM. -The [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. +The [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. The method comes in handy when we are iterating over elements (like in an array or something) and trying to filter out those that interest us. @@ -142,7 +142,7 @@ For instance: *Ancestors* of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top. -The method `elem.closest(css)` looks the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. +The method `elem.closest(css)` looks for the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. In other words, the method `closest` goes up from the element and checks each of parents. If it matches the selector, then the search stops, and the ancestor is returned. @@ -154,7 +154,7 @@ For instance:
  • Chapter 1
  • -
  • Chapter 1
  • +
  • Chapter 2
@@ -363,7 +363,7 @@ There are 6 main methods to search for nodes in DOM: -By far the most used are `querySelector` and `querySelectorAll`, but `getElementBy*` can be sporadically helpful or found in the old scripts. +By far the most used are `querySelector` and `querySelectorAll`, but `getElement(s)By*` can be sporadically helpful or found in the old scripts. Besides that: diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index 76469c187..7a31f62d4 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -10,7 +10,7 @@ Different DOM nodes may have different properties. For instance, an element node Each DOM node belongs to the corresponding built-in class. -The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. +The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](https://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. Here's the picture, explanations to follow: @@ -18,16 +18,46 @@ Here's the picture, explanations to follow: The classes are: +<<<<<<< HEAD - [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. - [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes. - [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. A browser supports not only HTML, but also XML and SVG. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`. - [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by concrete HTML elements: +======= +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class for everything. + + Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. + +- [Node](https://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. + + It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are other classes that inherit from it (and so inherit the `Node` functionality). + +- [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole. + + The `document` global object belongs exactly to this class. It serves as an entry point to the DOM. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by: + - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `

Hello

`. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- the class for comments. They are not shown, but each comment becomes a member of DOM. + +- [Element](https://dom.spec.whatwg.org/#interface-element) -- is the base class for DOM elements. + + It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. + + A browser supports not only HTML, but also XML and SVG. So the `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` (we don't need them here) and `HTMLElement`. + +- Finally, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is the basic class for all HTML elements. We'll work with it most of the time. + + It is inherited by concrete HTML elements: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `` elements, - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `` elements, - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `` elements, - - ...and so on, each tag has its own class that may provide specific properties and methods. + - ...and so on. + +There are many other tags with their own classes that may have specific properties and methods, while some elements, such as ``, `
`, `
` do not have any specific properties, so they are instances of `HTMLElement` class. -So, the full set of properties and methods of a given node comes as the result of the inheritance. +So, the full set of properties and methods of a given node comes as the result of the chain of inheritance. For example, let's consider the DOM object for an `` element. It belongs to [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class. @@ -128,13 +158,13 @@ For instance: ```html run - ``` -But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back: +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: ```html run @@ -298,7 +298,7 @@ For instance, here for the order state the attribute `order-state` is used:
``` -Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`? +Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `.order-state-canceled`? Because an attribute is more convenient to manage. The state can be changed as easy as: diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md index e127bc0ef..40c75dff3 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md @@ -6,7 +6,7 @@ importance: 5 We have an empty DOM element `elem` and a string `text`. -Which of these 3 commands do exactly the same? +Which of these 3 commands will do exactly the same? 1. `elem.append(document.createTextNode(text))` 2. `elem.innerHTML = text` diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md index 15238fcf4..1414e90c1 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md @@ -39,15 +39,19 @@ The clock-managing functions: ```js let timerId; -function clockStart() { // run the clock - timerId = setInterval(update, 1000); +function clockStart() { // run the clock + if (!timerId) { // only set a new interval if the clock is not running + timerId = setInterval(update, 1000); + } update(); // (*) } function clockStop() { clearInterval(timerId); - timerId = null; + timerId = null; // (**) } ``` Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then. + +Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`. diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html index 1bf642b10..84ee26f19 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html @@ -43,15 +43,19 @@ } function clockStart() { - timerId = setInterval(update, 1000); + // set a new interval only if the clock is stopped + // otherwise we would rewrite the timerID reference to the running interval and wouldn't be able to stop the clock ever again + if (!timerId) { + timerId = setInterval(update, 1000); + } update(); // <-- start right now, don't wait 1 second till the first setInterval works } function clockStop() { clearInterval(timerId); + timerId = null; // <-- clear timerID to indicate that the clock has been stopped, so that it is possible to start it again in clockStart() } - clockStart(); diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 6b85168b9..3d1f6698f 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,9 +1,9 @@ The HTML in the task is incorrect. That's the reason of the odd thing. -The browser has to fix it automatically. But there may be no text inside the ``: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `
`. +The browser has to fix it automatically. But there may be no text inside the `
`: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
`. Now it's obvious that when we remove the table, it remains. -The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the `
`. +The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
`. The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct. diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md index f87074dba..861f70503 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md @@ -22,6 +22,6 @@ Why does that happen? alert(table); // the table, as it should be table.remove(); - // why there's still aaa in the document? + // why there's still "aaa" in the document? ``` diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md index 43b0a34a7..a57e7e2d9 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/task.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md @@ -10,7 +10,7 @@ For every list item: 1. Ask a user about its content using `prompt`. 2. Create the `
  • ` with it and add it to `
      `. -3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt). +3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry). All elements should be created dynamically. diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index c4796a1d4..75ce1fbb0 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -28,7 +28,7 @@ Here's how it will look: */!* ``` -That was an HTML example. Now let's create the same `div` with JavaScript (assuming that the styles are in the HTML or an external CSS file). +That was the HTML example. Now let's create the same `div` with JavaScript (assuming that the styles are in the HTML/CSS already). ## Creating an element @@ -48,21 +48,28 @@ To create DOM nodes, there are two methods: let textNode = document.createTextNode('Here I am'); ``` +Most of the time we need to create element nodes, such as the `div` for the message. + ### Creating the message -In our case the message is a `div` with `alert` class and the HTML in it: +Creating the message div takes 3 steps: ```js +// 1. Create
      element let div = document.createElement('div'); + +// 2. Set its class to "alert" div.className = "alert"; + +// 3. Fill it with the content div.innerHTML = "Hi there! You've read an important message."; ``` -We created the element, but as of now it's only in a variable. We can't see the element on the page, as it's not yet a part of the document. +We've created the element. But as of now it's only in a variable named `div`, not in the page yet. So we can't see it. ## Insertion methods -To make the `div` show up, we need to insert it somewhere into `document`. For instance, in `document.body`. +To make the `div` show up, we need to insert it somewhere into `document`. For instance, into `` element, referenced by `document.body`. There's a special method `append` for that: `document.body.append(div)`. @@ -90,14 +97,20 @@ Here's the full code: ``` -This set of methods provides more ways to insert: +Here we called `append` on `document.body`, but we can call `append` method on any other element, to put another element into it. For instance, we can append something to `
      ` by calling `div.append(anotherElement)`. -- `node.append(...nodes or strings)` -- append nodes or strings at the end of `node`, -- `node.prepend(...nodes or strings)` -- insert nodes or strings at the beginning of `node`, -- `node.before(...nodes or strings)` –- insert nodes or strings before `node`, -- `node.after(...nodes or strings)` –- insert nodes or strings after `node`, +Here are more insertion methods, they specify different places where to insert: + +- `node.append(...nodes or strings)` -- append nodes or strings *at the end* of `node`, +- `node.prepend(...nodes or strings)` -- insert nodes or strings *at the beginning* of `node`, +- `node.before(...nodes or strings)` –- insert nodes or strings *before* `node`, +- `node.after(...nodes or strings)` –- insert nodes or strings *after* `node`, - `node.replaceWith(...nodes or strings)` –- replaces `node` with the given nodes or strings. +Arguments of these methods are an arbitrary list of DOM nodes to insert, or text strings (that become text nodes automatically). + +Let's see them in action. + Here's an example of using these methods to add items to a list and the text before/after it: ```html autorun @@ -121,7 +134,7 @@ Here's an example of using these methods to add items to a list and the text bef ``` -Here's a visual picture what methods do: +Here's a visual picture of what the methods do: ![](before-prepend-append-after.svg) @@ -139,7 +152,7 @@ before after ``` -These methods can insert multiple lists of nodes and text pieces in a single call. +As said, these methods can insert multiple nodes and text pieces in a single call. For instance, here a string and an element are inserted: @@ -150,7 +163,7 @@ For instance, here a string and an element are inserted: ``` -All text is inserted *as text*. +Please note: the text is inserted "as text", not "as HTML", with proper escaping of characters such as `<`, `>`. So the final HTML is: @@ -166,7 +179,7 @@ In other words, strings are inserted in a safe way, like `elem.textContent` does So, these methods can only be used to insert DOM nodes or text pieces. -But what if we want to insert HTML "as html", with all tags and stuff working, like `elem.innerHTML`? +But what if we'd like to insert an HTML string "as html", with all tags and stuff working, in the same manner as `elem.innerHTML` does it? ## insertAdjacentHTML/Text/Element diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index 9154d43d6..46aaa3b00 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -128,6 +128,14 @@ setTimeout(() => document.body.style.display = "", 1000); // back to normal If we set `style.display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style.display` property at all. +Also there is a special method for that, `elem.style.removeProperty('style property')`. So, We can remove a property like this: + +```js run +document.body.style.background = 'red'; //set background to red + +setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second +``` + ````smart header="Full rewrite with `style.cssText`" Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only. @@ -261,20 +269,6 @@ So nowadays `getComputedStyle` actually returns the resolved value of the proper We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed. For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. - -There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: - -```html run - - -``` ```` ```smart header="Styles applied to `:visited` links are hidden!" diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index 024e9a4d9..66f28115f 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -17,8 +17,8 @@ As a sample element to demonstrate properties we'll use the one given below: width: 300px; height: 200px; border: 25px solid #E8C48F; - padding: 20px; - overflow: auto; + padding: 20px; + overflow: auto; } ``` @@ -106,7 +106,7 @@ Geometry properties are calculated only for displayed elements. If an element (or any of its ancestors) has `display:none` or is not in the document, then all geometry properties are zero (or `null` for `offsetParent`). -For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or it's ancestor) has `display:none`. +For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or its ancestor) has `display:none`. We can use this to check if an element is hidden, like this: @@ -116,7 +116,7 @@ function isHidden(elem) { } ``` -Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes (like an empty `
      `). +Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes. ```` ## clientTop/Left @@ -211,7 +211,7 @@ If you click the element below, the code `elem.scrollTop += 10` executes. That m
      Click
      Me
      1
      2
      3
      4
      5
      6
      7
      8
      9
      ``` -Setting `scrollTop` to `0` or `Infinity` will make the element scroll to the very top/bottom respectively. +Setting `scrollTop` to `0` or a big value, such as `1e9` will make the element scroll to the very top/bottom respectively. ```` ## Don't take width/height from CSS diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index 10898dbf7..f2a863010 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -2,11 +2,11 @@ How do we find the width and height of the browser window? How do we get the full width and height of the document, including the scrolled out part? How do we scroll the page using JavaScript? -For most such requests, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities important enough to consider. +For this type of information, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities to consider. ## Width/height of the window -To get window width and height we can use `clientWidth/clientHeight` of `document.documentElement`: +To get window width and height, we can use the `clientWidth/clientHeight` of `document.documentElement`: ![](document-client-width-height.svg) @@ -16,12 +16,12 @@ For instance, this button shows the height of your window: ``` -````warn header="Not `window.innerWidth/Height`" -Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So why not to use them instead? +````warn header="Not `window.innerWidth/innerHeight`" +Browsers also support properties like `window.innerWidth/innerHeight`. They look like what we want, so why not to use them instead? -If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return width/height of the visible part of the document, available for the content. +If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return the width/height of the visible part of the document, available for the content. -...And `window.innerWidth/innerHeight` include the scrollbar. +`window.innerWidth/innerHeight` includes the scrollbar. If there's a scrollbar, and it occupies some space, then these two lines show different values: ```js run @@ -29,7 +29,7 @@ alert( window.innerWidth ); // full window width alert( document.documentElement.clientWidth ); // window width minus the scrollbar ``` -In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientHeight/Width`. +In most cases, we need the *available* window width in order to draw or position something within scrollbars (if there are any), so we should use `documentElement.clientHeight/clientWidth`. ```` ```warn header="`DOCTYPE` is important" @@ -40,9 +40,13 @@ In modern HTML we should always write `DOCTYPE`. ## Width/height of the document +<<<<<<< HEAD Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure document full size as `document.documentElement.scrollWidth/scrollHeight`. +======= +Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure the document's full size as `document.documentElement.scrollWidth/scrollHeight`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Sounds like a nonsense, weird, right? +But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera, if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Weird, right? To reliably obtain the full document height, we should take the maximum of these properties: @@ -60,11 +64,15 @@ Why so? Better don't ask. These inconsistencies come from ancient times, not a " ## Get the current scroll [#page-scroll] -DOM elements have their current scroll state in `elem.scrollLeft/scrollTop`. +DOM elements have their current scroll state in their `scrollLeft/scrollTop` properties. +<<<<<<< HEAD For document scroll `document.documentElement.scrollLeft/Top` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. +======= +For document scroll, `document.documentElement.scrollLeft/scrollTop` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties `window.pageXOffset/pageYOffset`: +Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties, `window.pageXOffset/pageYOffset`: ```js run alert('Current scroll from the top: ' + window.pageYOffset); @@ -73,19 +81,25 @@ alert('Current scroll from the left: ' + window.pageXOffset); These properties are read-only. +```smart header="Also available as `window` properties `scrollX` and `scrollY`" +For historical reasons, both properties exist, but they are the same: +- `window.pageXOffset` is an alias of `window.scrollX`. +- `window.pageYOffset` is an alias of `window.scrollY`. +``` + ## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] ```warn -To scroll the page from JavaScript, its DOM must be fully built. +To scroll the page with JavaScript, its DOM must be fully built. -For instance, if we try to scroll the page from the script in ``, it won't work. +For instance, if we try to scroll the page with a script in ``, it won't work. ``` Regular elements can be scrolled by changing `scrollTop/scrollLeft`. -We can do the same for the page using `document.documentElement.scrollTop/Left` (except Safari, where `document.body.scrollTop/Left` should be used instead). +We can do the same for the page using `document.documentElement.scrollTop/scrollLeft` (except Safari, where `document.body.scrollTop/Left` should be used instead). -Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). +Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). - The method `scrollBy(x,y)` scrolls the page *relative to its current position*. For instance, `scrollBy(0,10)` scrolls the page `10px` down. @@ -106,28 +120,28 @@ These methods work for all browsers the same way. ## scrollIntoView -For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). +For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument: -- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element is aligned with the window top. -- if `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element is aligned with the window bottom. +- If `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element will be aligned with the window top. +- If `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element will be aligned with the window bottom. ```online -The button below scrolls the page to make itself show at the window top: +The button below scrolls the page to position itself at the window top: -And this button scrolls the page to show it at the bottom: +And this button scrolls the page to position itself at the bottom: ``` ## Forbid the scrolling -Sometimes we need to make the document "unscrollable". For instance, when we need to cover it with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. +Sometimes we need to make the document "unscrollable". For instance, when we need to cover the page with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. -To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll. +To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will "freeze" at its current scroll position. ```online Try it: @@ -136,20 +150,20 @@ Try it: -The first button freezes the scroll, the second one resumes it. +The first button freezes the scroll, while the second one releases it. ``` -We can use the same technique to "freeze" the scroll for other elements, not just for `document.body`. +We can use the same technique to freeze the scroll for other elements, not just for `document.body`. -The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it. +The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free and the content "jumps" to fill it. -That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width the same. +That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze. If it increased (the scrollbar disappeared), then add `padding` to `document.body` in place of the scrollbar to keep the content width the same. ## Summary Geometry: -- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/Height` +- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/clientHeight` - Width/height of the whole document, with the scrolled out part: ```js diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index b4b577cd0..dc161c0f3 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -36,7 +36,7 @@ Additionally, there are derived properties: ```online For instance click this button to see its window coordinates: -

      +

      ``` +In the first example, the HTML attribute is used to initialize the `button.onclick`, while in the second example -- the script, that's all the difference. + **As there's only one `onclick` property, we can't assign more than one event handler.** In the example below adding a handler with JavaScript overwrites the existing handler: @@ -124,16 +124,6 @@ In the example below adding a handler with JavaScript overwrites the existing ha ``` -By the way, we can assign an existing function as a handler directly: - -```js -function sayThanks() { - alert('Thanks!'); -} - -elem.onclick = sayThanks; -``` - To remove a handler -- assign `elem.onclick = null`. ## Accessing the element: this @@ -150,7 +140,17 @@ In the code below `button` shows its contents using `this.innerHTML`: If you're starting to work with events -- please note some subtleties. -**The function should be assigned as `sayThanks`, not `sayThanks()`.** +We can set an existing function as a handler: + +```js +function sayThanks() { + alert('Thanks!'); +} + +elem.onclick = sayThanks; +``` + +But be careful: the function should be assigned as `sayThanks`, not `sayThanks()`. ```js // right @@ -160,7 +160,7 @@ button.onclick = sayThanks; button.onclick = sayThanks(); ``` -If we add parentheses, `sayThanks()` -- is a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work. +If we add parentheses, then `sayThanks()` becomes a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work. ...On the other hand, in the markup we do need the parentheses: @@ -168,21 +168,17 @@ If we add parentheses, `sayThanks()` -- is a function call. So the last line ac ``` -The difference is easy to explain. When the browser reads the attribute, it creates a handler function with *body from its content*: `sayThanks()`. +The difference is easy to explain. When the browser reads the attribute, it creates a handler function with body from the attribute content. So the markup generates this property: ```js button.onclick = function() { *!* - sayThanks(); // the attribute content + sayThanks(); // <-- the attribute content goes here */!* }; ``` -**Use functions, not strings.** - -The assignment `elem.onclick = "alert(1)"` would work too. It works for compatibility reasons, but is strongly not recommended. - **Don't use `setAttribute` for handlers.** Such a call won't work: @@ -199,9 +195,9 @@ Assign a handler to `elem.onclick`, not `elem.ONCLICK`, because DOM properties a ## addEventListener -The fundamental problem of the aforementioned ways to assign handlers -- we can't assign multiple handlers to one event. +The fundamental problem of the aforementioned ways to assign handlers is that we *can't assign multiple handlers to one event*. -For instance, one part of our code wants to highlight a button on click, and another one wants to show a message. +Let's say, one part of our code wants to highlight a button on click, and another one wants to show a message on the same click. We'd like to assign two event handlers for that. But a new DOM property will overwrite the existing one: @@ -211,7 +207,7 @@ input.onclick = function() { alert(1); } input.onclick = function() { alert(2); } // replaces the previous handler ``` -Web-standard developers understood that long ago and suggested an alternative way of managing handlers using special methods `addEventListener` and `removeEventListener`. They are free of such a problem. +Developers of web standards understood that long ago and suggested an alternative way of managing handlers using the special methods `addEventListener` and `removeEventListener` which aren't bound by such constraint. The syntax to add a handler: @@ -229,8 +225,7 @@ element.addEventListener(event, handler, [options]); : An additional optional object with properties: - `once`: if `true`, then the listener is automatically removed after it triggers. - `capture`: the phase where to handle the event, to be covered later in the chapter . For historical reasons, `options` can also be `false/true`, that's the same as `{capture: false/true}`. - - `passive`: if `true`, then the handler will not `preventDefault()`, we'll cover that later in . - + - `passive`: if `true`, then the handler will not call `preventDefault()`, we'll explain that later in . To remove the handler, use `removeEventListener`: @@ -241,7 +236,7 @@ element.removeEventListener(event, handler, [options]); ````warn header="Removal requires the same function" To remove a handler we should pass exactly the same function as was assigned. -That doesn't work: +This doesn't work: ```js no-beautify elem.addEventListener( "click" , () => alert('Thanks!')); @@ -249,7 +244,7 @@ elem.addEventListener( "click" , () => alert('Thanks!')); elem.removeEventListener( "click", () => alert('Thanks!')); ``` -The handler won't be removed, because `removeEventListener` gets another function -- with the same code, but that doesn't matter. +The handler won't be removed, because `removeEventListener` gets another function -- with the same code, but that doesn't matter, as it's a different function object. Here's the right way: @@ -266,7 +261,7 @@ input.removeEventListener("click", handler); Please note -- if we don't store the function in a variable, then we can't remove it. There's no way to "read back" handlers assigned by `addEventListener`. ```` -Multiple calls to `addEventListener` allow to add multiple handlers, like this: +Multiple calls to `addEventListener` allow it to add multiple handlers, like this: ```html run no-beautify @@ -291,19 +286,34 @@ Multiple calls to `addEventListener` allow to add multiple handlers, like this: As we can see in the example above, we can set handlers *both* using a DOM-property and `addEventListener`. But generally we use only one of these ways. ````warn header="For some events, handlers only work with `addEventListener`" -There exist events that can't be assigned via a DOM-property. Must use `addEventListener`. +There exist events that can't be assigned via a DOM-property. Only with `addEventListener`. +<<<<<<< HEAD For instance, the event `DOMContentLoaded`, that triggers when the document is loaded and DOM is built. ```js document.onDOMContentLoaded = function() { alert("DOM built"); // will never run +======= +For instance, the `DOMContentLoaded` event, that triggers when the document is loaded and the DOM has been built. + +```js +// will never run +document.onDOMContentLoaded = function() { + alert("DOM built"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }; ``` ```js +<<<<<<< HEAD document.addEventListener("DOMContentLoaded", function() { alert("DOM built"); // this way it works +======= +// this way it works +document.addEventListener("DOMContentLoaded", function() { + alert("DOM built"); +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }); ``` So `addEventListener` is more universal. Although, such events are an exception rather than the rule. @@ -311,11 +321,11 @@ So `addEventListener` is more universal. Although, such events are an exception ## Event object -To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keypress", but what were the pointer coordinates? Which key was pressed? And so on. +To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keydown", but what were the pointer coordinates? Which key was pressed? And so on. When an event happens, the browser creates an *event object*, puts details into it and passes it as an argument to the handler. -Here's an example of getting mouse coordinates from the event object: +Here's an example of getting pointer coordinates from the event object: ```html run @@ -337,12 +347,12 @@ Some properties of `event` object: `event.currentTarget` : Element that handled the event. That's exactly the same as `this`, unless the handler is an arrow function, or its `this` is bound to something else, then we can get the element from `event.currentTarget`. -`event.clientX / event.clientY` -: Window-relative coordinates of the cursor, for mouse events. +`event.clientX` / `event.clientY` +: Window-relative coordinates of the cursor, for pointer events. -There are more properties. They depend on the event type, so we'll study them later when we come to different events in details. +There are more properties. Many of them depend on the event type: keyboard events have one set of properties, pointer events - another one, we'll study them later when as we move on to the details of different events. -````smart header="The event object is also accessible from HTML" +````smart header="The event object is also available in HTML handlers" If we assign a handler in HTML, we can also use the `event` object, like this: ```html autorun height=60 @@ -364,17 +374,19 @@ For instance: ``` -As we can see, when `addEventListener` receives an object as the handler, it calls `object.handleEvent(event)` in case of an event. +As we can see, when `addEventListener` receives an object as the handler, it calls `obj.handleEvent(event)` in case of an event. -We could also use a class for that: +We could also use objects of a custom class, like this: ```html run @@ -396,6 +408,7 @@ We could also use a class for that: *!* let menu = new Menu(); + elem.addEventListener('mousedown', menu); elem.addEventListener('mouseup', menu); */!* diff --git a/2-ui/2-events/02-bubbling-and-capturing/article.md b/2-ui/2-events/02-bubbling-and-capturing/article.md index ac0186f42..2448cfa5b 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -120,26 +120,27 @@ There's usually no real need to prevent the bubbling. A task that seemingly requ There's another phase of event processing called "capturing". It is rarely used in real code, but sometimes can be useful. -The standard [DOM Events](http://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation: +The standard [DOM Events](https://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation: 1. Capturing phase -- the event goes down to the element. 2. Target phase -- the event reached the target element. 3. Bubbling phase -- the event bubbles up from the element. -Here's the picture of a click on `
  • ` inside a table, taken from the specification: +Here's the picture, taken from the specification, of the capturing `(1)`, target `(2)` and bubbling `(3)` phases for a click event on a `` inside a table: ![](eventflow.svg) That is: for a click on `` the event first goes through the ancestors chain down to the element (capturing phase), then it reaches the target and triggers there (target phase), and then it goes up (bubbling phase), calling handlers on its way. -**Before we only talked about bubbling, because the capturing phase is rarely used. Normally it is invisible to us.** +Until now, we only talked about bubbling, because the capturing phase is rarely used. -Handlers added using `on`-property or using HTML attributes or using two-argument `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. +In fact, the capturing phase was invisible for us, because handlers added using `on`-property or using HTML attributes or using two-argument `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. To catch an event on the capturing phase, we need to set the handler `capture` option to `true`: ```js elem.addEventListener(..., {capture: true}) + // or, just "true" is an alias to {capture: true} elem.addEventListener(..., true) ``` @@ -180,9 +181,10 @@ The code sets click handlers on *every* element in the document to see which one If you click on `

    `, then the sequence is: -1. `HTML` -> `BODY` -> `FORM` -> `DIV` (capturing phase, the first listener): -2. `P` (target phrase, triggers two times, as we've set two listeners: capturing and bubbling) -3. `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). +1. `HTML` -> `BODY` -> `FORM` -> `DIV -> P` (capturing phase, the first listener): +2. `P` -> `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). + +Please note, the `P` shows up twice, because we've set two listeners: capturing and bubbling. The target triggers at the end of the first and at the beginning of the second phase. There's a property `event.eventPhase` that tells us the number of the phase on which the event was caught. But it's rarely used, because we usually know it in the handler. @@ -190,7 +192,7 @@ There's a property `event.eventPhase` that tells us the number of the phase on w If we `addEventListener(..., true)`, then we should mention the same phase in `removeEventListener(..., true)` to correctly remove the handler. ``` -````smart header="Listeners on same element and same phase run in their set order" +````smart header="Listeners on the same element and same phase run in their set order" If we have multiple event handlers on the same phase, assigned to the same element with `addEventListener`, they run in the same order as they are created: ```js @@ -199,6 +201,12 @@ elem.addEventListener("click", e => alert(2)); ``` ```` +```smart header="The `event.stopPropagation()` during the capturing also prevents the bubbling" +The `event.stopPropagation()` method and its sibling `event.stopImmediatePropagation()` can also be called on the capturing phase. Then not only the futher capturing is stopped, but the bubbling as well. + +In other words, normally the event goes first down ("capturing") and then up ("bubbling"). But if `event.stopPropagation()` is called during the capturing phase, then the event travel stops, no bubbling will occur. +``` + ## Summary @@ -206,7 +214,7 @@ When an event happens -- the most nested element where it happens gets labeled a - Then the event moves down from the document root to `event.target`, calling handlers assigned with `addEventListener(..., true)` on the way (`true` is a shorthand for `{capture: true}`). - Then handlers are called on the target element itself. -- Then the event bubbles up from `event.target` up to the root, calling handlers assigned using `on` and `addEventListener` without the 3rd argument or with the 3rd argument `false/{capture:false}`. +- Then the event bubbles up from `event.target` to the root, calling handlers assigned using `on`, HTML attributes and `addEventListener` without the 3rd argument or with the 3rd argument `false/{capture:false}`. Each handler can access `event` object properties: @@ -216,10 +224,10 @@ Each handler can access `event` object properties: Any event handler can stop the event by calling `event.stopPropagation()`, but that's not recommended, because we can't really be sure we won't need it above, maybe for completely different things. -The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logic behind that. +The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logical explanation for that. In real world, when an accident happens, local authorities react first. They know best the area where it happened. Then higher-level authorities if needed. -The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular `

    ` may be suited for that exactly ``, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last. +The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular `` may be suited for that exactly ``, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last one. Bubbling and capturing lay the foundation for "event delegation" -- an extremely powerful event handling pattern that we study in the next chapter. diff --git a/2-ui/2-events/03-event-delegation/article.md b/2-ui/2-events/03-event-delegation/article.md index 1fbb3f3bd..881183740 100644 --- a/2-ui/2-events/03-event-delegation/article.md +++ b/2-ui/2-events/03-event-delegation/article.md @@ -1,11 +1,11 @@ # Event delegation -Capturing and bubbling allow us to implement one of most powerful event handling patterns called *event delegation*. +Capturing and bubbling allow us to implement one of the most powerful event handling patterns called *event delegation*. The idea is that if we have a lot of elements handled in a similar way, then instead of assigning a handler to each of them -- we put a single handler on their common ancestor. -In the handler we get `event.target`, see where the event actually happened and handle it. +In the handler we get `event.target` to see where the event actually happened and handle it. Let's see an example -- the [Ba-Gua diagram](http://en.wikipedia.org/wiki/Ba_gua) reflecting the ancient Chinese philosophy. @@ -101,8 +101,8 @@ table.onclick = function(event) { Explanations: 1. The method `elem.closest(selector)` returns the nearest ancestor that matches the selector. In our case we look for `` on the way up from the source element. -2. If `event.target` is not inside any ``, then the call returns `null`, and we don't have to do anything. -3. In case of nested tables, `event.target` may be a `` lying outside of the current table. So we check if that's actually *our table's* ``. +2. If `event.target` is not inside any ``, then the call returns immediately, as there's nothing to do. +3. In case of nested tables, `event.target` may be a ``, but lying outside of the current table. So we check if that's actually *our table's* ``. 4. And, if it's so, then highlight it. As the result, we have a fast, efficient highlighting code, that doesn't care about the total number of `` in the table. diff --git a/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/gallery.css b/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/gallery.css index 4522006ae..8d6472ee6 100644 --- a/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/gallery.css +++ b/2-ui/2-events/04-default-browser-action/3-image-gallery/solution.view/gallery.css @@ -4,16 +4,6 @@ body { font: 75%/120% sans-serif; } -h2 { - font: bold 190%/100% sans-serif; - margin: 0 0 .2em; -} - -h2 em { - font: normal 80%/100% sans-serif; - color: #999999; -} - #largeImg { border: solid 1px #ccc; width: 550px; diff --git a/2-ui/2-events/04-default-browser-action/3-image-gallery/source.view/gallery.css b/2-ui/2-events/04-default-browser-action/3-image-gallery/source.view/gallery.css index b6e523014..8d6472ee6 100644 --- a/2-ui/2-events/04-default-browser-action/3-image-gallery/source.view/gallery.css +++ b/2-ui/2-events/04-default-browser-action/3-image-gallery/source.view/gallery.css @@ -4,16 +4,6 @@ body { font: 75%/120% sans-serif; } -h2 { - font: bold 190%/100% sans-serif; - margin: 0 0 .2em; -} - -h2 em { - font: normal 80%/100% sans-serif; - color: #999999; -} - #largeImg { border: solid 1px #ccc; width: 550px; @@ -32,4 +22,13 @@ h2 em { #thumbs a:hover { border-color: #FF9900; +} + +#thumbs li { + list-style: none; +} + +#thumbs { + margin: 0; + padding: 0; } \ No newline at end of file diff --git a/2-ui/2-events/04-default-browser-action/article.md b/2-ui/2-events/04-default-browser-action/article.md index ceac160c1..cd815654f 100644 --- a/2-ui/2-events/04-default-browser-action/article.md +++ b/2-ui/2-events/04-default-browser-action/article.md @@ -17,7 +17,7 @@ There are two ways to tell the browser we don't want it to act: - The main way is to use the `event` object. There's a method `event.preventDefault()`. - If the handler is assigned using `on` (not by `addEventListener`), then returning `false` also works the same. -In this HTML a click on a link doesn't lead to navigation, browser doesn't do anything: +In this HTML, a click on a link doesn't lead to navigation; the browser doesn't do anything: ```html autorun height=60 no-beautify Click here @@ -96,7 +96,7 @@ That's because the browser action is canceled on `mousedown`. The focusing is st The optional `passive: true` option of `addEventListener` signals the browser that the handler is not going to call `preventDefault()`. -Why that may be needed? +Why might that be needed? There are some events like `touchmove` on mobile devices (when the user moves their finger across the screen), that cause scrolling by default, but that scrolling can be prevented using `preventDefault()` in the handler. diff --git a/2-ui/2-events/05-dispatch-events/article.md b/2-ui/2-events/05-dispatch-events/article.md index ee3e74875..b93acb3f1 100644 --- a/2-ui/2-events/05-dispatch-events/article.md +++ b/2-ui/2-events/05-dispatch-events/article.md @@ -8,7 +8,7 @@ We can generate not only completely new events, that we invent for our own purpo ## Event constructor -Build-in event classes form a hierarchy, similar to DOM element classes. The root is the built-in [Event](http://www.w3.org/TR/dom/#event) class. +Built-in event classes form a hierarchy, similar to DOM element classes. The root is the built-in [Event](https://dom.spec.whatwg.org/#events) class. We can create `Event` objects like this: @@ -162,7 +162,7 @@ Besides, the event class describes "what kind of event" it is, and if the event ## event.preventDefault() -Many browser events have a "default action", such as nagivating to a link, starting a selection, and so on. +Many browser events have a "default action", such as navigating to a link, starting a selection, and so on. For new, custom events, there are definitely no default browser actions, but a code that dispatches such event may have its own plans what to do after triggering the event. @@ -187,7 +187,6 @@ Any handler can listen for that event with `rabbit.addEventListener('hide',...)` ``` The output order is: 1 -> nested -> 2. -Please note that the nested event `menu-open` fully bubbles up and is handled on the `document`. The propagation and handling of the nested event must be fully finished before the processing gets back to the outer code (`onclick`). +Please note that the nested event `menu-open` is caught on the `document`. The propagation and handling of the nested event is finished before the processing gets back to the outer code (`onclick`). + +That's not only about `dispatchEvent`, there are other cases. If an event handler calls methods that trigger other events -- they are processed synchronously too, in a nested fashion. -That's not only about `dispatchEvent`, there are other cases. JavaScript in an event handler can call methods that lead to other events -- they are too processed synchronously. +Let's say we don't like it. We'd want `onclick` to be fully processed first, independently from `menu-open` or any other nested events. -If we don't like it, we can either put the `dispatchEvent` (or other event-triggering call) at the end of `onclick` or, maybe better, wrap it in zero-delay `setTimeout`: +Then we can either put the `dispatchEvent` (or another event-triggering call) at the end of `onclick` or, maybe better, wrap it in the zero-delay `setTimeout`: ```html run @@ -253,7 +259,6 @@ If we don't like it, we can either put the `dispatchEvent` (or other event-trigg menu.onclick = function() { alert(1); - // alert(2) setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", { bubbles: true }))); @@ -265,7 +270,7 @@ If we don't like it, we can either put the `dispatchEvent` (or other event-trigg ``` -Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `mouse.onclick`, so event handlers are totally separate. +Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `menu.onclick`, so event handlers are totally separate. The output order becomes: 1 -> 2 -> nested. @@ -281,9 +286,9 @@ Other constructors of native events like `MouseEvent`, `KeyboardEvent` and so on For custom events we should use `CustomEvent` constructor. It has an additional option named `detail`, we should assign the event-specific data to it. Then all handlers can access it as `event.detail`. -Despite the technical possibility to generate browser events like `click` or `keydown`, we should use with the great care. +Despite the technical possibility of generating browser events like `click` or `keydown`, we should use them with great care. -We shouldn't generate browser events as it's a hacky way to run handlers. That's a bad architecture most of the time. +We shouldn't generate browser events as it's a hacky way to run handlers. That's bad architecture most of the time. Native events might be generated: diff --git a/2-ui/3-event-details/1-mouse-events-basics/article.md b/2-ui/3-event-details/1-mouse-events-basics/article.md index 4a5ea6416..5fdb9869a 100644 --- a/2-ui/3-event-details/1-mouse-events-basics/article.md +++ b/2-ui/3-event-details/1-mouse-events-basics/article.md @@ -1,4 +1,5 @@ -# Mouse events basics + +# Mouse events In this chapter we'll get into more details about mouse events and their properties. @@ -6,11 +7,7 @@ Please note: such events may come not only from "mouse devices", but are also fr ## Mouse event types -We can split mouse events into two categories: "simple" and "complex" - -### Simple events - -The most used simple events are: +We've already seen some of these events: `mousedown/mouseup` : Mouse button is clicked/released over an element. @@ -21,54 +18,76 @@ The most used simple events are: `mousemove` : Every mouse move over an element triggers that event. -`contextmenu` -: Triggers when opening a context menu is attempted. In the most common case, that happens when the right mouse button is pressed. Although, there are other ways to open a context menu, e.g. using a special keyboard key, so it's not exactly the mouse event. - -...There are several other event types too, we'll cover them later. - -### Complex events - `click` : Triggers after `mousedown` and then `mouseup` over the same element if the left mouse button was used. `dblclick` -: Triggers after a double click over an element. +: Triggers after two clicks on the same element within a short timeframe. Rarely used nowadays. -Complex events are made of simple ones, so in theory we could live without them. But they exist, and that's good, because they are convenient. +`contextmenu` +: Triggers when the right mouse button is pressed. There are other ways to open a context menu, e.g. using a special keyboard key, it triggers in that case also, so it's not exactly the mouse event. -### Events order +...There are several other events too, we'll cover them later. -An action may trigger multiple events. +## Events order -For instance, a click first triggers `mousedown`, when the button is pressed, then `mouseup` and `click` when it's released. +As you can see from the list above, a user action may trigger multiple events. + +For instance, a left-button click first triggers `mousedown`, when the button is pressed, then `mouseup` and `click` when it's released. In cases when a single action initiates multiple events, their order is fixed. That is, the handlers are called in the order `mousedown` -> `mouseup` -> `click`. ```online Click the button below and you'll see the events. Try double-click too. +<<<<<<< HEAD On the teststand below all mouse events are logged, and if there is more than a 1 second delay between them they are separated by a horizontal ruler. +======= +On the teststand below, all mouse events are logged, and if there is more than a 1 second delay between them, they are separated by a horizontal rule. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 -Also we can see the `which` property that allows to detect the mouse button. +Also, we can see the `button` property that allows us to detect the mouse button; it's explained below.
    ``` -## Getting the button: which +## Mouse button -Click-related events always have the `which` property, which allows to get the exact mouse button. +Click-related events always have the `button` property, which allows to get the exact mouse button. -It is not used for `click` and `contextmenu` events, because the former happens only on left-click, and the latter -- only on right-click. +We usually don't use it for `click` and `contextmenu` events, because the former happens only on left-click, and the latter -- only on right-click. -But if we track `mousedown` and `mouseup`, then we need it, because these events trigger on any button, so `which` allows to distinguish between "right-mousedown" and "left-mousedown". +On the other hand, `mousedown` and `mouseup` handlers may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown". -There are the three possible values: +The possible values of `event.button` are: +<<<<<<< HEAD - `event.which == 1` -- the left button - `event.which == 2` -- the middle button - `event.which == 3` -- the right button +======= +| Button state | `event.button` | +|--------------|----------------| +| Left button (primary) | 0 | +| Middle button (auxiliary) | 1 | +| Right button (secondary) | 2 | +| X1 button (back) | 3 | +| X2 button (forward) | 4 | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 + +Most mouse devices only have the left and right buttons, so possible values are `0` or `2`. Touch devices also generate similar events when one taps on them. -The middle button is somewhat exotic right now and is very rarely used. +Also there's `event.buttons` property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at [MDN](mdn:/api/MouseEvent/buttons) if you ever need it. + +```warn header="The outdated `event.which`" +Old code may use `event.which` property that's an old non-standard way of getting a button, with possible values: + +- `event.which == 1` – left button, +- `event.which == 2` – middle button, +- `event.which == 3` – right button. + +As of now, `event.which` is deprecated, we shouldn't use it. +``` ## Modifiers: shift, alt, ctrl and meta @@ -116,17 +135,29 @@ For JS-code it means that we should check `if (event.ctrlKey || event.metaKey)`. ``` ```warn header="There are also mobile devices" +<<<<<<< HEAD Keyboard combinations are good as an addition to the workflow. So that if the visitor has a keyboard -- it works. And if their device doesn't have it -- then there should be another way to do the same. +======= +Keyboard combinations are good as an addition to the workflow. So that if the visitor uses a keyboard -- they work. + +But if their device doesn't have it -- then there should be a way to live without modifier keys. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Coordinates: clientX/Y, pageX/Y -All mouse events have coordinates in two flavours: +All mouse events provide coordinates in two flavours: 1. Window-relative: `clientX` and `clientY`. 2. Document-relative: `pageX` and `pageY`. -For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then `clientX` and `clientY` are `0`. And if the mouse is in the center, then `clientX` and `clientY` are `250`, no matter what place in the document it is, how far the document was scrolled. They are similar to `position:fixed`. +We already covered the difference between them in the chapter . + +In short, document-relative coordinates `pageX/Y` are counted from the left-upper corner of the document, and do not change when the page is scrolled, while `clientX/Y` are counted from the current window left-upper corner. When the page is scrolled, they change. + +For instance, if we have a window of the size 500x500, and the mouse is in the left-upper corner, then `clientX` and `clientY` are `0`, no matter how the page is scrolled. + +And if the mouse is in the center, then `clientX` and `clientY` are `250`, no matter what place in the document it is. They are similar to `position:fixed` in that aspect. ````online Move the mouse over the input field to see `clientX/clientY` (the example is in the `iframe`, so coordinates are relative to that `iframe`): @@ -136,13 +167,11 @@ Move the mouse over the input field to see `clientX/clientY` (the example is in ``` ```` -Document-relative coordinates `pageX`, `pageY` are counted from the left-upper corner of the document, not the window. You can read more about coordinates in the chapter . - -## Disabling selection +## Preventing selection on mousedown -Double mouse click has a side-effect that may be disturbing in some interfaces: it selects the text. +Double mouse click has a side effect that may be disturbing in some interfaces: it selects text. -For instance, a double-click on the text below selects it in addition to our handler: +For instance, double-clicking on the text below selects it in addition to our handler: ```html autorun height=50 Double-click me @@ -185,7 +214,7 @@ Surely the user has access to HTML-source of the page, and can take the content Mouse events have the following properties: -- Button: `which`. +- Button: `button`. - Modifier keys (`true` if pressed): `altKey`, `ctrlKey`, `shiftKey` and `metaKey` (Mac). - If you want to handle `key:Ctrl`, then don't forget Mac users, they usually use `key:Cmd`, so it's better to check `if (e.metaKey || e.ctrlKey)`. diff --git a/2-ui/3-event-details/1-mouse-events-basics/head.html b/2-ui/3-event-details/1-mouse-events-basics/head.html index f578fb7db..1b9a73fca 100644 --- a/2-ui/3-event-details/1-mouse-events-basics/head.html +++ b/2-ui/3-event-details/1-mouse-events-basics/head.html @@ -25,7 +25,7 @@ function logMouse(e) { let evt = e.type; while (evt.length < 11) evt += ' '; - showmesg(evt + " which=" + e.which, 'test') + showmesg(evt + " button=" + e.button, 'test') return false; } diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html index e998165fd..84d52b18c 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html @@ -54,7 +54,7 @@

    Once upon a time there was a mother pig who had three little pigs.

    -

    The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you." +

    The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

    The three little pigs set off. "We will take care that the wolf does not catch us," they said.

    diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html index 2dc4394e7..774e24a21 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html @@ -54,7 +54,7 @@

    Once upon a time there was a mother pig who had three little pigs.

    -

    The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you." +

    The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."

    The three little pigs set off. "We will take care that the wolf does not catch us," they said.

    diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js index 4e6e2a3e9..7503ca9c2 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js @@ -88,7 +88,7 @@ class HoverIntent { if (speed < this.sensitivity) { clearInterval(this.checkSpeedInterval); this.isHover = true; - this.over.call(this.elem, event); + this.over.call(this.elem); } else { // speed fast, remember new coordinates as the previous ones this.prevX = this.lastX; diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md index c7ac0d4db..d409c3f12 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md @@ -80,7 +80,7 @@ An important feature of `mouseout` -- it triggers, when the pointer moves from a ``` -If we're on `#parent` and then move the pointer deeper into `#child`, but we get `mouseout` on `#parent`! +If we're on `#parent` and then move the pointer deeper into `#child`, we get `mouseout` on `#parent`! ![](mouseover-to-child.svg) diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js index 6d87199c2..5752e83ae 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js @@ -3,7 +3,7 @@ parent.onmouseover = parent.onmouseout = parent.onmousemove = handler; function handler(event) { let type = event.type; - while (type < 11) type += ' '; + while (type.length < 11) type += ' '; log(type + " target=" + event.target.id) return false; diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index 0dc0d05c9..6caddd7d9 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -4,9 +4,15 @@ Drag'n'Drop is a great interface solution. Taking something and dragging and dro In the modern HTML standard there's a [section about Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) with special events such as `dragstart`, `dragend`, and so on. +<<<<<<< HEAD These events are useful in that they allow us to solve simple tasks easily. For instance, they allow us to handle the drag'n'drop of "external" files into the browser, so we can take a file in the OS file-manager and drop it into the browser window, thereby giving JavaScript access to its contents. But native Drag Events also have limitations. For instance, we can't limit dragging by a certain area. Also we can't make it "horizontal" or "vertical" only. And there are other drag'n'drop tasks that can't be done using that API. Also, mobile device support for such events is almost non-existant. +======= +These events allow us to support special kinds of drag'n'drop, such as handling dragging a file from OS file-manager and dropping it into the browser window. Then JavaScript can access the contents of such files. + +But native Drag Events also have limitations. For instance, we can't prevent dragging from a certain area. Also we can't make the dragging "horizontal" or "vertical" only. And there are many other drag'n'drop tasks that can't be done using them. Also, mobile device support for such events is very weak. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 So here we'll see how to implement Drag'n'Drop using mouse events. @@ -14,26 +20,23 @@ So here we'll see how to implement Drag'n'Drop using mouse events. The basic Drag'n'Drop algorithm looks like this: -1. On `mousedown` - prepare the element for moving, if needed (maybe create a copy of it). -2. Then on `mousemove` move it by changing `left/top` and `position:absolute`. -3. On `mouseup` - perform all actions related to a finished Drag'n'Drop. +1. On `mousedown` - prepare the element for moving, if needed (maybe create a clone of it, add a class to it or whatever). +2. Then on `mousemove` move it by changing `left/top` with `position:absolute`. +3. On `mouseup` - perform all actions related to finishing the drag'n'drop. -These are the basics. Later we can extend it, for instance, by highlighting droppable (available for the drop) elements when hovering over them. +These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them. -Here's the algorithm for drag'n'drop of a ball: +Here's the implementation of dragging a ball: ```js -ball.onmousedown = function(event) { // (1) start the process - - // (2) prepare to moving: make absolute and on top by z-index +ball.onmousedown = function(event) { + // (1) prepare to moving: make absolute and on top by z-index ball.style.position = 'absolute'; ball.style.zIndex = 1000; + // move it out of any current parents directly into body // to make it positioned relative to the body - document.body.append(ball); - // ...and put that absolutely positioned ball under the pointer - - moveAt(event.pageX, event.pageY); + document.body.append(ball); // centers the ball at (pageX, pageY) coordinates function moveAt(pageX, pageY) { @@ -41,14 +44,17 @@ ball.onmousedown = function(event) { // (1) start the process ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; } + // move our absolutely positioned ball under the pointer + moveAt(event.pageX, event.pageY); + function onMouseMove(event) { moveAt(event.pageX, event.pageY); } - // (3) move the ball on mousemove + // (2) move the ball on mousemove document.addEventListener('mousemove', onMouseMove); - // (4) drop the ball, remove unneeded handlers + // (3) drop the ball, remove unneeded handlers ball.onmouseup = function() { document.removeEventListener('mousemove', onMouseMove); ball.onmouseup = null; @@ -64,10 +70,10 @@ Here's an example in action: [iframe src="ball" height=230] -Try to drag'n'drop the mouse and you'll see such behavior. +Try to drag'n'drop with the mouse and you'll see such behavior. ``` -That's because the browser has its own Drag'n'Drop for images and some other elements that runs automatically and conflicts with ours. +That's because the browser has its own drag'n'drop support for images and some other elements. It runs automatically and conflicts with ours. To disable it: @@ -93,14 +99,14 @@ So we should listen on `document` to catch it. ## Correct positioning -In the examples above the ball is always moved so, that it's center is under the pointer: +In the examples above the ball is always moved so that its center is under the pointer: ```js ball.style.left = pageX - ball.offsetWidth / 2 + 'px'; ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ``` -Not bad, but there's a side-effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. +Not bad, but there's a side effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. It would be better if we keep the initial shift of the element relative to the pointer. @@ -124,7 +130,11 @@ Let's update our algorithm: ```js // onmousemove +<<<<<<< HEAD // ball has position:absoute +======= + // ball has position:absolute +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` @@ -219,7 +229,7 @@ That's why the initial idea to put handlers on potential droppables doesn't work So, what to do? -There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). +There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). If there are multiple overlapping elements on the same coordinates, then the topmost one is returned. We can use it in any of our mouse event handlers to detect the potential droppable under the pointer, like this: @@ -276,7 +286,7 @@ function onMouseMove(event) { } ``` -In the example below when the ball is dragged over the soccer gate, the gate is highlighted. +In the example below when the ball is dragged over the soccer goal, the goal is highlighted. [codetabs height=250 src="ball4"] @@ -300,4 +310,8 @@ We can lay a lot on this foundation. - We can use event delegation for `mousedown/up`. A large-area event handler that checks `event.target` can manage Drag'n'Drop for hundreds of elements. - And so on. +<<<<<<< HEAD There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-part solution. +======= +There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-party solution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html index 3fdd7fe76..8751c70ad 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html @@ -13,16 +13,13 @@ + + + diff --git a/2-ui/3-event-details/6-pointer-events/ball.view/index.html b/2-ui/3-event-details/6-pointer-events/ball.view/index.html new file mode 100644 index 000000000..8bbef8f63 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/ball.view/index.html @@ -0,0 +1,30 @@ + + +

    Drag the ball.

    + + + + + + + diff --git a/2-ui/3-event-details/6-pointer-events/multitouch.view/index.html b/2-ui/3-event-details/6-pointer-events/multitouch.view/index.html new file mode 100644 index 000000000..d46e1bc16 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/multitouch.view/index.html @@ -0,0 +1,28 @@ + + + + +
    + Multi-touch here +
    + + + diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html new file mode 100644 index 000000000..781016f52 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html @@ -0,0 +1,6 @@ + + + +
    +
    +
    diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css new file mode 100644 index 000000000..9b3d3b82d --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css @@ -0,0 +1,19 @@ +.slider { + border-radius: 5px; + background: #E0E0E0; + background: linear-gradient(left top, #E0E0E0, #EEEEEE); + width: 310px; + height: 15px; + margin: 5px; +} + +.thumb { + width: 10px; + height: 25px; + border-radius: 3px; + position: relative; + left: 10px; + top: -5px; + background: blue; + cursor: pointer; +} diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/index.html b/2-ui/3-event-details/6-pointer-events/slider.view/index.html new file mode 100644 index 000000000..b29e646a1 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider.view/index.html @@ -0,0 +1,50 @@ + + + +
    +
    +
    + +

    Mouse over here to see the date

    + + diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css new file mode 100644 index 000000000..a84cd5e7e --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css @@ -0,0 +1,20 @@ +.slider { + border-radius: 5px; + background: #E0E0E0; + background: linear-gradient(left top, #E0E0E0, #EEEEEE); + width: 310px; + height: 15px; + margin: 5px; +} + +.thumb { + touch-action: none; + width: 10px; + height: 25px; + border-radius: 3px; + position: relative; + left: 10px; + top: -5px; + background: blue; + cursor: pointer; +} diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.md b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.md similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.md rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.md diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.view/index.html b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.view/index.html similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.view/index.html rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.view/index.html diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/task.md b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/task.md similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/task.md rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/task.md diff --git a/2-ui/3-event-details/5-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md similarity index 81% rename from 2-ui/3-event-details/5-keyboard-events/article.md rename to 2-ui/3-event-details/7-keyboard-events/article.md index 617852ccf..12fe63201 100644 --- a/2-ui/3-event-details/5-keyboard-events/article.md +++ b/2-ui/3-event-details/7-keyboard-events/article.md @@ -107,7 +107,7 @@ So, `event.code` may match a wrong character for unexpected layout. Same letters To reliably track layout-dependent characters, `event.key` may be a better way. -On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch. +On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location. So hotkeys that rely on it work well even in case of a language switch. Do we want to handle layout-dependant keys? Then `event.key` is the way to go. @@ -139,22 +139,25 @@ For instance, the `` below expects a phone number, so it does not accept ```html autorun height=60 run ``` -Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`. +The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`. -Let's relax it a little bit: +As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the `` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters) +Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side effect of the strict filter `checkPhoneKey`. These keys make it return `false`. + +Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`: ```html autorun height=60 run @@ -162,7 +165,9 @@ function checkPhoneKey(key) { Now arrows and deletion works well. -...But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid. +Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable. + +The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the `` when it's invalid. Or we can use both event handlers together. ## Legacy @@ -170,6 +175,12 @@ In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `whic There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more. +## Mobile Keyboards + +When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's [`e.keyCode` should be `229`](https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode) and [`e.key` should be `"Unidentified"`](https://www.w3.org/TR/uievents-key/#key-attr-values). + +While some of these keyboards might still use the right values for `e.key`, `e.code`, `e.keyCode`... when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices. + ## Summary Pressing a key always generates a keyboard event, be it symbol keys or special keys like `key:Shift` or `key:Ctrl` and so on. The only exception is `key:Fn` key that sometimes presents on a laptop keyboard. There's no keyboard event for it, because it's often implemented on lower level than OS. diff --git a/2-ui/3-event-details/7-keyboard-events/german-layout.svg b/2-ui/3-event-details/7-keyboard-events/german-layout.svg new file mode 100644 index 000000000..7ac9a4008 --- /dev/null +++ b/2-ui/3-event-details/7-keyboard-events/german-layout.svg @@ -0,0 +1 @@ +StrgStrgAl tAlt GrWinWinMenu \ No newline at end of file diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html similarity index 95% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html index 401062830..a0d5a4f40 100644 --- a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html @@ -28,7 +28,7 @@ - + diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js similarity index 96% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js index 5eba24c7a..d97f7a7b5 100644 --- a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js @@ -5,6 +5,8 @@ let lastTime = Date.now(); function handle(e) { if (form.elements[e.type + 'Ignore'].checked) return; + area.scrollTop = 1e6; + let text = e.type + ' key=' + e.key + ' code=' + e.code + diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/style.css b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/style.css similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/style.css rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/style.css diff --git a/2-ui/3-event-details/7-keyboard-events/us-layout.svg b/2-ui/3-event-details/7-keyboard-events/us-layout.svg new file mode 100644 index 000000000..353f225f1 --- /dev/null +++ b/2-ui/3-event-details/7-keyboard-events/us-layout.svg @@ -0,0 +1 @@ +Caps LockShiftShift \ No newline at end of file diff --git a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md index 10945ccd7..54c101193 100644 --- a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md +++ b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md @@ -55,11 +55,11 @@ function populate() { // document bottom let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom; - // if the user scrolled far enough (<100px to the end) - if (windowRelativeBottom < document.documentElement.clientHeight + 100) { - // let's add more data - document.body.insertAdjacentHTML("beforeend", `

    Date: ${new Date()}

    `); - } + // if the user hasn't scrolled far enough (>100px to the end) + if (windowRelativeBottom > document.documentElement.clientHeight + 100) break; + + // let's add more data + document.body.insertAdjacentHTML("beforeend", `

    Date: ${new Date()}

    `); } } ``` diff --git a/2-ui/3-event-details/8-onscroll/article.md b/2-ui/3-event-details/8-onscroll/article.md index 7b5cf4848..734bd84c6 100644 --- a/2-ui/3-event-details/8-onscroll/article.md +++ b/2-ui/3-event-details/8-onscroll/article.md @@ -1,6 +1,6 @@ # Scrolling -The `scroll` event allows to react on a page or element scrolling. There are quite a few good things we can do here. +The `scroll` event allows reacting to a page or element scrolling. There are quite a few good things we can do here. For instance: - Show/hide additional controls or information depending on where in the document the user is. @@ -34,4 +34,4 @@ If we add an event handler to these events and `event.preventDefault()` in it, t There are many ways to initiate a scroll, so it's more reliable to use CSS, `overflow` property. -Here are few tasks that you can solve or look through to see the applications on `onscroll`. +Here are few tasks that you can solve or look through to see applications of `onscroll`. diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index 01af1f400..7bc87a0f0 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -8,11 +8,11 @@ Working with forms will be much more convenient when we learn them. Document forms are members of the special collection `document.forms`. -That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form. +That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form. ```js no-beautify -document.forms.my - the form with name="my" -document.forms[0] - the first form in the document +document.forms.my; // the form with name="my" +document.forms[0]; // the first form in the document ``` When we have a form, then any element is available in the named collection `form.elements`. @@ -36,9 +36,9 @@ For instance: ``` -There may be multiple elements with the same name, that's often the case with radio buttons. +There may be multiple elements with the same name. This is typical with radio buttons and checkboxes. -In that case `form.elements[name]` is a collection, for instance: +In that case, `form.elements[name]` is a *collection*. For instance: ```html run height=40
    @@ -119,7 +119,7 @@ That's easy to see in an example: ``` -That's usually not a problem, because we rarely change names of form elements. +That's usually not a problem, however, because we rarely change names of form elements. ```` @@ -155,7 +155,7 @@ Let's talk about form controls. ### input and textarea -We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes. +We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes and radio buttons. Like this: @@ -177,18 +177,16 @@ It stores only the HTML that was initially on the page, not the current value. A ``: -1. Find the corresponding `
    ` is in "edit mode", clicks on other cells are ignored. - The table may have many cells. Use event delegation. diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md index fc48c21ff..644d814d9 100644 --- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md +++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md @@ -9,4 +9,5 @@ Focus on the mouse. Then use arrow keys to move it: [demo src="solution"] P.S. Don't put event handlers anywhere except the `#mouse` element. + P.P.S. Don't modify HTML/CSS, the approach should be generic and work with any element. diff --git a/2-ui/4-forms-controls/2-focus-blur/article.md b/2-ui/4-forms-controls/2-focus-blur/article.md index d42013e5b..c253dc11d 100644 --- a/2-ui/4-forms-controls/2-focus-blur/article.md +++ b/2-ui/4-forms-controls/2-focus-blur/article.md @@ -1,6 +1,6 @@ # Focusing: focus/blur -An element receives a focus when the user either clicks on it or uses the `key:Tab` key on the keyboard. There's also an `autofocus` HTML attribute that puts the focus into an element by default when a page loads and other means of getting a focus. +An element receives the focus when the user either clicks on it or uses the `key:Tab` key on the keyboard. There's also an `autofocus` HTML attribute that puts the focus onto an element by default when a page loads and other means of getting the focus. Focusing on an element generally means: "prepare to accept the data here", so that's the moment when we can run the code to initialize the required functionality. @@ -18,7 +18,7 @@ Let's use them for validation of an input field. In the example below: -- The `blur` handler checks if the field the email is entered, and if not -- shows an error. +- The `blur` handler checks if the field has an email entered, and if not -- shows an error. - The `focus` handler hides the error message (on `blur` it will be checked again): ```html run autorun height=60 @@ -90,6 +90,8 @@ If we enter something into the input and then try to use `key:Tab` or click away Please note that we can't "prevent losing focus" by calling `event.preventDefault()` in `onblur`, because `onblur` works *after* the element lost the focus. +In practice though, one should think well, before implementing something like this, because we generally *should show errors* to the user, but *should not prevent their progress* in filling our form. They may want to fill other fields first. + ```warn header="JavaScript-initiated focus loss" A focus loss can occur for many reasons. @@ -104,11 +106,11 @@ The best recipe is to be careful when using these events. If we want to track us ``` ## Allow focusing on any element: tabindex -By default many elements do not support focusing. +By default, many elements do not support focusing. The list varies a bit between browsers, but one thing is always correct: `focus/blur` support is guaranteed for elements that a visitor can interact with: `