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 85e5fdaf0..19f25ad11 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 įvadas +<<<<<<< HEAD Pažvelkime kuo ypatinga JavaScript kalba, ką mes galime su ja padaryti ir kokios kitos technologijos gali būti naudojamos kartu. +======= +Let's see what's so special about JavaScript, what we can achieve with it, and what other technologies play well with it. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## Kas yra JavaScript? +<<<<<<< HEAD Iš pat pradžių *JavaScript* buvo sukurta tam, kad *"padarytų tinklalapius gyvus"*. +======= +*JavaScript* was initially created to "make web pages alive". +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Programos, parašytos šia kalba yra vadinamos *skriptais* (and. scripts). Jos gali būti parašytos tinklalapio HTML ir suveikti automatiškai, kuomet tinklalapis kraunamas. @@ -24,11 +32,19 @@ Naršyklės turi savo vidinį variklį, kuris kartais vadinamas "JavaScript virt Skirtingi varikliai turi skirtingus slapyvardžius (ang. "nicknames"). Pavyzdžiui: +<<<<<<< HEAD - [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- Chrome ir Opera. - [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- Firefox. - Egzistuoja kiti slapyvardžiai, tokie kaip "Trident", "Chakra" skirtingoms IE versijom, "ChakraCore" Microsoft Edge naršklėje, "Nitro" ir "SquirellFish" Safari ir t.t. Šias sąvokas verta atsiminti, nes jos naudojamos straipsniuose, skirtuose programuotojams. Mes taip pat jas naudosime. Pavyzdžiui, jeigu "feature X yra palaikoma V8", reiškias jinai ko gero veikia Chrome ir Opera naršklėse. +======= +- [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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```smart header="Kaip veikia varikliai?" @@ -59,12 +75,20 @@ Pavyzdžiui, JavaScript naršyklėje gali: ## Ko NEGALI PADARYTI JavaScript naršyklėje? +<<<<<<< HEAD JavaScript galimybės naryklėje yra ribojamos dėl vartotojų saugumo. Tikslas - neleisti tinklalapiams pasiekti privačius duomenis arba žaloti vartotojo duomenis. +======= +JavaScript's abilities in the browser are limited for the sake of a user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Ribojimų pavyzdžiai: - JavaScript tinklalapyje negali skaityti/rašyti failus esančius kietajame diske, juos kopijuoti arba vykdyti programas. JavaScript neturi tiesioginės prieigos prie operacinės sistemos funkcijų. +<<<<<<< HEAD Modernios naršklės leidžia dirbti su failais, bet prieiga ribojama ir tai leidžiama tik jeigu vartotojas įvykdo kažką konkretaus. Pavyzdžiui, perkelia failą į naršyklę arba pažymi failą naudodamas `` žymą. +======= +- 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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Yra būdų komunikuoti su kamera/mikrofonu ir kitais įrenginiais, bet tai reikalauja išreikštinio vartotojo leidimo. Taigi, JavaScript tinklalapis negali suktai įjungti web kameros, stebėti aplinkos ir siųsti informaciją į [NSA](https://en.wikipedia.org/wiki/National_Security_Agency). - Atskiros naršyklės kortelės (ang. "tabs") dažniausiai nežino viena apie kitą. Tačiau kartais viena kortelė naudoja JavaScript tam, kad atidarytų kitą kortelę, bet netgi tokiu atveju, JavaScript vienoje kortelėje negali pasiekti kitos, jeigu jie ateina iš skirtingų tinklalapių (skirtingas domenas, protokolas arba portas). @@ -81,9 +105,15 @@ Yra būdų komunikuoti su kamera/mikrofonu ir kitais įrenginiais, bet tai reika JavaScript turi bent *tris* nuostabius dalykus: ```compare +<<<<<<< HEAD + Pilna integracija su HTML/CSS + Paprastus dalykus padaryti yra nesudėtinga + Palaikoma visose populiariausiose naršyklėse +======= ++ Full integration with HTML/CSS. ++ Simple things are done simply. ++ Supported by all major browsers and enabled by default. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ``` JavaScript yra vienintelė naršyklės technologija, kuri turi šiuos tris dalykus. @@ -103,15 +133,30 @@ Modernūs įrankiai atlieka perrašymą labai greitai, tad programuotojai gali p Tokių kalbų pavyzdžiai: +<<<<<<< HEAD - [CoffeeScript](http://coffeescript.org/) yra "syntactic sugar" JavaScript. Trumpesnė sintaksė, su kuria galima rašyti aiškesnį ir konkretesnį kodą. Tai dažniausiai patinka Ruby programuotojams. - [TypeScript](http://www.typescriptlang.org/) pagrindinis tikslas yra įvesti statinį tipizavimą. Tai palengvina sudėtingų sistemų programavimą. Sukurtas Microsoft. - [Flow](http://flow.org/) taip pat turi statinį tipizavimą, bet kiek kitokiu būdu. Sukurtas Facebook. - [Dart](https://www.dartlang.org/) yra atskira kalba, kuri turi savo paties variklį, kuris veikia ne naršyklėse (pvz. mobiliose aplikacijose), bet taip pat gali būti transpiliuotas į Javascriptą. Sukurtas Google. +======= +- [CoffeeScript](http://coffeescript.org/) is a "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it. +- [TypeScript](http://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](http://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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Yra ir daugiau pavyzdžių. Tačiau, netgi jeigu mes naudojame kažkurią iš transpiliuojamų kalbų, suprasti JavaScript yra ne mažiau svarbu. ## Santrauka +<<<<<<< HEAD - JavaScript iš pat pradžių buvo sukurtas kaip kalba, veikianti naršyklėje, bet dabar turi ir daugiau aplinkų, kuriose gali būti vykdoma. - Šią dieną JavaScript yra unikali tuo, kad tai labiausiai paplitusi kalba naršyklei, turinti pilną integraciją su HTML/CSS. - Yra daug kalbų, kurios gali būti konvertuojamos į JavaScript ir turi papildomų funkcijų. Rekomenduojame į jas bent jau trumpai pažvelgti po to, kaip išmoksite JavaScript. +======= +- 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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 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 cd651d63a..4764b7a39 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -13,21 +13,33 @@ Kasmet yra išleidžiama nauja specifikacijos versija. Tarp šitų išleidimų, Galite perskaityti apie naujausias savybes, įskaitant ir tas, kurios yra "beveik standartas" (dar vadinamas "stage 3") galima rasti . +<<<<<<< HEAD Taip pat, jeigu norite programuoti naršyklei, tam yra kita specifikacija, kurią gali rasti [antroje](info:browser-environment) pamokų dalyje. +======= +Also, if you're developing for the browser, then there are other specifications covered in the [second part](info:browser-environment) of the tutorial. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## Vadovai - **MDN (Mozilla) JavaScript Reference** yra vadovas su pavyzdžiais ir kita informacija. Tinka, jeigu reikia detalios informacijos apie konkrečias kalbos funkcijas, metodus, ir pan. +<<<<<<< HEAD Vadovą galima rasti . +======= +- **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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Tačiau, dažniausiai geriausia tiesiog ieškoti informacijos internete. Verta tiesiog naudoti "MDN [apibrėžimas]" užklausoje, pavyzdžiui tam, kad rastumėte informacijos apie `parseInt` funkciją. +<<<<<<< HEAD - **MSDN** - Microsoft vadovas, turintis daug informacijos, įskaitant JavaScript (dažnai vadinamas JScript). Jeigu reikia kažko konkretaus skirto Internet Explorer, geriau eiti čia: . Taip pat, galima naudoti paiešką internete su frazėmis, tokiomis kaip "RegExp MSDN" arba "RegExp MSDN jscript". ## Suderinamumo (ang "compatibility") lentelės +======= +Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for `parseInt` function. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 JavaScript yra nuolatos tobulinama kalba, todėl naujos savybės atsiranda reguliariai. 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 dc3022a03..f8e234f5b 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -36,7 +36,14 @@ Praktikoj, lengvasvoriai redaktoriai turi daug papildymų (ang. "plugin"), įska - [Notepad++](https://notepad-plus-plus.org/) (Windows, nemokamas). - [Vim](http://www.vim.org/) ir [Emacs](https://www.gnu.org/software/emacs/) yra labai puikūs, jeigu moki jais naudotis. +<<<<<<< HEAD ## Nesiginčykime +======= +- [Atom](https://atom.io/) (cross-platform, free). +- [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). +- [Notepad++](https://notepad-plus-plus.org/) (Windows, free). +- [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Redaktoriai, kurie buvo paminėti šioje pamokoje yra tie, kuriuos aš ir mano draugai, kuriuos laikau kietais programuotojais, naudojame ilgą laiką ir kurie tenkina mūsų poreikius. diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index 690fa5661..3c57717e3 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -29,10 +29,19 @@ Konkretus vaizdas priklauso nuo Chrome versijos, kurią naudoji. Kartais atsiran - Čia mes galim pamatyti raudoną klaidos žinutę. Šiuo atveju skriptas turi nežinomą "lalala" komandą. - Dešinėje yra aktyvi nuoroda į `bug.html:12` su skaičiumi eilutės, kurioje yra klaida. +<<<<<<< HEAD Žemiau klaidos žinutės yra mėlynas `>` simbolis. Jis parodo komandų eilutę (ang. "command line"), kurioje mes galime rašyti JavaScript komandas. Spausk `key:Enter` kad jas paleisti (`key:Shift+Enter` kad rašyti komandas per daugiau nei vieną eilutę). +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Dabar mes galime matyti klaidas. Pradžiai, to pakanka. Vėliau mes grįšime į programuotojų įrankius ir kalbėsime apie klaidų taisymą skyriuje . +```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. +``` ## Firefox, Edge ir kiti @@ -50,6 +59,7 @@ Atidaryk Nuostatas (ang. "Preferences") ir eik į "Pažangi" (ang. "Advanced"). Dabar `key:Cmd+Opt+C` įjungs konsolę. Taip pat turėk omeny, kad naujas pasirinkimas "Programuoti" (ang. "Develop") atsirado viršutiniame meniu. Jame yra daug komandų ir nustatymų. +<<<<<<< HEAD ```smart header="Kelių eilučių komandos" Dažniausiai, jeigu konsolėje parašome vieną eilutę kodo ir paspaudžiame `key:Enter`, ji suveikia. @@ -57,6 +67,9 @@ Tam, kad parašyti kelias eilutes, spausk `key:Shift+Enter`. Tokiu būdu mes gal ``` ## Reziumė +======= +## Summary +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 - Programuotojo įrankiai leidžia mums pamatyti klaidas, paleisti komandas, analizuoti kintamuosiuos ir daugiau. - Juos paleisti galime su `key:F12` dauguma naršklių per Windows. Chrome, jeigu naudojame Mac, reikalauja `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (iš pradžių reikia aktyvuoti). diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html b/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html new file mode 100644 index 000000000..ff1d871b0 --- /dev/null +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md index e69de29bb..81552913b 100644 --- a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md @@ -0,0 +1,2 @@ + +[html src="index.html"] 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 4dae0d4ba..651d856c1 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 @@ Tad visų pirma pabandykime pridėti skriptą prie internetinio puslapio. Aplink ## Žyma "script" +<<<<<<< HEAD JavaScript programa gali būti pridėta prie bet kurios HTML dokumento dalies su ` ``` +<<<<<<< HEAD Šiuo atveju `/path/to/script.js` yra tikslus kelias į skriptus iš pagrindinės svetainės. Dažnai naudojamas ir kitoks kelias iš esamo puslapio. Kaip pavyzdžiui, `src="script.js"` reiškia, kad `"script.js"` failas yra tame pačiame aplanke (folder) kaip ir HTML failas. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Taip pat galime pateikti pilną adresą (URL). Pavyzdžiui: ```html - + ``` Kad pridėtume kelis skriptus, naudojame kiekvienam atskiras žymas: diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 4a8a0aa56..58068e04e 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 Kodas mums atiduoda `6`, nes JavaScript šiuo atveju neįterpia kabliataškio tarp eilučių. Intuityviai akivaizdu, kad jeigu eilutė pasibaigia pliusu `"+"`, tai yra nepabaigta išraiška (ang. "incomplete expression"), tokiu atveju kabliataškis nereikalingas. O kodas suveikia taip kaip buvo tikėtasi. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 **Bet yra situacijų kada JavaScript "nepavyksta" numanyti kabliataškio, kai jo iš tikrųjų reikia.** @@ -56,28 +60,43 @@ Klaidas tokiais atvejais dažnai sunku surasti ir pataisyti. Jeigu jums smalsu pamatyti tokios klaidos konkretų pavyzdį, patikrinkite sekantį kodą: ```js run -[1, 2].forEach(alert) +alert("Hello"); + +[1, 2].forEach(alert); ``` +<<<<<<< HEAD Kol kas negalvokite apie šiuos skliaustelius `[]` ir `forEach`. Juos studijuosime vėliau. Jums reikia tik prisiminti kodo rezultatą: jis rodo `1` tada `2`. O dabar pridėkime `alert` prieš kodą ir *neužbaikime* jo su kabliataškiu: ```js run no-beautify alert("Tai bus klaida") +======= +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`: + +```js run no-beautify +alert("Hello") +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 -[1, 2].forEach(alert) +[1, 2].forEach(alert); ``` +<<<<<<< HEAD Dabar jeigu paleisime šį kodą, tik pirmasis `alert` pasirodo, o tada gauname klaidą! Bet jeigu pridedame kabliataškį po `alert` vėl viskas gerai: ```js run alert("Dabar viskas gerai"); +======= +The difference compared to the code above is only one character: the semicolon at the end of the first line is gone. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 -[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. +<<<<<<< HEAD Gauname "Dabar viskas gerai" žinutę, kurią seka `1` ir `2`. @@ -90,11 +109,28 @@ alert("Tai bus klaida")[1, 2].forEach(alert) ``` Bet tai turėtų būti du atskiri pareiškimai, ne vienas. Toks sujungimas šiuo atveju yra neteisingas, dėl to ir gauname klaidą. Taip gali nutikti ir kitose situacijose. +======= +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. + +Here's how the engine sees it: + +```js run no-beautify +alert("Hello")[1, 2].forEach(alert); +``` + +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```` Mes rekomenduojame dėti kabliataškius pareiškimų pabaigoje net jeigu jie yra atskirose eilutėse. Tokia taisyklė yra plačiai naudojama. Dar kartą prisiminkime -- *įmanoma* daugeliu atvejų kabliataškių nedėti. Bet daug saugiau -- ypač naujokams -- juos naudoti. +<<<<<<< HEAD ## Komentarai +======= +## Comments [#code-comments] +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Laikui bėgant programos tampa vis sudėtingesnės, dėl to svarbu pridėti komentarus (ang. *comments*), kurie paaiškintų ką kodas daro ir kodėl. @@ -136,7 +172,11 @@ alert('Pasauli'); ``` ```smart header="Use hotkeys!" +<<<<<<< HEAD Didžiojoje dalyje redaktorių vieno kodo eilutė gali būti paversta komentaru spaudžiant klaviatūroje vienu metu `key:Ctrl+/`, o kelių eilučių komentaras gaunamas spaudžiant `key:Ctrl+Shift+/` -- (išbandykite tai patys su savo kodu). Mac kompiuteriuose, naudokite `key:Cmd` vietoje `key:Ctrl`. +======= +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`. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ``` ````warn header="Sudedamieji (ang. nested) komentarai nėra galimi!" 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 177042cb9..2e3d0d29b 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -19,8 +19,12 @@ Pavyzdžiui: ... ``` +<<<<<<< HEAD Mes greitai mokinsimės funkcijas (tam tikras būdas grupuoti komandas), bet užbėgant už akių, galime pažymėti, kad `"use strict"` gali būti dedamas funkcijos pradžioje. Tokiu būdų tik funkcijos korpusas (ang. "body") turi griežtą režimą, o ne visas skriptas. Bet dažniausiai žmonės naudoja šį režimą visame skripte. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ````warn header="Įsitikinkite, kad \"use strict\" yra viršuje" Prašau, įsitikinkite, kad `"use strict"` yra jūsų skriptų viršuje, kitu atveju griežtasis režimas gali nesuveikti. @@ -42,16 +46,30 @@ Anksčiau už `"use strict"` gali būti tik komentarai. ```warn header="Nėra būdo kaip atšaukti `use strict`" Nėra tokios direktyvos kaip `"no use strict"`, kuris sugrąžintų sistemą į senąjį funkcionavimą. +<<<<<<< HEAD Kai jau įžengiame į griežtą režimą, kelio atgal nebėra. +======= +Once we enter strict mode, there's no going back. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ``` ## Naršyklės konsolė +<<<<<<< HEAD Ateičiai, kai naudosite naršyklės konsolės testavimo funkcijas, žinokite, kad ji nenaudoja `use strict` pagal numatytus nustatymus. +======= +When you use a [developer console](info:devtools) to run code, please note that it doesn't `use strict` by default. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Kartais, kai `use strict` įtaka yra svarbi, galite gauti neteisingus rezultatus. +<<<<<<< HEAD Norėdami konsolėje suvesti daugiau nei vieną eilutę paspauskite `key:Shift+Enter`, tokiu būdu galite užrašyti `use strict` viršuje, štai taip: +======= +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: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js 'use strict'; @@ -61,12 +79,17 @@ Norėdami konsolėje suvesti daugiau nei vieną eilutę paspauskite `key:Shift+E Tai suveikia didžiojoje dalyje naršyklių, tarp jų Firefox ir Chrome. +<<<<<<< HEAD Jeigu nesuveikia, geriausias būdas įsitikinti, kad `use strict` veiks, kai kodas konsolėje paleidžiamas tokiu būdu: +======= +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: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js (function() { 'use strict'; +<<<<<<< HEAD // ...jūsų kodas... })() ``` @@ -83,3 +106,24 @@ Kol kas užtenka žinoti apie tai pagrindinius dalykus: 2. Griežtas režimas paleidžiamas užrašant `"use strict"` skripto arba funkcijos viršuje. Kai kurios kalbos savybės, kaip klasės (ang. "classes") ir moduliai (ang. "modules") griežtą režimą paleidžia automatiškai. 3. Griežtą režimą palaiko visos modernios naršyklės. 4. Rekomenduojame visus skriptus pradėti su `"use strict"`. Visi šių pamokų pavyzdžiai numano, kad griežtas režimas yra naudojamas, nebent (labai retais atvejais) yra nurodoma kitaip. +======= + // ...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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 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 7c64f263a..737486ba2 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 @@ Tai labai paprasta: let ourPlanetName = "Žemė"; ``` +<<<<<<< HEAD Atkreipkite dėmesį, kad galėjome naudoti trumpesnį pavadinimą `planet`, bet tada nebūtų aišku apie kurią planetą eina kalba. Šiuo atveju nieko tokio daugiažoždiauti. Bent jau kol kintamasis nėraPerIlgas. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## Esamo lankytojo vardas diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index c531c60d6..deafd1a29 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 @@ Dabar į jį galime patalpinti duomenis naudodami užduoties operatorių `=`: let message; *!* +<<<<<<< HEAD message = 'Labas'; // patalpinama eilutė +======= +message = 'Hello'; // store the string 'Hello' in the variable named message +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 */!* ``` @@ -80,9 +84,14 @@ let user = 'John' Techniškai, visi šie variantai daro tą patį. Tad tai daugiau asmeninio skonio ir estetikos reikalas. +<<<<<<< HEAD ````smart header="`var` vietoje `let`" Senesniuose skriptuose galite rasti raktažodį: `var` vietoje `let`: +======= +````smart header="`var` instead of `let`" +In older scripts, you may also find another keyword: `var` instead of `let`: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js *!*var*/!* message = 'Labas'; @@ -135,8 +144,27 @@ alert(hello); // Labas pasauli! alert(message); // Labas pasauli! ``` +<<<<<<< HEAD ```smart header="Funkcinės kalbos" Yra įdomu pastebėti, kad egzistuoja [funkcinės](https://en.wikipedia.org/wiki/Functional_programming) programavimo kalbos, tokios kaip [Scala](http://www.scala-lang.org/) arba [Erlang](http://www.erlang.org/), kurios draudžia keisti kintamųjų vertes. +======= +````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 [functional](https://en.wikipedia.org/wiki/Functional_programming) programming languages, like [Scala](http://www.scala-lang.org/) or [Erlang](http://www.erlang.org/) that forbid changing variable values. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Tokiose kalbose, kai vertė yra patalpinama "į dėžę", ji ten ir pasilieka amžiams. Jeigu norime patalpinti kažką kito, kalba mus priverčia sukurti naują dėžę (deklaruoti naują kintamąjį). Mes nebegalime dar kartą panaudoti senojo. @@ -190,7 +218,11 @@ let имя = '...'; let 我 = '...'; ``` +<<<<<<< HEAD Techniškai jokios klaidos tame nėra ir tokie pavadinimai yra leistini, tačiau tarptautinė tradicija yra naudoti angliškus kintamųjų pavadinimus. Net jeigu rašome trumpą skriptą, jo gyvenimas gali būti labai ilgas. Kada nors žmonėms iš kitų šalių gali tekti jį perskaityti. +======= +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 some time. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```` ````warn header="Rezervuoti pavadinimai" diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 35d7e01e3..792bce8ff 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 @@ # Duomenų tipai +<<<<<<< HEAD Kintamasis JavaScript gali savyje laikyti bet kokius duomenis. Kintamasis gali vienu momentu būti eilutė, o kitu numeris: +======= +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: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js // no error @@ -8,11 +16,17 @@ let message = "labas"; message = 123456; ``` +<<<<<<< HEAD Programinės kalbos, kurios tai leidžia yra vadinamos "dinamiškai tipizuotomis" (ang. "dynamically typed"), tai reiškia, kad duomenų tipai yra, tačiau kintamieji nėra prie jų pririšti. JavaScript turi septynis pagrindinius duomenų tipus. Čia mes juos peržvelgsime bendrai, o tolesniuose skyriuose apie kiekvieną pakalbėsime detaliau. ## Skaičius +======= +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 +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js let n = 123; @@ -44,6 +58,7 @@ Kartu su įprastiniais skaičiais, yra taip vadinamos "specialios skaitinės rei alert( "ne skaičius" / 2 ); // NaN, tokia dalyba yra klaidinga ``` +<<<<<<< HEAD `NaN` yra kabus. Bet kokie tolesni veiksmai su `NaN` grąžins `NaN`: ```js run @@ -51,6 +66,17 @@ Kartu su įprastiniais skaičiais, yra taip vadinamos "specialios skaitinės rei ``` Taigi, jeigu kažkur matematinėje formulėje yra `NaN` jis persiduoda į visus tolesnius rezultatus. +======= + `NaN` is sticky. Any further mathematical operation on `NaN` returns `NaN`: + + ```js run + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "not a number" / 2 - 1 ); // 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`). +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```smart header="Matematiniai veiksmai yra saugūs" Užsiimti matematika JavaScript yra "saugu". Galime daryti viską: dalinti iš nulio, elgtis su neskaitinėmis eilutėmis kaip su skaičiais ir t.t. @@ -62,14 +88,48 @@ Specialios skaitinės reikšmės priklauso "skaičių" tipui. Žinoma, jie nėra Daugiau apie darbą su skaičiais bus skyriuje . +<<<<<<< HEAD ## Eilutė +======= +## BigInt [#bigint-type] + +In JavaScript, the "number" type cannot represent integer values larger than (253-1) (that's `9007199254740991`), or less than -(253-1) for negatives. It's a technical limitation caused by their internal representation. + +For most purposes that's quite enough, but sometimes we need really big numbers, 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. + + +```smart header="Compatibility issues" +Right now, `BigInt` is supported in Firefox/Chrome/Edge/Safari, but not in IE. +``` + +You can check [*MDN* BigInt compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) to know which versions of a browser are supported. + +## String +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Eilutė JavaScript turi būti apsupta kabutėmis. ```js +<<<<<<< HEAD let str = "Labas"; let str2 = 'Viengubos kabutės taip pat tinka'; let phrase = `galima įterpti ${str}`; +======= +let str = "Hello"; +let str2 = 'Single quotes are ok too'; +let phrase = `can embed another ${str}`; +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ``` JavaScript turi 3-ų tipų kabutes. @@ -78,7 +138,11 @@ JavaScript turi 3-ų tipų kabutes. 2. Viengubos kabutės: `'Hello'`. 3. Atvirkštinės kabutės: `Labas`. +<<<<<<< HEAD Dvigubos ir viengubos kabutės yra "paprastosios" kabutės. Tarp jų nėra jokio skirtumo JavaScript. +======= +Double and single quotes are "simple" quotes. There's practically no difference between them in JavaScript. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Atvirkštinės kabutės yra kabutės su "išplėstu funkcionalumu". Jos leidžia mums įterpti kintamuosius ir išraiškas į pačią eilutę kai apsupame juos tokiais ženklais `${…}`, pavyzdžiui: @@ -101,6 +165,7 @@ alert( "rezultatas yra ${1 + 2}" ); // rezultatas yra ${1 + 2} (dvigubos kabutė Mes kalbėsime daugiau apie eilutes skyriuje . +<<<<<<< HEAD ```smart header="Nėra tokio tipo kaip *ženklas*." Kai kuriose kalbose yra specialus "ženklo" (ang. character) tipas skirtas vienetiniam ženklui. Pavyzdžiui tokiose kalbose kaip C arba Java toks ženklas yra vadinamas `char`. @@ -108,6 +173,15 @@ JavaScript tokio tipo nėra. Yra tik vienas tipas: `string`(eilutė). Eilutė ga ``` ## Loginis tipas +======= +```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) +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Loginis tipas (ang. boolean) turi tik dvi reikšmes: `true` (tiesa) ir `false`(netiesa). @@ -144,7 +218,11 @@ JavaScript `null` nėra "nuoroda į neegzistuojantį objektą" arba į "nulinę Tai tik speciali vertė, kuri atstovauja "nieką", "tuštumą" arba "vertė nežinoma". +<<<<<<< HEAD Kodas viršuje reiškia, kad `age` nėra žinomas arba tuščias dėl neaiškios priežasties. +======= +The code above states that `age` is unknown. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## Vertė "undefined" @@ -155,35 +233,56 @@ Ypatingoji vertė `undefined` (neapibrėžtas) taip pat yra išskirtinė, nes tu Jeigu kintamasis deklaruotas, bet nepriskirtas, tada jo vertė yra `undefined`: ```js run -let x; +let age; +<<<<<<< HEAD alert(x); // parodo "undefined" ``` Techniškai yra įmanoma priskirti `undefined` vertę bet kuriam kintamajam: +======= +alert(age); // shows "undefined" +``` + +Technically, it is possible to explicitly assign `undefined` to a variable: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js run -let x = 123; +let age = 100; -x = undefined; +// change the value to undefined +age = undefined; -alert(x); // "undefined" +alert(age); // "undefined" ``` +<<<<<<< HEAD ...Bet mes nerekomenduojame to daryti. Dažniausiai tam, kad priskirtume "tuščią" ar "nežinomą" vertę kintamajam, mes naudojame `null`, o `undefined` naudojame patikrinimams ar kintamajam buvo priskirta vertė. +======= +...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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## Objektai ir Simboliai Objekto `object` tipas yra ypatingas. +<<<<<<< HEAD Visi kiti tipai vadinami "primityviais", nes jų vertė gali turėti tik vieną dalyką (nesvarbu ar tai eilutė, numeris ar kita). Tuo tarpu objektai naudojami saugoti duomenų kolekcijas ir daug sudėtingesnius darinius. Apie juos kalbėsime vėliau skyriuje kai sužinosime daugiau apie primityvius tipus. Simbolio `symbol` tipas yra skirtas sukurti unikalius identifikatorius skirtus objektams. Paminėjome juos tik dėl užbaigtumo, bet labiau juos studijuosime po objektų. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## Operatorius typeof [#type-typeof] Operatorius `typeof` grąžina argumento tipą. Jis naudingas kai mes norime išskirtinai apdoroti skirtingų tipų vertes arba norime greitai patikrinti tipą. +<<<<<<< HEAD Jis palaiko dviejų formų sintaksę: 1. Kaip operatorius: `typeof x`. @@ -192,12 +291,17 @@ Jis palaiko dviejų formų sintaksę: Kitais žodžiais, jis veikia su skliausteliais ar be jų. Rezultatas toks pats. Šaukimas `typeof x` grąžina eilutę su tipo pavadinimu: +======= +A call to `typeof x` returns a string with the type name: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js typeof undefined // "undefined" typeof 0 // "number" +typeof 10n // "bigint" + typeof true // "boolean" typeof "foo" // "string" @@ -219,13 +323,29 @@ typeof alert // "function" (3) Paskutinės trys eilės gali reikalauti papildomo paaiškinimo: +<<<<<<< HEAD 1. `Math` yra įrašyta (ang. built-in) matematinė operacija. Apie ją sužinosime skyriuje . Čia ji yra tik kaip objekto pavyzdys. 2. Rezultatas iš `typeof null` yra `"object"`. Tai nėra tiesa. Tai yra oficialiai pripažinta `typeof` klaida, palikta dėl suderinamumo. Žinoma, kad `null` nėra objektas. Tai yra ypatinga vertė su atskiru tipu. Tad dar kartą, tai yra kalbos klaida. 3. Rezultatas iš `typeof alert` yra `"function"`, nes `alert` ir yra funkcija. Funkcijas studijuosime sekančiuose skyriuose kur sužinosime, kad JavaScript neturi atskiro ypatingo "funkcijos" tipo. Funkcijos priklauso prie objekto tipo. Bet `typeof` jas vertina kitaip, grąžindamas `"function"`. Tai nėra visiškai teisinga, bet praktiškai labai patogu. +======= +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. +```smart header="The `typeof(x)` syntax" +You may also come across another syntax: `typeof(x)`. It's the same as `typeof x`. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 + +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. +``` ## Santrauka +<<<<<<< HEAD JavaScript turi 7 pagrindinius duomenų tipus. - `number` skirta bet kokio tipo skaičiams: sveikiems ir slankiojančio kablelio skaičiams. @@ -235,11 +355,29 @@ JavaScript turi 7 pagrindinius duomenų tipus. - `undefined` nepriskirtoms vertėms -- atskiras tipas turintis vieną vertę `undefined`. - `object` skirta sudėtingesnėms duomenų struktūroms. - `symbol` skirta unikaliems identifikatoriams. +======= +There are 8 basic data types in JavaScript. + +- `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253-1). +- `bigint` is 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`. +- `object` for more complex data structures. +- `symbol` for unique identifiers. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Operatorius `typeof` leidžia matyti, kuris tipas yra saugomas kintamajame. +<<<<<<< HEAD - Dvi formos: `typeof x` arba `typeof(x)`. - Grąžina eilutę su tipo pavadinimu, kaip pavyzdžiui `"string"`. - Kai yra `null` grąžina `"object"` -- klaida kalboje, nes tai iš tikrųjų nėra objektas. +======= +- 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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Kituose skyriuose susikoncentruosime prie primityvių verčių, o kai su jomis būsime pažįstami, pereisime prie objektų. 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 70% 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 35b35f7f5..2421970db 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 @@ # Interakcija: alert, prompt, confirm +<<<<<<< HEAD:1-js/02-first-steps/09-alert-prompt-confirm/article.md Šioje pamokų dalyje kalbėsime apie JavaScript kalbą "tokia kokia ji yra", be aplinkai būdingų pataisymų. Bet vis dar naudosime naršykles kaip mūsų pavyzdinę aplinką, tad turėtume žinoti bent kelias vartotojo sąsajos (ang. user-interface) savybes. Šiame skyriuje susipažinsime su šiomis naršyklės funkcijomis `alert`, `prompt` ir `confirm`. @@ -13,6 +14,13 @@ alert(message); ``` Parodo žinutę ir sustabdo skriptą iki kol vartotojas paspaus "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". +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/06-alert-prompt-confirm/article.md Pavyzdžiui: @@ -20,7 +28,11 @@ Pavyzdžiui: alert("Labas"); ``` +<<<<<<< HEAD:1-js/02-first-steps/09-alert-prompt-confirm/article.md Miniatiūrinis langelis su žinute yra vadinamas *modaliniu langeliu* (ang. *modal window*). Žodis "modalinis" reiškia, kad lankytojas negali naudotis likusiu puslapiu, spausti kitų mygtukų ir t.t. kol nesusitvarkė su langeliu. Šiuo atveju -- iki kol paspaus "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". +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/06-alert-prompt-confirm/article.md ## prompt @@ -38,7 +50,15 @@ Parodo modalinį langelį su paprasta tekstine žinute, įvesties laukeliu lanky `default` : Nebūtinas antras parametras, pradinė vertė įvesties laukeliui. +<<<<<<< HEAD:1-js/02-first-steps/09-alert-prompt-confirm/article.md Lankytojas gali ką nors įvesti laukelyje ir paspausti OK. Arba jie gali atšaukti paspausdami Cancel ar klaviatūroje `key:Esc` mygtuką. +======= +```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`. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/06-alert-prompt-confirm/article.md Iššauktas `prompt` grąžina tekstą iš įvesties laukelio arba `null`, jeigu įvestis buvo atšaukta. 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 85% 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 657af44f0..1ba702bf6 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 @@ # Tipų konversijos +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md Dažniausiai operatoriai ir funkcijos automatiškai pakeičia jiems duotas vertes į teisingą tipą. +======= +Most of the time, operators and functions automatically convert the values given to them to the right type. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/07-type-conversions/article.md Pavyzdžiui `alert` automatiškai paverčia bet kokią jiems duotą vertę į eilutės tipą, kad galėtų jį parodytų. Matematinės operacijos pakeičia vertes į skaičius. Yra tokių konkrečių atvejų kai mums reikia vertę pakeisti į atitinkamą tipą. +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md ```smart header="Dar nekalbame apie objektus" Šiame skyriuje kol kas dar nekalbėsime apie objektus. Vietoje to studijuosime primityvius tipus. Vėliau kai sužinosime daugiau apie objektus, pamatysime kaip objektų keitimai veikia skyriuje . +======= +```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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/07-type-conversions/article.md ``` ## Eilutės konversijos @@ -81,6 +92,7 @@ alert( Number(false) ); // 0 Atkreipkite dėmesį, kad `null` ir `undefined` elgiasi kitaip šiuo atveju: `null` tampa nuliu kai `undefined` tampa `NaN`. +<<<<<<< HEAD:1-js/02-first-steps/06-type-conversions/article.md ````smart header="Sudėtis '+' sujungia eilutes" Beveik visos matematinės operacijos paverčia vertes numeriais. Svarbi išimtis yra sudėtis `+`. Jeigu viena iš verčių yra eilutės, kita taip pat paverčiama eilute. @@ -93,6 +105,9 @@ alert( '1' + 2 ); // '12' (eilutė iš kairės) Taip įvyksta tik tokiu atveju kai vienas iš argumentų yra eilutė. Kitu atveju vertės konvertuojamos į skaičius. ```` +======= +Most mathematical operators also perform such conversion, we'll see that in the next chapter. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/07-type-conversions/article.md ## Loginės konversijos 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/06-type-conversions/1-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md similarity index 93% rename from 1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md rename to 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index 4e5c06568..b90ed81cc 100644 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -9,9 +9,8 @@ true + false = 1 "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN -7 / 0 = Infinity -" -9 " + 5 = " -9 5" // (3) -" -9 " - 5 = -14 // (4) +" -9 " + 5 = " -9 5" // (3) +" -9 " - 5 = -14 // (4) null + 1 = 1 // (5) undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md similarity index 98% rename from 1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md rename to 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md index 6b0320bcd..cccc6a83e 100644 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/08-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/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 66% rename from 1-js/02-first-steps/07-operators/article.md rename to 1-js/02-first-steps/08-operators/article.md index 021c62b65..a8e1321e5 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 # Operatoriai +======= +# Basic operators, maths +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md Mes žinome daug operatorių iš mokyklos. Tokie kaip sudėtis `+`, daugyba `*`, atimtis `-` ir taip toliau. +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Šiame skyriuje, susikoncentruosime prie tokio operatorių aspekto, kuris nėra aptariamas mokykloje. +======= +In this chapter, we’ll start with simple operators, then concentrate on JavaScript-specific aspects, not covered by school arithmetic. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md ## Terminai: "unary", "binary", "operand" @@ -28,9 +36,65 @@ Prieš tęsiant, išsiaiškinkime įprastinę terminologiją. Formaliai aukščiau esančiame pavyzdyje mes turime du skirtingus operatorius, kurie dalinasi tuo pačiu simboliu: neigimo operatoriumi, t.y. unariniu operatoriumi, kuris pakeičia simbolį, ir atimties operatoriumi, t.y. binarinu operatoriumi, kuris atima vieną skaičių iš kito. +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md ## Eilutės sujungimas, binarinis + Dabar susipažinkime su ypatingomis JavaScript savybėmis, kurios pranoksta mokyklos aritmetiką. +======= +## 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, a remainder of 5 divided by 2 +alert( 8 % 3 ); // 2, a remainder of 8 divided by 3 +``` + +### 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 features of JavaScript operators that are beyond school arithmetics. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md Dažniausiai operatorius pliusas `+` sumuoja skaičius. @@ -41,7 +105,11 @@ let s = "my" + "string"; alert(s); // mystring ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Atkreipkite dėmesį, jeigu vienas iš operandų yra eilutė, tai kitas taip pat paverčiamas eilute. +======= +Note that if any of the operands is a string, then the other one is converted to a string too. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md Pavyzdžiui: @@ -50,22 +118,40 @@ alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Kaip matote nėra svarbu ar pirmasis operandas ar antrasis yra eilutė. Taisyklė paprasta: jeigu bent vienas operandas yra eilutė, kitas taip pat paverčiamas eilute. Tačiau pastebėkite, kad operacijos vyksta iš kairės į dešinę. Jeigu pirmiau yra du numeriai, kuriuos seka eilutė, numeriai bus susumuoti ir tik tada paversti į eilutę: +======= +See, it doesn't matter whether the first operand is a string or the second one. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md +Here's a more complex example: ```js run alert(2 + 2 + '1' ); // "41" bet ne "221" ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Eilutės sujungimas ir konversija yra ypatinga binarinio pliuso savybė `+`. Kiti aritmetiniai operatoriai veikia tik su skaičiais ir visada konvertuoja savo operandus į skaičius. Pavyzdžiui, atimtis ir dalyba: +======= +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'`. + +```js run +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: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md ```js run -alert( 2 - '1' ); // 1 -alert( '6' / '2' ); // 3 +alert( 6 - '2' ); // 4, converts '2' to a number +alert( '6' / '2' ); // 3, converts both operands to numbers ``` ## Skaitinė konversija, unarinis + @@ -133,11 +219,16 @@ Skliausteliai perrašo bet kokią pirmenybę, tad jeigu mums netinka numatyta tv JavaScript turi daug operatorių. Kiekvienas operatorius turi atitinkamą pirmenybės numerį. Tas kas turi aukštenį numerį įvykdomas pirmiau. Jeigu prioritetas vienodas, įvykdymo eilė yra iš kairės į dešinę. +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Štai ištrauka iš [pirmenybės lentelės](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence) (jums nereikia šito prisiminti, bet žinokite, kad unariniai operatoriai yra aukščiau už atitinkamus binarinius operatorius): +======= +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): +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md | Pirmenybė | Pavadinimas | Ženklas | |------------|------|------| | ... | ... | ... | +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md | 16 | unarinis pliusas | `+` | | 16 | unarinis minusas | `-` | | 14 | daugyba | `*` | @@ -149,10 +240,28 @@ JavaScript turi daug operatorių. Kiekvienas operatorius turi atitinkamą pirmen | ... | ... | ... | Kaip matome, "unarinis pliusas" turi pirmenybę `16`, kuri yra didesnė nei "sudėties" `13` (binarinis pliusas). Dėl tos priežasties, išraiškoje `"+apples + +oranges"`, unariniai pliusai suveikia pirmiau už sudėtį. +======= +| 15 | unary plus | `+` | +| 15 | unary negation | `-` | +| 14 | exponentiation | `**` | +| 13 | multiplication | `*` | +| 13 | division | `/` | +| 12 | addition | `+` | +| 12 | subtraction | `-` | +| ... | ... | ... | +| 2 | assignment | `=` | +| ... | ... | ... | + +As we can see, the "unary plus" has a priority of `15` which is higher than the `12` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md ## Asignavimas +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Atkreipkite dėmesį, kad asignavimas (kitaip užduoties operatorius) `=` taip pat yra operatorius. Lentelėje jis įrašytas kaip turintis labai žemą pirmenybę `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`. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md Dėl tos priežasties kai mes priskiriame kintamąjį, kaip `x = 2 * 2 + 1`, visų pirma suskaičiuojama vertė ir tik tada `=` yra įvertinamas bei rezultatas patalpinamas į `x`. @@ -162,6 +271,7 @@ let x = 2 * 2 + 1; alert( x ); // 5 ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Asignavimus įmanoma sujungti į grandinę: ```js run @@ -180,6 +290,13 @@ Sujungti į grandinę asignavimai įvertinami iš dešinės į kairę. Visų pir ````smart header="Užduoties operatorius `\"=\"` grąžina vertę" Operatorius visada grąžina vertę. Tai yra akivaizdu tokiems atvejams kaip sudėtis `+` arba daugyba `*`. Bet asignavimas taip pat seka šita taisykle. +======= +### Assignment = returns a value + +The fact of `=` being an operator, not a "magical" language construct has an interesting implication. + +All operators in JavaScript return a value. That's obvious for `+` and `-`, but also true for `=`. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md Šaukimas `x = value` įrašo `value` į `x` *ir tada ją grąžina*. @@ -199,6 +316,7 @@ alert( c ); // 0 Pavyzdyje aukščiau, išraiškos `(a = b + 1)` rezultatas yra vertė, kuri buvo priskirta `a` (tai yra `3`). Vėliau ji naudojama tolesniuose vertinimuose. +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Koks juokingas kodas, juk taip? Turėtume suprasti kaip jis veikia, nes kartais tokius dalykus randame JavaScript bibliotekose, bet patys tokių geriau nerašykite. Tokie triukai tikrai nepaverčia kodo aiškesniu ir įskaitomesniu. ```` @@ -214,8 +332,40 @@ Pavyzdžiui: alert( 5 % 2 ); // 1 yra liekana kai 5 padalinami iš 2 alert( 8 % 3 ); // 2 yra liekana kai 8 padalinami iš 3 alert( 6 % 3 ); // 0 yra liekana kai 6 padalinami iš 3 +======= +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 +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; +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md +``` +That's easier to read, especially when eye-scanning the code fast. + +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md ## Kėlimas laipsniu ** Kėlimo laipsniu (ang. exponentiation) operatorius `**` yra naujas kalbos priedas. @@ -223,13 +373,31 @@ Kėlimo laipsniu (ang. exponentiation) operatorius `**` yra naujas kalbos prieda Natūraliajam skaičiui `b`, kai `a ** b` rezultatas yra `a` padaugintas iš savęs `b` kartus. Pavyzdžiui: +======= +## Modify-in-place + +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; +``` + +This notation can be shortened using the operators `+=` and `*=`: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md ```js run -alert( 2 ** 2 ); // 4 (2 * 2) -alert( 2 ** 3 ); // 8 (2 * 2 * 2) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) +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 ``` +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Operatorius veikia ir su trupmenomis. Pavyzdžiui: @@ -237,11 +405,23 @@ Pavyzdžiui: ```js run alert( 4 ** (1/2) ); // 2 (laipsnis 1/2 yra taip pat kaip traukti iš šaknies, tai paprasta matematika) alert( 8 ** (1/3) ); // 2 (laipsnis 1/3 taip pats kaip kubinė šaknis) +======= +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; + +alert( n ); // 16 (right part evaluated first, same as n *= 8) +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md ``` ## Padidėjimas/sumažėjimas - + Skaičiaus padidinimas arba sumažinimas vienetu yra viena dažniausiai naudojamų įprastinių skaičių operacijų. @@ -368,6 +548,7 @@ Operatorių sąrašas: - RIGHT SHIFT ( `>>` ) - ZERO-FILL RIGHT SHIFT ( `>>>` ) +<<<<<<< HEAD:1-js/02-first-steps/07-operators/article.md Šie operatoriai naudojami labai retai. Kad juos suprastume, mums reikia pasinerti į žemo-lygio skaičių taikimą ir tai daryti šiuo metu nebūtų optimalu, juolabiau, kad mums jų greitu laiku neprireiks. Jeigu jums įdomu, galite daugiau paskaityti apie [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) MDN straipsnyje. Daug praktiškiau tai padaryti kai iškils tikras poreikis. ## Keitimas vietoje (ang. Modify-in-place) @@ -403,6 +584,9 @@ n *= 3 + 5; alert( n ); // 16 (pirma įvykdoma dešinė pusė, tas pats kaip būtų 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) chapter on MDN when a need arises. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/08-operators/article.md ## Kablelis 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 51% 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 863b3d211..bbae3076a 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 Kai kurios priežastys: +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md 1. Akivaizdžiai tiesa. 2. Žodynėlio palyginimas, tad netiesa. 3. Vėlgi, žodynėlio palyginimas, tad pirmasis ženklas eilutėje `"2"` yra didenis nei pirmasis ženklas eilutėje `"1"`. @@ -19,3 +20,12 @@ Kai kurios priežastys: 5. Griežta lygybė yra griežta. Skirtingi tipai abiejose pusėse atveda prie netiesos. 6. Panašiai į `(4)`, `null` yra lygus tik `undefined`. 7. Griežta skirtingų tipų lygybė. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834: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 53a0ac3cd..1f7595493 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 @@ # Palyginimai +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/article.md Iš matematikos mes žinome daug palyginimo operatorių: - Daugiau/mažiau negu: a > b, a < b. - Daugiau/mažiau arba lygu negu: a >= b, a <= b. - Lygu: `a == b` (atkreipkite dėmesį į dvigubos lygybės ženklą `=`. Vienas ženklas `a = b` reikštų priskyrimą). - Nelygus. Matematikoje toks ženklas yra , bet JavaScript jis rašomas kaip asigmentas su šauktuku prieš jį: 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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/09-comparison/article.md ## Loginė vertė yra rezultatas +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/article.md Kaip ir visi kiti operatoriai, palyginimas grąžina vertę. Šiuo atveju ta vertė yra loginė. +======= +All comparison operators return a boolean value: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/09-comparison/article.md - `true` -- reiškia "taip", "teisingai" arba "tiesa". - `false` -- reiškia "ne", "neteisingai" arba "netiesa". @@ -51,7 +70,13 @@ Algoritmas eilučių palyginimui yra labai paprastas: 4. Pakartoti iki vienos iš eilučių pabaigos. 5. Jeigu abi eilutės baigiasi tuo pačiu metu, jos yra vienodos. Kitu atveju ilgesnė eilutė yra didesnė. +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/article.md Pavyzdyje aukščiau, palyginimas `'Z' > 'A'` gauna atsakymą pirmame žingsnyje, kai tuo tarpu `"Glow"` ir `"Glee"` yra lyginami ženklas po ženklo: +======= +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: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/09-comparison/article.md 1. `G` tas pats kaip `G`. 2. `l` tas pats kaip `l`. @@ -192,6 +217,7 @@ Gauname tokius rezultatas, nes: - Palyginimai `(1)` ir `(2)` grąžina `false`, nes `undefined` paverčiamas į `NaN`, o `NaN` yra ypatinga skaitinė vertė, kuri visoms vertėms grąžina `false`. - Lygybės patikrinimas `(3)` grąžina `false`, nes `undefined` yra lygus tik `null`, `undefined` ir jokiai kitai vertei. +<<<<<<< HEAD:1-js/02-first-steps/08-comparison/article.md ### Išvenkite problemų Kodėl mes peržiūrėjome tokius pavyzdžius? Ar turėtume tokias keistenybes visada prisiminti? Nebūtinai. Tiesą sakant tokie triukai taps pažįstamais su laiku, bet yra būdas kaip išvengti su jais susijusių problemų: @@ -199,6 +225,14 @@ Kodėl mes peržiūrėjome tokius pavyzdžius? Ar turėtume tokias keistenybes v Kiekvieną palyginimą susijusį su `undefined/null` vertinkite atsargiai, išskyrus su griežta lygybe `===`. Nenaudokite palyginimų `>= > < <=` su kintamuoju, kuris gali būti `null/undefined`, nebent tikrai žinote ką darote. Jeigu kintamasis gali turėti tokias vertybes, patikrinkite jas atskirai. +======= +### 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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/09-comparison/article.md ## Santrauka 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 ae465789b..7d4fdd2a7 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 Naudodami konstruktą `if..else`, parašykite kodą, kuris klausia: 'Koks yra "oficialus JavaScript pavadinimas?' +<<<<<<< HEAD Jeigu lankytojas įveda "ECMAScript", tada gaunamas "Teisingai!", kitu atveju -- gaunamas: "Nežinojote? ECMAScript!" +======= +If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "You don't know? ECMAScript!" +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ![](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 7862079e4..1a94b57da 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 # Sąlyginiai operatoriai: if, '?' +======= +# Conditional branching: if, '?' +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Kartais mums tenka atlikti skirtingus veiksmus atitinkančius skirtingas sąlygas. @@ -68,7 +72,11 @@ if (cond) { ## Išlyga "else" +<<<<<<< HEAD Teiginys `if` gali turėti išlygos "else" rinkinį. Jis įvykdomas kai sąlyga yra false. +======= +The `if` statement may contain an optional "else" block. It executes when the condition is falsy. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Pavyzdžiui: ```js run 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 5f245ea4c..f9ee41277 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) ); Šaukimas `alert` negrąžina jokios vertės. Arba kitaip sakant, jis grąžina `undefined`. +<<<<<<< HEAD 1. Pirmasis ARBA `||` įvertina kairėje esantį operandą `alert(1)`. Jis parodo žinutę su `1`. 2. `alert` grąžina `undefined`, tad ARBA eina prie sekančio operando ieškodamas truthy vertės. 3. Antrasis operandas `2` yra truthy, tad operacija sustabdoma, grąžinamas `2` ir parodomas per išorinį 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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Nebebus `3`, nes įvertinimas nepasiekia `alert(3)`. 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 8999dbb78..218762f10 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,6 +4,10 @@ importance: 3 # Patikrinkite intervalą +<<<<<<< HEAD Parašykite "if" sąlygą, kuri patikrintų ar `age` yra tarp `14` ir `90` įskaitant. +======= +Write an `if` condition to check that `age` is between `14` and `90` inclusively. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 "Įskaitant" reiškia, kad `age` gali pasiekti `14` ir `90` ribas. 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 5787cc419..11b9f7223 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,6 +4,10 @@ importance: 3 # Patikrinkite už intervalo ribų +<<<<<<< HEAD Parašykite `if` sąlygą, kuri patikrintų ar `age` nėra tarp 14 ir 90 įskaitant. +======= +Write an `if` condition to check that `age` is NOT between `14` and `90` inclusively. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Sukurkite du variantus: vieną naudojant NE `!`, kitą nenaudojant jo. 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 58898b294..bf0f53eab 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 @@ -3,20 +3,32 @@ ```js run demo let userName = prompt("Kas čia?", ''); -if (userName == 'Admin') { +if (userName === 'Admin') { let pass = prompt('Slaptažodis?', ''); +<<<<<<< HEAD if (pass == 'TheMaster') { alert( 'Sveiki!' ); } else if (pass == '' || pass == null) { alert( 'Atšaukta' ); +======= + if (pass === 'TheMaster') { + alert( 'Welcome!' ); + } else if (pass === '' || pass === null) { + alert( 'Canceled' ); +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 } else { alert( 'Neteisingas slaptažodis' ); } +<<<<<<< HEAD } else if (userName == '' || userName == null) { alert( 'Atšaukta' ); +======= +} else if (userName === '' || userName === null) { + alert( 'Canceled' ); +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 } else { alert( "Aš jūsų nepažįstu" ); } 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 1dbec4e4a..b814d5e7f 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 @@ # Loginiai operatoriai +<<<<<<< HEAD JavaScript yra trys loginiai operatoriai: `||` (OR - arba), `&&` (AND - ir), `!` (NOT - ne). +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Nors jie vadinami "loginiais", juos galima naudoti su bet kokio tipo vertėmis, ne vien loginėmis. Jų rezultatas taip pat gali būti bet kokio tipo. @@ -64,7 +68,11 @@ if (hour < 10 || hour > 18 || isWeekend) { } ``` +<<<<<<< HEAD ## ARBA "||" suranda pirmąją truthy vertę +======= +## OR "||" finds the first truthy value [#or-finds-the-first-truthy-value] +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Aukščiau apibūdinta logika yra klasikinė. Dabar pridėkime "ekstra" JavaScript savybių. @@ -84,32 +92,53 @@ ARBA `||` operatorius atlieka sekančius veiksmus: Vertė grąžinama savo originalioje formoje be konversijos. +<<<<<<< HEAD Kitaip sakant ARBA `"||"` grandinė grąžina pirmąją truthy vertę arba pačią paskutinę vertę, jeigu teisinga vertė nebuvo rasta. +======= +In other words, a chain of OR `||` returns the first truthy value or the last one if no truthy value is found. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Pavyzdžiui: ```js run +<<<<<<< HEAD alert( 1 || 0 ); // 1 (1 yra truthy) alert( true || 'nesvarbu kas' ); // (true yra truthy) alert( null || 1 ); // 1 (1 yra pirmoji truthy vertė) alert( null || 0 || 1 ); // 1 (pirmoji truthy vertė) alert( undefined || null || 0 ); // 0 (visos falsy, grąžinama paskutinė vertė) +======= +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) +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ``` Tai veda prie labai įdomių panaudojimo būdų, lyginant su "grynu, klasikiniu, loginiu ARBA". 1. **Gaunant pirmą truthy vertę iš kintamųjų ar išraiškų sąrašo.** +<<<<<<< HEAD Įsivaizduokite, kad turime sąrašą kintamųjų, kuriuose arba yra duomenys arba `null/undefined`. Kaip mums surasti pirmąjį su duomenimis? Galime naudoti ARBA `||`: +======= + 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): +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js run - let currentUser = null; - let defaultUser = "John"; + let firstName = ""; + let lastName = ""; + let nickName = "SuperCoder"; *!* +<<<<<<< HEAD let name = currentUser || defaultUser || "bevardis"; */!* @@ -124,30 +153,47 @@ Tai veda prie labai įdomių panaudojimo būdų, lyginant su "grynu, klasikiniu, Tai labai akivaizdu kai išraiška, duota kaip antras argumentas, turi tokį šalutinį efektą kaip kintamojo priskyrimą. Pavyzdyje žemiau `x` nėra priskiriamas: +======= + alert( firstName || lastName || nickName || "Anonymous"); // SuperCoder + */!* + ``` - ```js run no-beautify - let x; + 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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 - *!*true*/!* || (x = 1); + 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. + +<<<<<<< HEAD alert(x); // undefined, nes (x = 1) nėra įvertinamas ``` Tačiau jeigu pirmas argumentas yra `false`, `||` įvertina antrąjį, tada įvykdomas priskyrimas: +======= + In the example below, only the second message is printed: +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ```js run no-beautify - let x; - - *!*false*/!* || (x = 1); - - alert(x); // 1 + *!*true*/!* || alert("not printed"); + *!*false*/!* || alert("printed"); ``` +<<<<<<< HEAD Asignavimas yra paprastas atvejis. Tam gali būti šalutinių efektų, kurie nepasirodys, jeigu įvertinimas jų nepasieks. Kaip matote toks naudojimo atvejis yra "trumpesnis būdas" nei `if`". Pirmasis operandas paverčiamas logine verte, antrasis įvertinamas. Vis dėlto dažniausiai, geriau naudoti "įprastinį" `if`, kad kodas būtų lengviau įskaitomas, bet kartais toks būdas gali būti naudingas. +======= + 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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## && (IR) @@ -236,7 +282,12 @@ IR `&&` operatoriaus pirmenybė yra aukštesnė už ARBA `||`. Tad kodas `a && b || c && d` būtų tas pats lyg `&&` išraiškos būtų tarp skliaustelių: `(a && b) || (c && d)`. ```` +<<<<<<< HEAD Taip pat kaip ir ARBA, operatorius IR `&&` kartais gali pakeisti `if`. +======= +````warn header="Don't replace `if` with `||` or `&&`" +Sometimes, people use the AND `&&` operator as a "shorter way to write `if`". +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Pavyzdžiui: @@ -253,6 +304,7 @@ Tad mes tiesiog turime analogą šiai išraiškai: ```js run let x = 1; +<<<<<<< HEAD if (x > 0) { alert( 'Didesnis nei nulis!' ); } @@ -261,6 +313,14 @@ if (x > 0) { Variantas su `&&` atrodo trumpesnis. Bet `if` yra labiau akivaizdus ir dėl to yra geriau įskaitomas. Mes rekomenduojame naudoti kiekvieną konstruktą pagal paskirtį: naudokite `if` jeigu norite if, o `&&` jeigu norite IR. +======= +if (x > 0) alert( 'Greater than zero!' ); +``` + +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. +```` + +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## ! (NE) 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..6f3e969f9 --- /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. We'll say that an expression 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 a potentially undefined variable. + +For example, here we show `user` if defined, otherwise `Anonymous`: + +```js run +let user; + +alert(user ?? "Anonymous"); // Anonymous (user not defined) +``` + +Here's the example with `user` assigned to a name: + +```js run +let user = "John"; + +alert(user ?? "Anonymous"); // John (user defined) +``` + +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 enter a value. + +We'd like to display the user name using one of these variables, or show "Anonymous" if all of them aren't defined. + +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 exists 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 `4` 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 if we'd like to choose a value with `??` in an expression with other operators, consider adding parentheses: + +```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 the same as this (probably 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 58% 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 af771bd34..75a17f9cc 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,7 +9,12 @@ do { Ciklas`do..while` pakartoja kol abu patikrinimai yra truthy: +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/6-repeat-until-correct/solution.md 1. Patikrinimas dėl `num <= 100` -- tai yra, įvesta vertė vis dar nėra didesnė už `100`. 2. Patikrinimas `&& num` yra neteisingas, jeigu `num` yra `null` arba tuščia eilutė. Tada `while` ciklas taip pat sustoja. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md P.S. Jeigu `num` yra `null` tada `num <= 100` yra `true`, tad be antro patikrinimo ciklas nesustotų, jeigu lankytojas paspaustų CANCEL. Abu patikrinimai yra reikalingi. 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 86% 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 894181366..6a14b7b00 100644 --- a/1-js/02-first-steps/12-while-for/article.md +++ b/1-js/02-first-steps/13-while-for/article.md @@ -106,10 +106,17 @@ Ištirkime teiginį `for` dalis po dalies: | dalis | | | |-------|----------|----------------------------------------------------------------------------| +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md | pradžia | `i = 0` | Įvykdomas vieną kartą pradedant ciklą. | | salyga | `i < 3`| Patikrinama prieš kiekvieną ciklo iteraciją. Jeigu netiesa, ciklas sustoja. | | korpusas | `alert(i)`| Įvykdomas vėl ir vėl kol sąlyga yra truthy. | | žingsnis | `i++` | Įvykdomas po korpuso per kiekvieną iteraciją. | +======= +| 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. | +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/13-while-for/article.md Įprastinio ciklo algoritmas veikia taip: @@ -256,7 +263,7 @@ Lyginėms `i` vertėms, direktyva `continue` nustoja vykdyti korpusą ir perleid ````smart header="Direktyva `continue` sumažina matrioškinį kodą (ang. nesting)" Ciklas, kuris parodo nelygines vertes gali atrodyti ir taip: -```js +```js run for (let i = 0; i < 10; i++) { if (i % 2) { @@ -268,7 +275,11 @@ for (let i = 0; i < 10; i++) { Iš techninės perspektyvos tai yra visiškai identiškas kodas aukščiau esančiam pavyzdžiui. Žinoma, mes galime tiesiog apgobti `if` rinkinį vietoje to, kad naudotume `continue`. +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md Bet to pašalinis efektas yra papildomas matrioškinis lygis (šaukimas `alert` viduje riestinių skliaustų). O jeigu kodas `if` viduje yra ilgesnis nei kelios eilės, tai apsunkina skaitomumą. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/13-while-for/article.md ```` ````warn header="Jokių `break/continue` dešinėje '?' pusėje" @@ -318,7 +329,11 @@ alert('Baigta!'); Mums reikia tokio būdo, kuris sustabdytų procesą, jeigu lankytojas atšaukia įvedimą. +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md Įprastinis `break` sekantis po `input` sustabdytų tik vidinį ciklą. To neužtenka--į pagalba ateina etiketės! +======= +The ordinary `break` after `input` would only break the inner loop. That's not sufficient -- labels, come to the rescue! +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/13-while-for/article.md *Etikėtė* tai toks identifikatorius su dvitaškiu prieš ciklą: ```js @@ -363,12 +378,31 @@ Etiketės negali leisti peršokti į bet kurią arbitrišką kodo vietą. Pavyzdžiui tai nėra įmanoma: ```js +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md break etiketė; // neperšoka į etiketę žemiau +======= +break label; // jump to the label below (doesn't work) +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/13-while-for/article.md etiketė: for (...) ``` +<<<<<<< HEAD:1-js/02-first-steps/12-while-for/article.md Šaukimas `break/continue` įmanoma tik iš ciklo vidaus ir etiketė turi būti kažkur virš direktyvos. +======= +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. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/13-while-for/article.md ```` ## Santrauka 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 95% rename from 1-js/02-first-steps/13-switch/article.md rename to 1-js/02-first-steps/14-switch/article.md index 6391c365e..cf054988b 100644 --- a/1-js/02-first-steps/13-switch/article.md +++ b/1-js/02-first-steps/14-switch/article.md @@ -47,7 +47,11 @@ switch (a) { break; */!* case 5: +<<<<<<< HEAD:1-js/02-first-steps/13-switch/article.md alert( 'Per didelis' ); +======= + alert( 'Too big' ); +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834:1-js/02-first-steps/14-switch/article.md break; default: alert( "Tokios vertės nežinau" ); @@ -117,7 +121,7 @@ Keli `case` variantai, kurie dalinasi tuo pačiu kodu gali būti sugrupuoti. Pavyzdžiui, jeigu mes norime, kad tas pats kodas pasileistų byloms `case 3` ir `case 5`: ```js run no-beautify -let a = 2 + 2; +let a = 3; switch (a) { case 4: diff --git a/1-js/02-first-steps/14-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md similarity index 100% rename from 1-js/02-first-steps/14-function-basics/1-if-else-required/solution.md rename to 1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md 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 89% 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 c8ee9618f..e48502642 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,4 +14,4 @@ function checkAge(age) { } ``` -Note that the parentheses around `age > 18` are not required here. They exist for better readabilty. +Note that the parentheses around `age > 18` are not required here. They exist for better readability. 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 82% 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 b1881e311..b46f42924 100644 --- a/1-js/02-first-steps/14-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -20,10 +20,10 @@ function showMessage() { } ``` -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) and finally the code of the function, also named "the function body", between curly braces. +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. ```js -function name(parameters) { +function name(parameter1, parameter2, ... parameterN) { ...body... } ``` @@ -137,26 +137,23 @@ It's a good practice to minimize the use of global variables. Modern code has fe ## Parameters -We can pass arbitrary data to functions using parameters (also called *function arguments*) . +We can pass arbitrary data to functions using parameters. In the example below, the function has two parameters: `from` and `text`. ```js run -function showMessage(*!*from, text*/!*) { // arguments: from, text +function showMessage(*!*from, text*/!*) { // parameters: from, text 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? (**) ``` When the function is called in lines `(*)` and `(**)`, the given values are copied to local variables `from` and `text`. Then the function uses them. Here's one more example: we have a variable `from` and pass it to the function. Please note: the function changes `from`, but the change is not seen outside, because a function always gets a copy of the value: - ```js run function showMessage(from, text) { @@ -175,9 +172,21 @@ showMessage(from, "Hello"); // *Ann*: Hello alert( from ); // Ann ``` +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 parameter is not provided, then its value becomes `undefined`. +If a function is called, but an argument is not provided, then the corresponding value becomes `undefined`. For instance, the aforementioned function `showMessage(from, text)` can be called with a single argument: @@ -185,9 +194,9 @@ For instance, the aforementioned function `showMessage(from, text)` can be calle showMessage("Ann"); ``` -That's not an error. Such a call would output `"Ann: undefined"`. There's no `text`, so it's assumed that `text === undefined`. +That's not an error. Such a call would output `"*Ann*: undefined"`. As the value for `text` isn't passed, it becomes `undefined`. -If we want to use a "default" `text` in this case, then we can specify it after `=`: +We can specify the so-called "default" (to use if omitted) value for a parameter in the function declaration, using `=`: ```js run function showMessage(from, *!*text = "no text given"*/!*) { @@ -211,39 +220,55 @@ function showMessage(from, text = anotherFunction()) { ```smart header="Evaluation of default parameters" In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. -In the example above, `anotherFunction()` is called every time `showMessage()` is called without the `text` parameter. +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 old-style" -Old editions of JavaScript did not support default parameters. So there are alternative ways to support them, that you can find mostly in the old scripts. +### Alternative default parameters -For instance, an explicit check for being `undefined`: +Sometimes it makes sense to assign default values for parameters not in the function declaration, but at a later stage. + +We can check if the parameter is passed during the function execution, by comparing it with `undefined`: + +```js run +function showMessage(text) { + // ... -```js -function showMessage(from, text) { *!* - if (text === undefined) { - text = 'no text given'; + if (text === undefined) { // if the parameter is missing + text = 'empty message'; } */!* - alert( from + ": " + text ); + alert(text); } + +showMessage(); // empty message ``` -...Or the `||` operator: +...Or we could use the `||` operator: ```js -function showMessage(from, text) { - // if text is falsy then text gets the "default" value - text = text || 'no text given'; +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 @@ -266,7 +291,7 @@ There may be many occurrences of `return` in a single function. For instance: ```js run function checkAge(age) { - if (age > 18) { + if (age >= 18) { *!* return true; */!* @@ -399,7 +424,7 @@ Functions that are used *very often* sometimes have ultrashort names. For example, the [jQuery](http://jquery.com) framework defines a function with `$`. The [Lodash](http://lodash.com/) library has its core function named `_`. -These are exceptions. Generally functions names should be concise and descriptive. +These are exceptions. Generally function names should be concise and descriptive. ``` ## Functions == Comments 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 87% 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 a8ccd6c6c..bca23ae51 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,9 @@ function sayHi() { There is another syntax for creating a function that is called a *Function Expression*. -It looks like this: +It allows us to create a new function in the middle of any expression. + +For example: ```js let sayHi = function() { @@ -20,9 +22,19 @@ let sayHi = function() { }; ``` -Here, the function is created and assigned to the variable explicitly, like any other value. No matter how the function is defined, it's just a value stored in the variable `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`". -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. We can even print out that value using `alert`: @@ -63,10 +75,10 @@ Here's what happens above in detail: 2. Line `(2)` copies it into the variable `func`. Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself. 3. Now the function can be called as both `sayHi()` and `func()`. -Note that we could also have used a Function Expression to declare `sayHi`, in the first line: +We could also have used a Function Expression to declare `sayHi`, in the first line: ```js -let sayHi = function() { +let sayHi = function() { // (1) create alert( "Hello" ); }; @@ -90,9 +102,9 @@ let sayHi = function() { }*!*;*/!* ``` -The answer is simple: -- There's no need for `;` at the end of code blocks and syntax structures that use them like `if { ... }`, `for { }`, `function f { }` etc. -- A Function Expression is used inside the statement: `let sayHi = ...;`, as a value. It's not a code block, but rather an assignment. The semicolon `;` is recommended at the end of statements, no matter what the value is. So the semicolon here is not related to the Function Expression itself, it just terminates the statement. +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. ```` ## Callback functions 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 3ea112473..041db18bc 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 90% 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 2f44db27e..e18c08a83 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 @@ Replace Function Expressions with arrow functions in the code below: ```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 89% 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 02090f3c1..c28b56c56 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,15 @@ There's another very simple and concise syntax for creating functions, that's of It's called "arrow functions", because it looks like this: ```js -let func = (arg1, arg2, ...argN) => expression +let func = (arg1, arg2, ..., argN) => 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. +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. In other words, it's the shorter version of: ```js -let func = function(arg1, arg2, ...argN) { +let func = function(arg1, arg2, ..., argN) { return expression; }; ``` @@ -33,7 +33,7 @@ let sum = function(a, b) { alert( sum(1, 2) ); // 3 ``` -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. +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. - If we have only one argument, then parentheses around parameters can be omitted, making that even shorter. @@ -67,7 +67,7 @@ let welcome = (age < 18) ? () => alert('Hello') : () => alert("Greetings!"); -welcome(); // ok now +welcome(); ``` Arrow functions may appear unfamiliar and not very readable at first, but that quickly changes as the eyes get used to the structure. @@ -86,7 +86,7 @@ Like this: let sum = (a, b) => { // the curly brace opens a multiline function let result = a + b; *!* - return result; // if we use curly braces, then we need an explicit "return" + return result; // if we use curly braces, then we need an explicit "return" */!* }; 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 93% 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 cfc043d7d..d0ed0ef08 100644 --- a/1-js/02-first-steps/17-javascript-specials/article.md +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -81,9 +81,10 @@ let x = 5; x = "John"; ``` -There are 7 data types: +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", @@ -143,7 +144,7 @@ Assignments : There is a simple assignment: `a = b` and combined ones like `a *= 2`. Bitwise -: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Reference/Operators/Bitwise_Operators) when they are needed. +: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Guide/Expressions_and_Operators#Bitwise) when they are needed. Conditional : The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`. @@ -151,6 +152,9 @@ Conditional Logical operators : Logical AND `&&` and OR `||` perform short-circuit evaluation and then return the value where it stopped (not necessary `true`/`false`). Logical NOT `!` converts the operand to boolean type and returns the inverse value. +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: @@ -170,7 +174,7 @@ Comparisons Other operators : There are few others, like a comma operator. -More in: , , . +More in: , , , . ## Loops @@ -212,6 +216,7 @@ let age = prompt('Your age?', 18); switch (age) { case 18: alert("Won't work"); // the result of prompt is a string, not a number + break; case "18": alert("This works!"); @@ -268,7 +273,7 @@ We covered three ways to create a function in JavaScript: ``` -- Functions may have local variables: those declared inside its body. Such variables are only visible inside the function. +- 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`. 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 c0291af41..89994541a 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,10 +1,18 @@ +<<<<<<< HEAD # Klaidų taisymas naršyklėje Chrome +======= +# Debugging in the browser +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 Prieš rašydami apie sudėtingesnius kodus, pakalbėkime apie klaidų ieškojimą ir taisymą (ang. debugging). [Debugging](https://en.wikipedia.org/wiki/Debugging) yra toks procesas kai ieškome ir taisome klaidas skriptuose. Visos modernios naršyklės ir didžioji dalis kitų aplinkų palaiko klaidų taisymo įrankius -- tam tikra programuotojo įrankių vartotojo sąsaja (UI), kuri palengvina klaidų taisymą. Ji taip pat leidžia atsekti kodą žingsnis po žingsnio, kad pamatytume kas iš tikrųjų vyksta. +<<<<<<< HEAD Mes tam naudosime Chrome, nes jis turi užtektinai funkcijų, bet didžioji dalis naršyklių turi panašius procesus`. +======= +We'll be using Chrome here, because it has enough features, most other browsers have a similar process. +>>>>>>> 29216730a877be28d0a75a459676db6e7f5c4834 ## The "Sources" panel @@ -24,11 +32,11 @@ Let's click it and select `hello.js` in the tree view. Here's what should show u ![](chrome-tabs.svg) -Here we can see three zones: +The Sources panel has 3 parts: -1. The **Resources zone** lists HTML, JavaScript, CSS and other files, including images that are attached to the page. Chrome extensions may appear here too. -2. The **Source zone** shows the source code. -3. The **Information and control zone** is for debugging, we'll explore it soon. +1. The **File Navigator** pane lists HTML, JavaScript, CSS and other files, including images that are attached to the page. Chrome extensions may appear here too. +2. The **Code Editor** pane shows the source code. +3. The **JavaScript Debugging** pane is for debugging, we'll explore it soon. Now you could click the same toggler again to hide the resources list and give the code some space. @@ -135,7 +143,7 @@ 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. That is: 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. 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 764e36c63..4facc8b29 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 @@ -12,7 +12,7 @@ function pow(x,n) // <- no space between arguments 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 +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 alert(`Power ${n} is not supported, please enter an integer number greater than zero`); @@ -39,7 +39,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 bdcfec545..904f0a939 100644 --- a/1-js/03-code-quality/02-coding-style/article.md +++ b/1-js/03-code-quality/02-coding-style/article.md @@ -86,7 +86,7 @@ For example: ```js // backtick quotes ` allow to split the string into multiple lines let str = ` - Ecma International's TC39 is a group of JavaScript developers, + ECMA International's TC39 is a group of JavaScript developers, implementers, academics, and more, collaborating with the community to maintain and evolve the definition of JavaScript. `; @@ -116,7 +116,7 @@ There are two types of indents: One advantage of spaces over tabs is that spaces allow more flexible configurations of indents than the tab symbol. - For instance, we can align the arguments with the opening bracket, like this: + For instance, we can align the parameters with the opening bracket, like this: ```js no-beautify show(parameters, @@ -285,7 +285,7 @@ Of course, a team can always write their own style guide, but usually there's no Some popular choices: -- [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/) @@ -301,11 +301,11 @@ The great thing about them is that style-checking can also find some bugs, like Here are some well-known linting tools: -- [JSLint](http://www.jslint.com/) -- one of the first linters. -- [JSHint](http://www.jshint.com/) -- more settings than JSLint. -- [ESLint](http://eslint.org/) -- probably the newest one. +- [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](http://eslint.org/). +All of them can do the job. The author uses [ESLint](https://eslint.org/). Most linters are integrated with many popular editors: just enable the plugin in the editor and configure the style. @@ -328,14 +328,14 @@ Here's an example of an `.eslintrc` file: }, "rules": { "no-console": 0, - "indent": ["warning", 2] + "indent": 2 } } ``` Here the directive `"extends"` denotes that the configuration is based on the "eslint:recommended" set of settings. After that, we specify our own. -It is also possible to download style rule sets from the web and extend them instead. See for more details about installation. +It is also possible to download style rule sets from the web and extend them instead. See for more details about installation. Also certain IDEs have built-in linting, which is convenient but not as customizable as ESLint. diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index 29ba701f8..0d11c6c52 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -125,25 +125,25 @@ Describe the architecture Document function parameters and usage : There's a special syntax [JSDoc](http://en.wikipedia.org/wiki/JSDoc) to document a function: usage, parameters, returned value. - 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) { - ... - } - ``` +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. +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. +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/jsdoc3/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at . +Also, there are tools like [JSDoc 3](https://github.com/jsdoc3/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at . Why is the task solved this way? : What's written is important. But what's *not* written may be even more important to understand what's going on. Why is the task solved exactly this way? The code gives no answer. 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 7846f6e21..96fdf4143 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,7 @@ # Ninja code -```quote author="Confucius" +```quote author="Confucius (Analects)" Learning without thought is labor lost; thought without learning is perilous. ``` @@ -43,7 +43,7 @@ The Dao hides in wordlessness. Only the Dao is well begun and well completed. ``` -Another way to code faster is to use single-letter variable names everywhere. Like `a`, `b` or `c`. +Another way to code shorter is to use single-letter variable names everywhere. Like `a`, `b` or `c`. A short variable disappears in the code like a real ninja in the forest. No one will be able to find it using "search" of the editor. And even if someone does, they won't be able to "decipher" what the name `a` or `b` means. @@ -104,8 +104,8 @@ A quick read of such code becomes impossible. And when there's a typo... Ummm... ## Smart synonyms -```quote author="Confucius" -The hardest thing of all is to find a black cat in a dark room, especially if there is no cat. +```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. ``` Using *similar* names for *same* things makes life more interesting and shows your creativity to the public. 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 e9b5e96c1..9037b484d 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,7 @@ Automated testing will be used in further tasks, and it's also widely used in real projects. -## Why we need tests? +## Why do we need tests? When we write a function, we can usually imagine what it should do: which parameters give which results. @@ -159,8 +159,8 @@ We can select one of two ways to organize the test here: assert.equal(pow(2, 3), 8); }); - it("3 raised to power 3 is 27", function() { - assert.equal(pow(3, 3), 27); + it("3 raised to power 4 is 81", function() { + assert.equal(pow(3, 4), 81); }); }); @@ -182,7 +182,7 @@ The result: [iframe height=250 src="pow-2" edit border="1"] -As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `27`. +As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `81`. ## Improving the implementation 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/05-testing-mocha/pow-2.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js index 9a2f8fde7..c803f0e61 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js @@ -4,8 +4,8 @@ describe("pow", function() { assert.equal(pow(2, 3), 8); }); - it("3 raised to power 3 is 27", function() { - assert.equal(pow(3, 3), 27); + it("3 raised to power 4 is 81", function() { + assert.equal(pow(3, 4), 81); }); }); diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index b399fd428..02be20872 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,5 +1,5 @@ -# Polyfills +# Polyfills and transpilers 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](http://www.ecma-international.org/publications/standards/Ecma-262.htm). @@ -9,46 +9,84 @@ So it's quite common for an engine to implement only the 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). -## Babel +As programmers, we'd like to use most recent features. The more good stuff - the better! -When we use modern features of the language, some engines may fail to support such code. Just as said, not all features are implemented everywhere. +On the other hand, how to make our modern code work on older engines that don't understand recent features yet? -Here Babel comes to the rescue. +There are two tools for that: -[Babel](https://babeljs.io) is a [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler). It rewrites modern JavaScript code into the previous standard. +1. Transpilers. +2. Polyfills. -Actually, there are two parts in Babel: +Here, in this chapter, our purpose is to get the gist of how they work, and their place in web development. -1. First, the transpiler program, which rewrites the code. The developer runs it on their own computer. It rewrites the code into the older standard. And then the code is delivered to the website for users. Modern project build systems like [webpack](http://webpack.github.io/) provide means to run transpiler automatically on every code change, so that very easy to integrate into development process. +## Transpilers -2. Second, the polyfill. +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. - New language features may include new built-in functions and syntax constructs. - The transpiler rewrites the code, transforming syntax constructs into older ones. But as for new built-in functions, we need to implement them. JavaScript is a highly dynamic language, scripts may add/modify any functions, so that they behave according to the modern standard. +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 script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations. +A transpiler would analyze our code and rewrite `height ?? 100` into `(height !== undefined && height !== null) ? height : 100`. - Two interesting polyfills are: - - [core js](https://github.com/zloirock/core-js) that supports a lot, allows to include only needed features. - - [polyfill.io](http://polyfill.io) service that provides a script with polyfills, depending on the features and user's browser. +```js +// before running the transpiler +height = height ?? 100; -So, if we're going to use modern language features, a transpiler and a polyfill are necessary. +// after running the transpiler +height = (height !== undefined && height !== null) ? height : 100; +``` -## Examples in the tutorial +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. -````online -Most examples are runnable at-place, like this: +Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there. -```js run -alert('Press the "Play" button in the upper-right corner to run'); -``` +Modern project build systems, such as [webpack](http://webpack.github.io/), provide means to run transpiler automatically on every code change, so it's very easy to integrate into 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`. -Examples that use modern JS will work only if your browser supports it. -```` +In some (very outdated) JavaScript engines, there's no `Math.trunc`, so such code will fail. -```offline -As you're reading the offline version, in PDF examples are not runnable. In EPUB some of them can run. +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); + }; +} ``` -Google Chrome is usually the most up-to-date with language features, good to run bleeding-edge demos without any transpilers, but other modern browsers also work fine. +JavaScript is a highly dynamic language, scripts may add/modify any functions, even including built-in ones. + +Two interesting libraries of polyfills are: +- [core js](https://github.com/zloirock/core-js) that supports a lot, allows to include only needed features. +- [polyfill.io](http://polyfill.io) service that provides a script with polyfills, depending on the features and user's browser. + + +## 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 transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). And 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](http://webpack.github.io/) with [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. + diff --git a/1-js/04-object-basics/01-object/4-const-object/solution.md b/1-js/04-object-basics/01-object/4-const-object/solution.md deleted file mode 100644 index f73c2f92b..000000000 --- a/1-js/04-object-basics/01-object/4-const-object/solution.md +++ /dev/null @@ -1,19 +0,0 @@ -Sure, it works, no problem. - -The `const` only protects the variable itself from changing. - -In other words, `user` stores a reference to the object. And it can't be changed. But the content of the object can. - -```js run -const user = { - name: "John" -}; - -*!* -// works -user.name = "Pete"; -*/!* - -// error -user = 123; -``` diff --git a/1-js/04-object-basics/01-object/4-const-object/task.md b/1-js/04-object-basics/01-object/4-const-object/task.md deleted file mode 100644 index a9aada631..000000000 --- a/1-js/04-object-basics/01-object/4-const-object/task.md +++ /dev/null @@ -1,18 +0,0 @@ -importance: 5 - ---- - -# Constant objects? - -Is it possible to change an object declared with `const`? What do you think? - -```js -const user = { - name: "John" -}; - -*!* -// does it work? -user.name = "Pete"; -*/!* -``` 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 33eb89220..6878ca088 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,9 @@ importance: 3 --- -# Multiply numeric properties by 2 +# Multiply numeric property values by 2 -Create a function `multiplyNumeric(obj)` that multiplies all numeric properties of `obj` by `2`. +Create a function `multiplyNumeric(obj)` that multiplies all numeric property values of `obj` by `2`. For instance: diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index ea015e7ca..ed8a3f4d7 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -1,7 +1,7 @@ # Objects -As we know from the chapter , there are seven data types in JavaScript. Six of them are called "primitive", because their values contain only a single thing (be it a string or a number or whatever). +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). In contrast, objects are used to store keyed collections of various data and more complex entities. In JavaScript, objects penetrate almost every aspect of the language. So we must understand them first before going in-depth anywhere else. @@ -101,7 +101,9 @@ For multiword properties, the dot access doesn't work: user.likes birds = true ``` -That's because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations. +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). There's an alternative "square bracket notation" that works with any string: @@ -159,7 +161,7 @@ alert( user.key ) // undefined ### Computed properties -We can use square brackets in an object literal. That's called *computed properties*. +We can use square brackets in an object literal, when creating an object. That's called *computed properties*. For instance: @@ -203,43 +205,6 @@ Square brackets are much more powerful than the dot notation. They allow any pro So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets. - - -````smart header="Reserved words are allowed as property names" -A variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc. - -But for an object property, there's no such restriction. Any name is fine: - -```js run -let obj = { - for: 1, - let: 2, - return: 3 -}; - -alert( obj.for + obj.let + obj.return ); // 6 -``` - -Basically, any name is allowed, but there's a special one: `"__proto__"` that gets special treatment for historical reasons. For instance, we can't set it to a non-object value: - -```js run -let obj = {}; -obj.__proto__ = 5; -alert(obj.__proto__); // [object Object], didn't work as intended -``` - -As we see from the code, the assignment to a primitive `5` is ignored. - -That can become a source of bugs and even vulnerabilities if we intend to store arbitrary key-value pairs in an object, and allow a visitor to specify the keys. - -In that case the visitor may choose `__proto__` as the key, and the assignment logic will be ruined (as shown above). - -There is a way to make objects treat `__proto__` as a regular property, which we'll cover later, but first we need to know more about objects. - -There's also another data structure [Map](info:map-set), that we'll learn in the chapter , which supports arbitrary keys. -```` - - ## Property value shorthand In real code we often use existing variables as values for property names. @@ -250,7 +215,7 @@ For instance: function makeUser(name, age) { return { name: name, - age: age + age: age, // ...other properties }; } @@ -268,7 +233,7 @@ function makeUser(name, age) { *!* return { name, // same as name: name - age // same as age: age + age, // same as age: age // ... }; */!* @@ -284,9 +249,57 @@ let user = { }; ``` -## Existence check -A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined: +## Property names limitations + +As we already know, a variable cannot have a name equal to one of 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 +let obj = { + for: 1, + let: 2, + return: 3 +}; + +alert( obj.for + obj.let + obj.return ); // 6 +``` + +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: ```js run let user = {}; @@ -294,7 +307,7 @@ let user = {}; alert( user.noSuchProperty === undefined ); // true means "no such property" ``` -There also exists a special operator `"in"` to check for the existence of a property. +There's also a special operator `"in"` for that. The syntax is: ```js @@ -312,17 +325,18 @@ alert( "blabla" in user ); // false, user.blabla doesn't exist Please note that on the left side of `in` there must be a *property name*. That's usually a quoted string. -If we omit quotes, that would mean a variable containing the actual name will be tested. For instance: +If we omit quotes, that means a variable, it should contain the actual name to be tested. For instance: ```js run let user = { age: 30 }; let key = "age"; -alert( *!*key*/!* in user ); // true, takes the name from key and checks for such property +alert( *!*key*/!* in user ); // true, property "age" exists ``` -````smart header="Using \"in\" for properties that store `undefined`" -Usually, the strict comparison `"=== undefined"` check the property existance just fine. But there's a special case when it fails, but `"in"` works correctly. +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. It's when an object property exists, but stores `undefined`: @@ -336,11 +350,10 @@ alert( obj.test ); // it's undefined, so - no such property? alert( "test" in obj ); // true, the property does exist! ``` - In the code above, the property `obj.test` technically exists. So the `in` operator works right. -Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. -```` +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 @@ -375,7 +388,6 @@ Note that all "for" constructs allow us to declare the looping variable inside t Also, we could use another variable name here instead of `key`. For instance, `"for (let prop in obj)"` is also widely used. - ### Ordered like an object Are objects ordered? In other words, if we loop over an object, do we get all properties in the same order they were added? Can we rely on this? @@ -459,262 +471,6 @@ for (let code in codes) { Now it works as intended. -## Copying by reference - -One of the fundamental differences of objects vs primitives is that they are stored and copied "by reference". - -Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value". - -For instance: - -```js -let message = "Hello!"; -let phrase = message; -``` - -As a result we have two independent variables, each one is storing the string `"Hello!"`. - -![](variable-copy-value.svg) - -Objects are not like that. - -**A variable stores not the object itself, but its "address in memory", in other words "a reference" to it.** - -Here's the picture for the object: - -```js -let user = { - name: "John" -}; -``` - -![](variable-contains-reference.svg) - -Here, the object is stored somewhere in memory. And the variable `user` has a "reference" to it. - -**When an object variable is copied -- the reference is copied, the object is not duplicated.** - -If we imagine an object as a cabinet, then a variable is a key to it. Copying a variable duplicates the key, but not the cabinet itself. - -For instance: - -```js no-beautify -let user = { name: "John" }; - -let admin = user; // copy the reference -``` - -Now we have two variables, each one with the reference to the same object: - -![](variable-copy-reference.svg) - -We can use any variable to access the cabinet 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 -``` - -The example above demonstrates that there is only one object. As if we had a cabinet with two keys and used one of them (`admin`) to get into it. Then, if we later use the other key (`user`) we would see changes. - -### Comparison by reference - -The equality `==` and strict equality `===` operators for objects work exactly the same. - -**Two objects are equal only if they are the same object.** - -For instance, if two variables reference the same object, 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 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 necessary very rarely and usually are a result of a coding mistake. - -### Const object - -An object declared as `const` *can* be changed. - -For instance: - -```js run -const user = { - name: "John" -}; - -*!* -user.age = 25; // (*) -*/!* - -alert(user.age); // 25 -``` - -It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes only value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`. - -The `const` would give an error if we try to set `user` to something else, for instance: - -```js run -const user = { - name: "John" -}; - -*!* -// Error (can't reassign user) -*/!* -user = { - name: "Pete" -}; -``` - -...But what if we want to make constant object properties? So that `user.age = 25` would give an error. That's possible too. We'll cover it in the chapter . - -## 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? Create an independent copy, a clone? - -That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. Actually, that's rarely needed. Copying by reference is good most of the time. - -But if we really want that, then we need to 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 clone -clone.name = "Pete"; // changed the data in it - -alert( user.name ); // still John in the original object -``` - -Also we can use the method [Object.assign](mdn:js/Object/assign) for that. - -The syntax is: - -```js -Object.assign(dest, [src1, src2, src3...]) -``` - -- Arguments `dest`, and `src1, ..., srcN` (can be as many as needed) are objects. -- It copies the properties of all objects `src1, ..., srcN` into `dest`. In other words, properties of all arguments starting from the 2nd are copied into the 1st. Then it returns `dest`. - -For instance, we can use it to merge several objects into one: -```js -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 } -``` - -If the receiving object (`user`) already has the same named property, it will be overwritten: - -```js -let user = { name: "John" }; - -// overwrite name, add isAdmin -Object.assign(user, { name: "Pete", isAdmin: true }); - -// now user = { name: "Pete", isAdmin: true } -``` - -We also can use `Object.assign` to replace the loop for simple cloning: - -```js -let user = { - name: "John", - age: 30 -}; - -*!* -let clone = Object.assign({}, user); -*/!* -``` - -It copies all properties of `user` into the empty object and returns it. Actually, the same as the loop, but shorter. - -Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them? - -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 the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes: - -Like this: -```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++; // change a property from one place -alert(clone.sizes.width); // 51, see the result from the other one -``` - -To fix that, we should use the 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". - -There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](https://html.spec.whatwg.org/multipage/structured-data.html#safe-passing-of-structured-data). In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library [lodash](https://lodash.com), the method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). - - - ## Summary Objects are associative arrays with several special features. @@ -732,10 +488,6 @@ Additional operators: - To check if a property with the given key exists: `"key" in obj`. - To iterate over an object: `for (let key in obj)` loop. -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. 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` or [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). - What we've studied in this chapter is called a "plain object", or just `Object`. There are many other kinds of objects in JavaScript: 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-garbage-collection/family-delete-refs.svg b/1-js/04-object-basics/02-garbage-collection/family-delete-refs.svg deleted file mode 100644 index 133b3908d..000000000 --- a/1-js/04-object-basics/02-garbage-collection/family-delete-refs.svg +++ /dev/null @@ -1 +0,0 @@ -<global variable>ObjectObjectwifefamilyname: "John"name: "Ann"motherObjectfatherhusband \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-family.svg b/1-js/04-object-basics/02-garbage-collection/family-no-family.svg deleted file mode 100644 index 8ba3b9f9f..000000000 --- a/1-js/04-object-basics/02-garbage-collection/family-no-family.svg +++ /dev/null @@ -1 +0,0 @@ -<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.svg b/1-js/04-object-basics/02-garbage-collection/family-no-father.svg deleted file mode 100644 index 59f0b2afc..000000000 --- a/1-js/04-object-basics/02-garbage-collection/family-no-father.svg +++ /dev/null @@ -1 +0,0 @@ -ObjectObjectwifefamilyname: "John"name: "Ann"motherObject<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/family.svg b/1-js/04-object-basics/02-garbage-collection/family.svg deleted file mode 100644 index 8fbbc35ea..000000000 --- a/1-js/04-object-basics/02-garbage-collection/family.svg +++ /dev/null @@ -1 +0,0 @@ -ObjectObjectfatherwifefamilyname: "John"name: "Ann"motherObjecthusband<global variable> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.svg deleted file mode 100644 index 125031ee4..000000000 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.svg +++ /dev/null @@ -1 +0,0 @@ -<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.svg deleted file mode 100644 index b4f1a0870..000000000 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.svg +++ /dev/null @@ -1 +0,0 @@ -<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.svg deleted file mode 100644 index 7477c4ba7..000000000 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.svg +++ /dev/null @@ -1 +0,0 @@ -<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.svg deleted file mode 100644 index 6b13c1dc0..000000000 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.svg +++ /dev/null @@ -1 +0,0 @@ -<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.svg deleted file mode 100644 index 5cb54dc28..000000000 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.svg +++ /dev/null @@ -1 +0,0 @@ -<global>unreachables \ 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..b56a8034b --- /dev/null +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -0,0 +1,264 @@ +# 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`, as 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. + +## 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? Create an independent copy, a clone? + +That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. But there is rarely a need -- copying by reference is good most of the time. + +But if we really want that, then we need to 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 +``` + +Also we can use the method [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) for that. + +The syntax is: + +```js +Object.assign(dest, [src1, src2, src3...]) +``` + +- The first argument `dest` is a target object. +- Further arguments `src1, ..., srcN` (can be as many as needed) are source objects. +- It copies the properties of all source objects `src1, ..., srcN` into the target `dest`. In other words, properties of all arguments starting from the second are copied into the first object. +- The call returns `dest`. + +For instance, we can use it to merge several objects into one: +```js +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 } +``` + +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 replace `for..in` loop for simple cloning: + +```js +let user = { + name: "John", + age: 30 +}; + +*!* +let clone = Object.assign({}, user); +*/!* +``` + +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. What to do with them? + +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 the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes: + +Like this: + +```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++; // change a property from one place +alert(clone.sizes.width); // 51, see the result from the other one +``` + +To fix that, 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". + +We can use recursion to implement it. 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). + +````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 . +```` + +## 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, 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 95% 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 672e26d43..72e30469c 100644 --- a/1-js/04-object-basics/02-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -14,8 +14,8 @@ Simply put, "reachable" values are those that are accessible or usable somehow. For instance: - - Local variables and parameters of the current function. - - Variables and parameters for other functions on the current chain of nested calls. + - 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) @@ -23,7 +23,7 @@ Simply put, "reachable" values are those that are accessible or usable somehow. 2. Any other value is considered reachable if it's reachable from a root by a reference or by a chain of references. - For instance, if there's an object in a local 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. + 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. There's a background process in the JavaScript engine that is called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that have become unreachable. 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 ea00c970d..f33c9310e 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 @@ -7,7 +7,7 @@ function makeUser() { name: "John", ref: this }; -}; +} let user = makeUser(); @@ -22,6 +22,17 @@ The value of `this` is one for the whole function, code blocks and object litera So `ref: this` actually takes current `this` of the function. +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: ```js run @@ -34,7 +45,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 4784b082c..c6f8f9658 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/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 2b47873fc..f215461dd 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 ``` We also can write a single call per line. For long chains it's more readable: @@ -33,7 +33,7 @@ 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 eca9f4e92..a2a19c620 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 @@ -28,12 +28,14 @@ ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 +ladder.down(); +ladder.showStep(); // 0 ``` Modify the code of `up`, `down` and `showStep` to make the calls chainable, like this: ```js -ladder.up().up().down().showStep(); // 1 +ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` Such approach is widely used across JavaScript libraries. 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 2dda938d7..a36b9ca07 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,11 @@ user.sayHi = function() { user.sayHi(); // Hello! ``` -Here we've just used a Function Expression to create the function and assign it to the property `user.sayHi` of the object. +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. The user can now speak! +Then we can call it as `user.sayHi()`. The user can now speak! -A function that is the property of an object is called its *method*. +A function that is a property of an object is called its *method*. So, here we've got a method `sayHi` of the object `user`. @@ -63,7 +63,7 @@ user.sayHi(); // Hello! ```smart header="Object-oriented programming" When we write our code using objects to represent entities, that's called [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), in short: "OOP". -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. +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. ``` ### Method shorthand @@ -81,7 +81,7 @@ user = { // method shorthand looks better, right? user = { *!* - sayHi() { // same as "sayHi: function()" + sayHi() { // same as "sayHi: function(){...}" */!* alert("Hello"); } @@ -160,14 +160,16 @@ let user = { let admin = user; user = null; // overwrite to make things obvious -admin.sayHi(); // Whoops! inside sayHi(), the old name is used! error! +*!* +admin.sayHi(); // TypeError: Cannot read property 'name' of null +*/!* ``` If we used `this.name` instead of `user.name` inside the `alert`, then the code would work. ## "this" is not bound -In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function. +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. There's no syntax error in the following example: @@ -233,98 +235,6 @@ The concept of run-time evaluated `this` has both pluses and minuses. On the one Here our position is not to judge whether this language design decision is good or bad. We'll understand how to work with it, how to get benefits and avoid problems. ``` -## Internals: Reference Type - -```warn header="In-depth language feature" -This section covers an advanced topic, to understand certain edge-cases better. - -If you want to go on faster, it can be skipped or postponed. -``` - -An intricate method call can lose `this`, for instance: - -```js run -let user = { - name: "John", - hi() { alert(this.name); }, - bye() { alert("Bye"); } -}; - -user.hi(); // John (the simple call 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. - -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). Later in this tutorial, we will learn various ways to solve this problem such as [func.bind()](/bind#solution-2-bind). - ## Arrow functions have no "this" Arrow functions are special: they don't have their "own" `this`. If we reference `this` from such a function, it's taken from the outer "normal" function. 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 8c1fea8eb..d80113acc 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,7 +4,7 @@ importance: 2 # Two functions – one object -Is it possible to create functions `A` and `B` such as `new A()==new B()`? +Is it possible to create functions `A` and `B` so that `new A() == new B()`? ```js no-beautify function A() { ... } 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/article.md b/1-js/04-object-basics/06-constructor-new/article.md index a885e35ff..f3e9c3ec0 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,6 @@ # Constructor, operator "new" -The regular `{...}` syntax allows to create one object. But often we need to create many similar objects, like multiple users or menu items and so on. +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. That can be done using constructor functions and the `"new"` operator. @@ -64,13 +64,14 @@ Now if we want to create other users, we can call `new User("Ann")`, `new User(" That's the main purpose of constructors -- to implement reusable object creation code. -Let's note once again -- technically, any function can be used as a constructor. That is: any function 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`. +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 constructor function, like this: +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: ```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 +81,7 @@ let user = new function() { }; ``` -The 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. +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. ```` ## Constructor mode test: new.target @@ -91,7 +92,7 @@ The syntax from this section is rarely used, skip it unless you want to know eve Inside a function, we can check whether it was called with `new` or without it, using a special `new.target` property. -It is empty for regular calls and equals the function if called with `new`: +It is undefined for regular calls and equals the function if called with `new`: ```js run function 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..f27f7d1d2 --- /dev/null +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -0,0 +1,220 @@ + +# 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` 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. For more deeply nested properties, that becomes a problem as more repetitions are required. + +E.g. let's try getting `user.address.street.name`. + +We need to check both `user.address` and `user.address.street`: + +```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. + +Don't even care to, as there's a 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. + +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 coding logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`. + +So, if `user` happens to be undefined due to a mistake, we'll see a programming error about it and fix it. Otherwise, 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 side effects, they don't occur. + +For instance: + +```js run +let user = null; +let x = 0; + +user?.sayHi(x++); // no "sayHi", so the execution doesn't reach 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 (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 at 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" +``` + +It's just not that smart. +```` + +## 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 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 92% rename from 1-js/04-object-basics/03-symbol/article.md rename to 1-js/04-object-basics/08-symbol/article.md index a17f85fe2..2f8ac6935 100644 --- a/1-js/04-object-basics/03-symbol/article.md +++ b/1-js/04-object-basics/08-symbol/article.md @@ -18,7 +18,7 @@ let id = Symbol(); Upon creation, we can give symbol a description (also called a symbol name), mostly useful for debugging purposes: -```js run +```js // id is a symbol with the description "id" let id = Symbol("id"); ``` @@ -109,7 +109,7 @@ There will be no conflict between our and their identifiers, because symbols are ...But if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict: -```js run +```js let user = { name: "John" }; // Our script uses "id" property @@ -121,7 +121,7 @@ user.id = "Their id value" // Boom! overwritten by another script! ``` -### Symbols in a literal +### Symbols in an object literal If we want to use a symbol in an object literal `{...}`, we need square brackets around it. @@ -133,7 +133,7 @@ let id = Symbol("id"); let user = { name: "John", *!* - [id]: 123 // not "id: 123" + [id]: 123 // not "id": 123 */!* }; ``` @@ -161,7 +161,7 @@ for (let key in user) alert(key); // name, age (no symbols) alert( "Direct: " + user[id] ); ``` -`Object.keys(user)` 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. +[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. In contrast, [Object.assign](mdn:js/Object/assign) copies both string and symbol properties: @@ -178,22 +178,6 @@ alert( clone[id] ); // 123 There's no paradox here. That's by design. The idea is that when we clone an object or merge objects, we usually want *all* properties to be copied (including symbols like `id`). -````smart header="Property keys of other types are coerced to strings" -We can only use strings or symbols as keys in objects. Other types are 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) -``` -```` - ## Global symbols As we've seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property. @@ -241,7 +225,7 @@ alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id ``` -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 return `undefined`. +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, any symbols have `description` property. 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 76% 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 f6b715ce5..3e52a1d51 100644 --- a/1-js/04-object-basics/05-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -3,7 +3,24 @@ What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`? -In that case, objects are auto-converted to primitives, and then the operation is carried out. +JavaScript doesn't exactly allow 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 an addition (or other operators). + +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, as the result of `obj1 + obj2` 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 do much here, there's no maths with objects in real projects. When it happens, it's usually 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. @@ -11,11 +28,11 @@ In the chapter we've seen the rules for numeric, string 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 like `alert(obj)` and in similar contexts. -## ToPrimitive - We can fine-tune string and numeric conversion, using special object methods. -There are three variants of type conversion, so-called "hints", described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): +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): `"string"` : For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`: @@ -46,7 +63,7 @@ There are three variants of type conversion, so-called "hints", described in the `"default"` : Occurs in rare cases when the operator is "not sure" what type to expect. - For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if the a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. + For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used. @@ -82,11 +99,14 @@ Let's start from the first method. There's a built-in symbol named `Symbol.toPri ```js obj[Symbol.toPrimitive] = function(hint) { - // must return a primitive value + // 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: ```js run @@ -111,12 +131,12 @@ As we can see from the code, `user` becomes a self-descriptive string or a money ## toString/valueOf -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. +If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: -If there's no `Symbol.toPrimitive` then JavaScript tries to find them and try in the order: +- For the "string" hint: `toString`, and if it doesn't exist, then `valueOf` (so `toString` has the priority for string conversions). +- For other hints: `valueOf`, and if it doesn't exist, then `toString` (so `valueOf` has the priority for maths). -- `toString -> valueOf` for "string" hint. -- `valueOf -> toString` otherwise. +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). @@ -136,9 +156,9 @@ alert(user.valueOf() === user); // true So if we try to use an object as a string, like in an `alert` or so, then by default we see `[object Object]`. -And 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. +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. +Let's implement these methods to customize the conversion. For instance, here `user` does the same as above using a combination of `toString` and `valueOf` instead of `Symbol.toPrimitive`: @@ -183,7 +203,7 @@ alert(user + 500); // toString -> John500 In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions. -## Return types +### A conversion can return any primitive type The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive. @@ -252,4 +272,6 @@ The conversion algorithm is: 3. Otherwise if hint is `"number"` or `"default"` - try `obj.valueOf()` and `obj.toString()`, whatever exists. -In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for all conversions that return a "human-readable" representation of an object, for logging or debugging purposes. +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. + +As for math operations, JavaScript doesn't provide a way to "override" them using methods, so real life projects rarely use them on objects. 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 06138a004..930a304f7 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -7,7 +7,7 @@ Let's look at the key distinctions between primitives and objects. A primitive - Is a value of a primitive type. -- There are 6 primitive types: `string`, `number`, `boolean`, `symbol`, `null` and `undefined`. +- There are 7 primitive types: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` and `undefined`. An object @@ -39,7 +39,7 @@ Objects are "heavier" than primitives. They require additional resources to supp Here's the paradox faced by the creator of JavaScript: -- 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 as methods. +- 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. The solution looks a little bit awkward, but here it is: @@ -48,7 +48,7 @@ The solution looks a little bit awkward, but here it is: 2. The language allows access to methods and properties of strings, numbers, booleans and symbols. 3. In order for that to work, a special "object wrapper" that provides the extra functionality is created, and then is destroyed. -The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean` and `Symbol`. Thus, they provide different sets of methods. +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. For instance, there exists a string method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns a capitalized `str`. diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index f9ffdd81b..a2d2c3eb7 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -1,8 +1,12 @@ # Numbers -All numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers". +In modern JavaScript, there are two types of numbers: -Let's expand upon what we currently know about them. +1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), 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, to represent integers of arbitrary length. They are sometimes needed, because a regular number can't safely exceed 253 or be less than -253. As bigints are used in few special areas, we devote them a special chapter . + +So here we'll talk about regular numbers. Let's expand our knowledge of them. ## More ways to write a number @@ -12,46 +16,53 @@ Imagine we need to write 1 billion. The obvious way is: let billion = 1000000000; ``` -But in real life, we usually avoid writing a long string of zeroes as it's easy to mistype. Also, we are lazy. We will usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers. +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", it makes the number more readable. The JavaScript engine simply ignores `_` between digits, so it's exactly the same one billion as above. -In JavaScript, we shorten a number by appending the letter `"e"` to the number and specifying the zeroes count: +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: ```js run let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes -alert( 7.3e9 ); // 7.3 billions (7,300,000,000) +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. +In other words, `e` multiplies the number by `1` with the given zeroes count. ```js -1e3 = 1 * 1000 -1.23e6 = 1.23 * 1000000 +1e3 === 1 * 1000; // e3 means *1000 +1.23e6 === 1.23 * 1000000; // e6 means *1000000 ``` - Now let's write something very small. Say, 1 microsecond (one millionth of a second): ```js -let ms = 0.000001; +let mсs = 0.000001; ``` -Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say: +Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say the same as: ```js -let ms = 1e-6; // six zeroes to the left from 1 +let mcs = 1e-6; // six 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`. +If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`. In other words, a negative number after `"e"` means a division by 1 with the given number of zeroes: ```js // -3 divides by 1 with 3 zeroes -1e-3 = 1 / 1000 (=0.001) +1e-3 === 1 / 1000; // 0.001 // -6 divides by 1 with 6 zeroes -1.23e-6 = 1.23 / 1000000 (=0.00000123) +1.23e-6 === 1.23 / 1000000; // 0.00000123 ``` ### Hex, binary and octal numbers @@ -107,6 +118,7 @@ Please note that two dots in `123456..toString(36)` is not a typo. If we want to 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 goes the method. Also could write `(123456).toString(36)`. + ``` ## Rounding @@ -122,7 +134,7 @@ There are several built-in functions for rounding: : Rounds up: `3.1` becomes `4`, and `-1.1` becomes `-1`. `Math.round` -: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4` and `-1.1` becomes `-1`. +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`, the middle case: `3.5` rounds up to `4` too. `Math.trunc` (not supported by Internet Explorer) : Removes anything after the decimal point without rounding: `3.1` becomes `3`, `-1.1` becomes `-1`. @@ -145,11 +157,11 @@ There are two ways to do so: 1. Multiply-and-divide. - For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100`, call the rounding function and then divide it back. + For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100` (or a bigger power of 10), call the rounding function and then divide it back. ```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. The method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rounds the number to `n` digits after the point and returns a string representation of the result. @@ -271,13 +283,11 @@ JavaScript doesn't trigger an error in such events. It does its best to fit the ```smart header="Two zeroes" Another funny consequence of the internal representation of numbers is the existence of two zeroes: `0` and `-0`. -That's because a sign is represented by a single bit, so every number can be positive or negative, including a zero. +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 Remember these two special numeric values? @@ -319,11 +329,11 @@ let num = +prompt("Enter a number", ''); alert( isFinite(num) ); ``` -Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. +Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. ```smart header="Compare with `Object.is`" -There is a special built-in method [Object.is](mdn:js/Object/is) that compares values like `===`, but is more reliable for two edge cases: +There is a special built-in method [`Object.is`](mdn:js/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 true, because internally the number has a sign bit that may be different even if all other bits are zeroes. @@ -382,7 +392,7 @@ JavaScript has a built-in [Math](https://developer.mozilla.org/en/docs/Web/JavaS A few examples: `Math.random()` -: Returns a random number from 0 to 1 (not including 1) +: Returns a random number from 0 to 1 (not including 1). ```js run alert( Math.random() ); // 0.1234567894322 @@ -399,24 +409,24 @@ A few examples: ``` `Math.pow(n, power)` -: Returns `n` raised the given power +: Returns `n` raised to the given power. ```js run alert( Math.pow(2, 10) ); // 2 in power 10 = 1024 ``` -There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object. +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). ## Summary -To write big numbers: +To write numbers with many zeroes: -- Append `"e"` with the zeroes count to the number. Like: `123e6` is `123` with 6 zeroes. -- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. That's for one-millionth or such. +- 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). For different numeral systems: -- Can write numbers directly in hex (`0x`), octal (`0o`) and binary (`0b`) systems +- 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`. 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 5546c47ee..d51672ae6 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,6 @@ The maximal length must be `maxlength`, so we need to cut it a little shorter, to give space for the ellipsis. -Note that there is actually a single unicode character for an ellipsis. That's not three dots. +Note that there is actually a single Unicode character for an ellipsis. That's not three dots. ```js run demo function truncate(str, maxlength) { diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index 8a2fe14f7..41bda2254 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -50,7 +50,7 @@ let guestList = "Guests: // Error: Unexpected token ILLEGAL 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 is called "tagged templates". This feature makes it easier to implement custom templating, but is rarely used in practice. You can read more about it in the [manual](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). +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 is called "tagged templates". This feature makes it easier to implement custom templating, but is rarely used in practice. You can read more about it in the [manual](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). ## Special characters @@ -81,21 +81,21 @@ Here's the full list: | Character | Description | |-----------|-------------| |`\n`|New line| -|`\r`|Carriage return: not used alone. Windows text files use a combination of two characters `\r\n` to represent a line break. | +|`\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 -- kept for compatibility, not used nowadays. | -|`\xXX`|Unicode character with the given hexadecimal unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.| -|`\uXXXX`|A unicode symbol with the hex code `XXXX` in UTF-16 encoding, for instance `\u00A9` -- is a unicode for the copyright symbol `©`. It must be exactly 4 hex digits. | -|`\u{X…XXXXXX}` (1 to 6 hex characters)|A unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two unicode symbols, taking 4 bytes. This way we can insert long codes. | +|`\xXX`|Unicode character with the given hexadecimal Unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.| +|`\uXXXX`|A Unicode symbol with the hex code `XXXX` in UTF-16 encoding, for instance `\u00A9` -- is a Unicode for the copyright symbol `©`. It must be exactly 4 hex digits. | +|`\u{X…XXXXXX}` (1 to 6 hex characters)|A Unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two Unicode symbols, taking 4 bytes. This way we can insert long codes. | -Examples with unicode: +Examples with Unicode: ```js run alert( "\u00A9" ); // © -alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long unicode) -alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long unicode) +alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long Unicode) +alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) ``` All special characters start with a backslash character `\`. It is also called an "escape character". @@ -110,7 +110,7 @@ alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus! As you can see, we have to prepend the inner quote by the backslash `\'`, because otherwise it would indicate the string end. -Of course, only to 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: +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: ```js run alert( `I'm the Walrus!` ); // I'm the Walrus! @@ -239,7 +239,7 @@ alert( str.indexOf('widget') ); // -1, not found, the search is case-sensitive alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id) ``` -The optional second parameter allows us to search starting from the given position. +The optional second parameter allows us to start searching from a given position. For instance, the first occurrence of `"id"` is at position `1`. To look for the next occurrence, let's start the search from position `2`: @@ -312,7 +312,7 @@ if (str.indexOf("Widget") != -1) { #### The bitwise NOT trick -One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation. +One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation. In practice, that means a simple thing: for 32-bit integers `~n` equals `-(n+1)`. @@ -345,7 +345,7 @@ It is usually not recommended to use language features in a non-obvious way, but Just remember: `if (~str.indexOf(...))` reads as "if found". -To be precise though, as big numbers are truncated to 32 bits by `~` operator, there exist other numbers that give `0`, the smallest is `~4294967295=0`. That makes such check is correct only if a string is not that long. +To be precise though, as big numbers are truncated to 32 bits by `~` operator, there exist other numbers that give `0`, the smallest is `~4294967295=0`. That makes such check correct only if a string is not that long. Right now we can see this trick only in the old code, as modern JavaScript provides `.includes` method (see below). @@ -499,7 +499,7 @@ All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). Th alert( String.fromCodePoint(90) ); // Z ``` - We can also add unicode characters by their codes using `\u` followed by the hex code: + We can also add Unicode characters by their codes using `\u` followed by the hex code: ```js run // 90 is 5a in hexadecimal system @@ -526,15 +526,15 @@ Now it becomes obvious why `a > Z`. The characters are compared by their numeric code. The greater code means that the character is greater. The code for `a` (97) is greater than the code for `Z` (90). - All lowercase letters go after uppercase letters because their codes are greater. -- Some letters like `Ö` stand apart from the main alphabet. Here, it's code is greater than anything from `a` to `z`. +- Some letters like `Ö` stand apart from the main alphabet. Here, its code is greater than anything from `a` to `z`. -### Correct comparisons +### Correct comparisons [#correct-comparisons] The "right" algorithm to do string comparisons is more complex than it may seem, because alphabets are different for different languages. So, the browser needs to know the language to compare. -Luckily, all modern browsers (IE10- requires the additional library [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). +Luckily, all modern browsers (IE10- requires the additional library [Intl.js](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). It provides a special method to compare strings in different languages, following their rules. @@ -608,7 +608,7 @@ In many languages there are symbols that are composed of the base character with For instance, the letter `a` can be the base character for: `àáâäãåā`. Most common "composite" character have their own code in the UTF-16 table. But not all of them, because there are too many possible combinations. -To support arbitrary compositions, UTF-16 allows us to use several unicode characters: the base character followed by one or many "mark" characters that "decorate" it. +To support arbitrary compositions, UTF-16 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 Ṡ. @@ -626,7 +626,7 @@ For example: 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. +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: @@ -639,7 +639,7 @@ 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. +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). @@ -663,7 +663,7 @@ If you want to learn more about normalization rules and variants -- they are des - There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions `${…}`. - Strings in JavaScript are encoded using UTF-16. -- We can use special characters like `\n` and insert letters by their unicode using `\u...`. +- We can use special characters like `\n` and insert letters by their Unicode using `\u...`. - To get a character, use: `[]`. - To get a substring, use: `slice` or `substring`. - To lowercase/uppercase a string, use: `toLowerCase/toUpperCase`. 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 daadf494b..befd80296 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,7 +57,7 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6 alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 ``` -The solution has a time complexety 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. +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 a serious sluggishness. 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 e63c4e625..f1a1d9f95 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,15 +10,15 @@ The task is: find the contiguous subarray of `arr` with the maximal sum of items Write the function `getMaxSubSum(arr)` that will return that sum. -For instance: +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) +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) ``` If all items are negative, it means that we take none (the subarray is empty), so the sum is zero: 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 e994ae078..3cb0317cf 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(){...} ``` The array has 3 values: initially it had two, plus the function. diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 7dc54bd4b..a86dead64 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -123,7 +123,7 @@ For stacks, the latest pushed item is received first, that's also called LIFO (L 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 it is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). +In computer science the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). **Methods that work with the end of the array:** @@ -156,7 +156,7 @@ In computer science the data structure that allows it is called [deque](https:// `shift` : Extracts the first element of the array and returns it: - ```js + ```js run let fruits = ["Apple", "Orange", "Pear"]; alert( fruits.shift() ); // remove Apple and alert it @@ -167,7 +167,7 @@ In computer science the data structure that allows it is called [deque](https:// `unshift` : Add the element to the beginning of the array: - ```js + ```js run let fruits = ["Orange", "Pear"]; fruits.unshift('Apple'); @@ -193,7 +193,7 @@ An array is a special kind of object. The square brackets used to access a prope They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object. -Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object. +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. For instance, it is copied by reference: @@ -209,7 +209,7 @@ arr.push("Pear"); // modify the array by reference alert( fruits ); // Banana, Pear - 2 items now ``` -...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. +...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. But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object. @@ -379,9 +379,7 @@ alert( arr[0] ); // undefined! no elements. alert( arr.length ); // length 2 ``` -In the code above, `new Array(number)` has all elements `undefined`. - -To evade such surprises, we usually use square brackets, unless we really know what we're doing. +To avoid such surprises, we usually use square brackets, unless we really know what we're doing. ## Multidimensional arrays @@ -429,6 +427,53 @@ alert( "1" + 1 ); // "11" alert( "1,2" + 1 ); // "1,21" ``` +## 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 Array is a special kind of object, suited to storing and managing ordered data items. @@ -460,4 +505,8 @@ To loop over the elements of the array: - `for (let item of arr)` -- the modern syntax for items only, - `for (let i in arr)` -- never use. -We will return to arrays and study more methods to add, remove, extract elements and sort arrays in the chapter . +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 . diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js new file mode 100644 index 000000000..8dea23a06 --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js @@ -0,0 +1,6 @@ +function groupById(array) { + return array.reduce((obj, value) => { + obj[value.id] = value; + return obj; + }, {}) +} 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 new file mode 100644 index 000000000..e48ba138d --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js @@ -0,0 +1,21 @@ +describe("groupById", function() { + + it("creates an object grouped by id", function() { + let users = [ + {id: 'john', name: "John Smith", age: 20}, + {id: 'ann', name: "Ann Smith", age: 24}, + {id: 'pete', name: "Pete Peterson", age: 31}, + ]; + + assert.deepEqual(groupById(users), { + john: {id: 'john', name: "John Smith", age: 20}, + ann: {id: 'ann', name: "Ann Smith", age: 24}, + pete: {id: 'pete', name: "Pete Peterson", age: 31}, + }); + }); + + it("works with an empty array", function() { + users = []; + assert.deepEqual(groupById(users), {}); + }); +}); diff --git a/1-js/11-async/01-callbacks/01-animate-circle-callback/solution.md b/1-js/05-data-types/05-array-methods/12-reduce-object/solution.md similarity index 100% rename from 1-js/11-async/01-callbacks/01-animate-circle-callback/solution.md rename to 1-js/05-data-types/05-array-methods/12-reduce-object/solution.md 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 new file mode 100644 index 000000000..7f0082357 --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md @@ -0,0 +1,37 @@ +importance: 4 + +--- + +# Create keyed object from array + +Let's say we received an array of users in the form `{id:..., name:..., age:... }`. + +Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values. + +For example: + +```js +let users = [ + {id: 'john', name: "John Smith", age: 20}, + {id: 'ann', name: "Ann Smith", age: 24}, + {id: 'pete', name: "Pete Peterson", age: 31}, +]; + +let usersById = groupById(users); + +/* +// after the call we should have: + +usersById = { + john: {id: 'john', name: "John Smith", age: 20}, + ann: {id: 'ann', name: "Ann Smith", age: 24}, + pete: {id: 'pete', name: "Pete Peterson", age: 31}, +} +*/ +``` + +Such function is really handy when working with server data. + +In this task we assume that `id` is unique. There may be no two array items with the same `id`. + +Please use array `.reduce` method in the solution. 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/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 5a1a7762a..b14e9a0be 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -36,15 +36,15 @@ That's natural, because `delete obj.key` removes a value by the `key`. It's all 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. @@ -384,7 +384,7 @@ The order became `1, 15, 2`. Incorrect. But why? **The items are sorted as strings by default.** -Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed `"2" > "15"`. +Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed `"2" > "15"`. To use our own sorting order, we need to supply a function as the argument of `arr.sort()`. @@ -419,19 +419,19 @@ 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. -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: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { alert( a + " <> " + b ); + return a - b; }); ``` The algorithm may compare an element with multiple others in the process, but it tries to make as few comparisons as possible. - ````smart header="A comparison function may return any number" Actually, a comparison function is only required to return a positive number to say "greater" and a negative number to say "less". @@ -456,6 +456,22 @@ arr.sort( (a, b) => a - b ); This works exactly the same as the longer version above. ```` +````smart header="Use `localeCompare` for strings" +Remember [strings](info:string#correct-comparisons) comparison algorithm? It compares letters by their codes by default. + +For many alphabets, it's better to use `str.localeCompare` method to correctly sort letters, such as `Ö`. + +For example, let's sort a few countries in German: + +```js run +let countries = ['Österreich', 'Andorra', 'Vietnam']; + +alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (wrong) + +alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (correct!) +``` +```` + ### reverse The method [arr.reverse](mdn:js/Array/reverse) reverses the order of elements in `arr`. @@ -530,7 +546,7 @@ The methods [arr.reduce](mdn:js/Array/reduce) and [arr.reduceRight](mdn:js/Array The syntax is: ```js -let value = arr.reduce(function(previousValue, item, index, array) { +let value = arr.reduce(function(accumulator, item, index, array) { // ... }, [initial]); ``` @@ -539,14 +555,16 @@ The function is applied to all array elements one after another and "carries on" Arguments: -- `previousValue` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided). +- `accumulator` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided). - `item` -- is the current array item. - `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. -Sounds complicated, but it's not if you think about the first argument as the "accumulator" that stores the combined result of all previous execution. 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`. + +Sounds complicated? The easiest way to grasp that is by example. @@ -611,7 +629,6 @@ let arr = []; 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. @@ -684,7 +701,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 @@ -695,8 +712,8 @@ 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: @@ -713,7 +730,7 @@ 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. @@ -722,14 +739,27 @@ 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. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index b55f8f018..ce9074cc7 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -1,7 +1,7 @@ # Iterables -*Iterable* objects is a generalization of arrays. That's a concept that allows 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. Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable. @@ -12,7 +12,7 @@ If an object isn't technically an array, but represents a collection (list, set) We can easily grasp the concept of iterables by making one of our own. -For instance, we have an object, that is not an array, but looks suitable for `for..of`. +For instance, we have an object that is not an array, but looks suitable for `for..of`. Like a `range` object that represents an interval of numbers: @@ -26,12 +26,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 +45,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 +140,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,12 +165,12 @@ 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`). @@ -224,12 +224,12 @@ let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (array toString conversion works) ``` -The full syntax for `Array.from` allows to provide an optional "mapping" function: +The full syntax for `Array.from` also allows us to provide an optional "mapping" function: ```js Array.from(obj[, mapFn, thisArg]) ``` -The optional second argument `mapFn` can be a function that will be applied to each element before adding to the array, and `thisArg` allows to set `this` for it. +The optional second argument `mapFn` can be a function that will be applied to each element before adding it to the array, and `thisArg` allows us to set `this` for it. For instance: @@ -270,7 +270,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,8 +293,8 @@ 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`. - - The result of `obj[Symbol.iterator]` is called an *iterator*. It handles the further iteration process. - - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the iteration end, otherwise the `value` is the next value. + - The result of `obj[Symbol.iterator]()` is called an *iterator*. It handles further iteration process. + - 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`. - String iterator knows about surrogate pairs. @@ -304,4 +304,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/02-filter-anagrams/solution.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md index 4c8af1f24..160675185 100644 --- a/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md @@ -36,7 +36,7 @@ Letter-sorting is done by the chain of calls in the line `(*)`. For convenience let's split it into multiple lines: ```js -let sorted = arr[i] // PAN +let sorted = word // PAN .toLowerCase() // pan .split('') // ['p','a','n'] .sort() // ['a','n','p'] 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 c4d7c21a4..bd6cad562 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -1,10 +1,10 @@ # 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. @@ -41,6 +41,12 @@ 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`" +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). + +So we should use `map` methods: `set`, `get` and so on. +``` + **Map can also use objects as keys.** For instance: @@ -57,24 +63,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. @@ -192,7 +200,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. @@ -214,7 +222,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 @@ -298,10 +306,10 @@ The same methods `Map` has for iterators are also supported: 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.set(key, value)` -- stores the value by the key, returns the map itself. - `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.delete(key)` -- removes the value by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`. - `map.clear()` -- removes everything from the map. - `map.size` -- returns the current element count. 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 6a4c20baf..e2147ccfa 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,7 @@ messages.shift(); // now readMessages has 1 element (technically memory may be cleaned later) ``` -The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it. +The `WeakSet` allows to store a set of messages and easily check for the existence of a message in it. It cleans up itself automatically. The tradeoff is that we can't iterate over it, can't get "all read messages" from it directly. But we can do it by iterating over all messages and filtering those that are in the set. 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 11ff9d5eb..8d5a86981 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,6 +1,6 @@ # WeakMap and WeakSet -As we know from the chapter , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). +As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. For instance: ```js @@ -30,7 +30,8 @@ let array = [ john ]; john = null; // overwrite the reference *!* -// john is stored inside the array, so it won't be garbage-collected +// 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] */!* ``` @@ -59,7 +60,7 @@ Let's see what it means on examples. ## WeakMap -The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values: +The first difference between `Map` and `WeakMap` is that keys must be objects, not primitive values: ```js run let weakMap = new WeakMap(); @@ -100,9 +101,9 @@ Compare it with the regular `Map` example above. Now if `john` only exists as th Why such a limitation? That's for technical reasons. If an object has lost all other references (like `john` in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified *when the cleanup happens*. -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. +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 data structure? +Now, where do we need such a data structure? ## Use case: additional data @@ -141,13 +142,12 @@ And here's another part of the code, maybe another file using it: let john = { name: "John" }; countUser(john); // count his visits -countUser(john); // later john leaves us john = null; ``` -Now `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. +Now, `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures. @@ -164,13 +164,13 @@ function countUser(user) { } ``` -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`. +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`. ## Use case: caching -Another common example is caching: when a function result should be remembered ("cached"), so that future calls on the same object reuse it. +Another common example is caching. We can store ("cache") results from a function, so that future calls on the same object can reuse it. -We can use `Map` to store results, like this: +To achieve that, we can use `Map` (not optimal scenario): ```js run // 📁 cache.js @@ -207,7 +207,7 @@ alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!) For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more. -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. +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. ```js run // 📁 cache.js @@ -248,7 +248,7 @@ obj = null; - An object exists in the set while it is reachable from somewhere else. - Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations. -Being "weak", it also serves as an additional storage. But not for an arbitrary data, but rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. +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. For instance, we can add users to `WeakSet` to keep track of those who visited our site: @@ -276,7 +276,7 @@ john = null; // visitedSet will be cleaned automatically ``` -The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and 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. +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. ## Summary @@ -284,6 +284,8 @@ The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterati `WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. -Both of them do not support methods and properties that refer to all keys or their count. Only individual operations are allowed. +Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector. -`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. +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. 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..98c7f73d2 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. +Although, when we pass those to a function, it may need not be an object/array as a whole. It may need individual pieces. -*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 great 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"] +let arr = ["John", "Smith"] *!* // destructuring assignment @@ -23,18 +26,22 @@ 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 better understand it. + ````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. @@ -69,26 +76,25 @@ 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 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. 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. We can use it with destructuring to loop over keys-and-values of an object: @@ -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 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 at the left, there'll 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 at 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 = { 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..bed449453 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("February 20, 2012 03:12:00"); +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/8-format-date-relative/solution.md b/1-js/05-data-types/11-date/8-format-date-relative/solution.md index 2507c840c..372485685 100644 --- a/1-js/05-data-types/11-date/8-format-date-relative/solution.md +++ b/1-js/05-data-types/11-date/8-format-date-relative/solution.md @@ -40,7 +40,7 @@ alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" -// yesterday's date like 31.12.2016, 20:00 +// yesterday's date like 31.12.2016 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` @@ -62,6 +62,8 @@ function formatDate(date) { year = year.toString().slice(-2); month = month < 10 ? '0' + month : month; dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth; + hour = hour < 10 ? '0' + hour : hour; + minutes = minutes < 10 ? '0' + minutes : minutes; if (diffSec < 1) { return 'right now'; diff --git a/1-js/05-data-types/11-date/8-format-date-relative/task.md b/1-js/05-data-types/11-date/8-format-date-relative/task.md index 4dc067375..9651b305f 100644 --- a/1-js/05-data-types/11-date/8-format-date-relative/task.md +++ b/1-js/05-data-types/11-date/8-format-date-relative/task.md @@ -20,6 +20,6 @@ alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" -// yesterday's date like 31.12.16, 20:00 +// yesterday's date like 31.12.16 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index 6f52a0d7c..ed4e21359 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -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); @@ -124,7 +124,7 @@ Besides the given methods, there are two special ones that do not have a UTC-var : Returns the timestamp for the date -- a number of milliseconds passed from the January 1st of 1970 UTC+0. [getTimezoneOffset()](mdn:js/Date/getTimezoneOffset) -: Returns the difference between the local time zone and UTC, in minutes: +: Returns the difference between UTC and the local time zone, in minutes: ```js run // if you are in timezone UTC-1, outputs 60 @@ -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); @@ -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`. @@ -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..ae5f045af 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](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](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: @@ -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" 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 688badb02..17fe5ea3e 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -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". ``` @@ -302,7 +302,7 @@ let company = { salary: 1000 }, { name: 'Alice', - salary: 600 + salary: 1600 }], development: { @@ -350,7 +350,7 @@ The algorithm is probably even easier to read from the code: ```js run let company = { // the same object, compressed for brevity - sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 600 }], + sales: [{name: 'John', salary: 1000}, {name: 'Alice', salary: 1600 }], development: { sites: [{name: 'Peter', salary: 2000}, {name: 'Alex', salary: 1800 }], internals: [{name: 'Jack', salary: 1300}] @@ -372,7 +372,7 @@ function sumSalaries(department) { } */!* -alert(sumSalaries(company)); // 6700 +alert(sumSalaries(company)); // 7700 ``` The code is short and easy to understand (hopefully?). That's the power of recursion. It also works for any level of subdepartment nesting. @@ -462,7 +462,7 @@ list.next.next.next = { value: 4 }; list.next.next.next.next = null; ``` -Here we can even more clearer see that there are multiple objects, each one has the `value` and `next` pointing to the neighbour. The `list` variable is the first object in the chain, so following `next` pointers from it we can reach any element. +Here we can even more clearly see that there are multiple objects, each one has the `value` and `next` pointing to the neighbour. The `list` variable is the first object in the chain, so following `next` pointers from it we can reach any element. The list can be easily split into multiple parts and later joined back: diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md similarity index 73% rename from 1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md rename to 1-js/06-advanced-functions/02-rest-parameters-spread/article.md index a14f0fb73..c63fe70cd 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -1,4 +1,4 @@ -# Rest parameters and spread operator +# Rest parameters and spread syntax Many JavaScript built-in functions support an arbitrary number of arguments. @@ -122,7 +122,7 @@ As we remember, arrow functions don't have their own `this`. Now we know they do ```` -## Spread operator [#spread-operator] +## Spread syntax [#spread-syntax] We've just seen how to get an array from the list of parameters. @@ -148,7 +148,7 @@ alert( Math.max(arr) ); // NaN And surely we can't manually list items in the code `Math.max(arr[0], arr[1], arr[2])`, because we may be unsure how many there are. As our script executes, there could be a lot, or there could be none. And that would get ugly. -*Spread operator* to the rescue! It looks similar to rest parameters, also using `...`, but does quite the opposite. +*Spread syntax* to the rescue! It looks similar to rest parameters, also using `...`, but does quite the opposite. When `...arr` is used in the function call, it "expands" an iterable object `arr` into the list of arguments. @@ -169,7 +169,7 @@ let arr2 = [8, 3, -8, 1]; alert( Math.max(...arr1, ...arr2) ); // 8 ``` -We can even combine the spread operator with normal values: +We can even combine the spread syntax with normal values: ```js run @@ -179,7 +179,7 @@ let arr2 = [8, 3, -8, 1]; alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25 ``` -Also, the spread operator can be used to merge arrays: +Also, the spread syntax can be used to merge arrays: ```js run let arr = [3, 5, 1]; @@ -192,9 +192,9 @@ let merged = [0, ...arr, 2, ...arr2]; alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2) ``` -In the examples above we used an array to demonstrate the spread operator, but any iterable will do. +In the examples above we used an array to demonstrate the spread syntax, but any iterable will do. -For instance, here we use the spread operator to turn the string into array of characters: +For instance, here we use the spread syntax to turn the string into array of characters: ```js run let str = "Hello"; @@ -202,7 +202,7 @@ let str = "Hello"; alert( [...str] ); // H,e,l,l,o ``` -The spread operator internally uses iterators to gather elements, the same way as `for..of` does. +The spread syntax internally uses iterators to gather elements, the same way as `for..of` does. So, for a string, `for..of` returns characters and `...str` becomes `"H","e","l","l","o"`. The list of characters is passed to array initializer `[...str]`. @@ -220,24 +220,75 @@ The result is the same as `[...str]`. But there's a subtle difference between `Array.from(obj)` and `[...obj]`: - `Array.from` operates on both array-likes and iterables. -- The spread operator operates only on iterables. +- The spread syntax works only with iterables. So, for the task of turning something into an array, `Array.from` tends to be more universal. +## 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 +*/!* + +// do the arrays have the same contents? +alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true + +// are the arrays equal? +alert(arr === arrCopy); // false (not same reference) + +// modifying our initial array does not modify the copy: +arr.push(4); +alert(arr); // 1, 2, 3, 4 +alert(arrCopy); // 1, 2, 3 +``` + +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 }; + +*!* +let objCopy = { ...obj }; // spread the object into a list of parameters + // then return the result in a new object +*/!* + +// do the objects have the same contents? +alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true + +// are the objects equal? +alert(obj === objCopy); // false (not same reference) + +// modifying our initial object does not modify the copy: +obj.d = 4; +alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4} +alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3} +``` + +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. + + ## Summary -When we see `"..."` in the code, it is either rest parameters or the spread operator. +When we see `"..."` in the code, it is either rest parameters or the spread syntax. There's an easy way to distinguish between them: - When `...` is at the end of function parameters, it's "rest parameters" and gathers the rest of the list of arguments into an array. -- When `...` occurs in a function call or alike, it's called a "spread operator" and expands an array into a list. +- When `...` occurs in a function call or alike, it's called a "spread syntax" and expands an array into a list. Use patterns: - Rest parameters are used to create functions that accept any number of arguments. -- The spread operator is used to pass an array to functions that normally require a list of many arguments. +- The spread syntax is used to pass an array to functions that normally require a list of many arguments. Together they help to travel between a list and an array of parameters with ease. diff --git a/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md new file mode 100644 index 000000000..7cbd85ab7 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md @@ -0,0 +1,5 @@ +The answer is: **Pete**. + +A function gets outer variables as they are now, it uses the most recent values. + +Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one. diff --git a/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md new file mode 100644 index 000000000..819189773 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md @@ -0,0 +1,23 @@ +importance: 5 + +--- + +# Does a function pickup latest changes? + +The function sayHi uses an external variable name. When the function runs, which value is it going to use? + +```js +let name = "John"; + +function sayHi() { + alert("Hi, " + name); +} + +name = "Pete"; + +sayHi(); // what will it show: "John" or "Pete"? +``` + +Such situations are common both in browser and server-side development. A function may be scheduled to execute later than it is created, for instance after a user action or a network request. + +So, the question is: does it pick up the latest changes? diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/solution.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/solution.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/solution.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/solution.js diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/source.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/source.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/source.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/source.js diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/test.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/test.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/test.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/test.js 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 new file mode 100644 index 000000000..9d99aa717 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md @@ -0,0 +1,129 @@ + +Let's examine what exactly happens inside `makeArmy`, and the solution will become obvious. + +1. It creates an empty array `shooters`: + + ```js + let shooters = []; + ``` +2. Fills it with functions via `shooters.push(function)` in the loop. + + Every element is a function, so the resulting array looks like this: + + ```js no-beautify + shooters = [ + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); }, + function () { alert(i); } + ]; + ``` + +3. The array is returned from the function. + + 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. + 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 new file mode 100644 index 000000000..f50c7dc20 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/10-make-army/task.md @@ -0,0 +1,41 @@ +importance: 5 + +--- + +# Army of functions + +The following code creates an array of `shooters`. + +Every function is meant to output its number. But something is wrong... + +```js run +function makeArmy() { + let shooters = []; + + let i = 0; + while (i < 10) { + 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 + return shooters; +} + +let army = makeArmy(); + +*!* +// 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. + diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg new file mode 100644 index 000000000..8dfd8bd63 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg @@ -0,0 +1 @@ +makeWorker: function name: "John"<empty>outerouterouternullname: "Pete" \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md new file mode 100644 index 000000000..0a522132f --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md @@ -0,0 +1,9 @@ +The answer is: **Pete**. + +The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference: + +![](lexenv-nested-work.svg) + +So, the result is `"Pete"` here. + +But if there were no `let name` in `makeWorker()`, then the search would go outside and take the global variable as we can see from the chain above. In that case the result would be `"John"`. diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md new file mode 100644 index 000000000..d12a385c8 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md @@ -0,0 +1,29 @@ +importance: 5 + +--- + +# Which variables are available? + +The function `makeWorker` below makes another function and returns it. That new function can be called from somewhere else. + +Will it have access to the outer variables from its creation place, or the invocation place, or both? + +```js +function makeWorker() { + let name = "Pete"; + + return function() { + alert(name); + }; +} + +let name = "John"; + +// create a function +let work = makeWorker(); + +// call it +work(); // what will it show? +``` + +Which value it will show? "Pete" or "John"? diff --git a/1-js/06-advanced-functions/03-closure/1-counter-independent/solution.md b/1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/1-counter-independent/solution.md rename to 1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md diff --git a/1-js/06-advanced-functions/03-closure/1-counter-independent/task.md b/1-js/06-advanced-functions/03-closure/3-counter-independent/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/1-counter-independent/task.md rename to 1-js/06-advanced-functions/03-closure/3-counter-independent/task.md diff --git a/1-js/06-advanced-functions/03-closure/2-counter-object-independent/solution.md b/1-js/06-advanced-functions/03-closure/4-counter-object-independent/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/2-counter-object-independent/solution.md rename to 1-js/06-advanced-functions/03-closure/4-counter-object-independent/solution.md diff --git a/1-js/06-advanced-functions/03-closure/2-counter-object-independent/task.md b/1-js/06-advanced-functions/03-closure/4-counter-object-independent/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/2-counter-object-independent/task.md rename to 1-js/06-advanced-functions/03-closure/4-counter-object-independent/task.md diff --git a/1-js/06-advanced-functions/03-closure/3-function-in-if/solution.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/3-function-in-if/solution.md rename to 1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md diff --git a/1-js/06-advanced-functions/03-closure/3-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/3-function-in-if/task.md rename to 1-js/06-advanced-functions/03-closure/5-function-in-if/task.md diff --git a/1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md b/1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md rename to 1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md diff --git a/1-js/06-advanced-functions/03-closure/4-closure-sum/task.md b/1-js/06-advanced-functions/03-closure/6-closure-sum/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/4-closure-sum/task.md rename to 1-js/06-advanced-functions/03-closure/6-closure-sum/task.md 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 new file mode 100644 index 000000000..b16b35290 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md @@ -0,0 +1,40 @@ +The result is: **error**. + +Try running it: + +```js run +let x = 1; + +function func() { +*!* + console.log(x); // ReferenceError: Cannot access 'x' before initialization +*/!* + let x = 2; +} + +func(); +``` + +In this example we can observe the peculiar difference between a "non-existing" and "uninitialized" variable. + +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. + +In other words, a variable technically exists, but can't be used before `let`. + +The code above demonstrates it. + +```js +function func() { +*!* + // the local variable x is known to the engine from the beginning of the function, + // but "uninitialized" (unusable) until let ("dead zone") + // hence the error +*/!* + + console.log(x); // ReferenceError: Cannot access 'x' before initialization + + let x = 2; +} +``` + +This zone of temporary unusability of a variable (from the beginning of the code block till `let`) is sometimes called the "dead zone". diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/task.md b/1-js/06-advanced-functions/03-closure/7-let-scope/task.md new file mode 100644 index 000000000..fb7445e66 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/task.md @@ -0,0 +1,21 @@ +importance: 4 + +--- + +# Is variable visible? + +What will be the result of this code? + +```js +let x = 1; + +function func() { + console.log(x); // ? + + let x = 2; +} + +func(); +``` + +P.S. There's a pitfall in this task. The solution is not obvious. diff --git a/1-js/06-advanced-functions/03-closure/7-sort-by-field/solution.md b/1-js/06-advanced-functions/03-closure/7-sort-by-field/solution.md deleted file mode 100644 index bd57085ea..000000000 --- a/1-js/06-advanced-functions/03-closure/7-sort-by-field/solution.md +++ /dev/null @@ -1,22 +0,0 @@ - - -```js run -let users = [ - { name: "John", age: 20, surname: "Johnson" }, - { name: "Pete", age: 18, surname: "Peterson" }, - { name: "Ann", age: 19, surname: "Hathaway" } -]; - -*!* -function byField(field) { - return (a, b) => a[field] > b[field] ? 1 : -1; -} -*/!* - -users.sort(byField('name')); -users.forEach(user => alert(user.name)); // Ann, John, Pete - -users.sort(byField('age')); -users.forEach(user => alert(user.name)); // Pete, Ann, John -``` - diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/solution.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/solution.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/solution.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/solution.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/source.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/source.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/source.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/source.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/test.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/test.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/test.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/test.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md b/1-js/06-advanced-functions/03-closure/8-filter-through-function/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/solution.md diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/task.md b/1-js/06-advanced-functions/03-closure/8-filter-through-function/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/task.md rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/task.md diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.svg b/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.svg deleted file mode 100644 index c0a312ec7..000000000 --- a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.svg +++ /dev/null @@ -1 +0,0 @@ -outeri: 0i: 1i: 2i: 10...makeArmy() LexicalEnvironmentfor block LexicalEnvironment \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/solution.md b/1-js/06-advanced-functions/03-closure/8-make-army/solution.md deleted file mode 100644 index 0fb0b4a49..000000000 --- a/1-js/06-advanced-functions/03-closure/8-make-army/solution.md +++ /dev/null @@ -1,120 +0,0 @@ - -Let's examine what's done inside `makeArmy`, and the solution will become obvious. - -1. It creates an empty array `shooters`: - - ```js - let shooters = []; - ``` -2. Fills it in the loop via `shooters.push(function...)`. - - Every element is a function, so the resulting array looks like this: - - ```js no-beautify - shooters = [ - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); }, - function () { alert(i); } - ]; - ``` - -3. The array is returned from the function. - -Then, later, the call to `army[5]()` will get the element `army[5]` from the array (it will be a function) and call it. - -Now why all such functions show the same? - -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. - -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 - }; - ... - } - ... -} -``` - -...We can see that it lives in the lexical environment associated with the current `makeArmy()` run. But when `army[5]()` is called, `makeArmy` has already finished its job, and `i` has the last value: `10` (the end of `while`). - -As a result, all `shooter` functions get from the outer lexical envrironment the same, last value `i=10`. - -We can fix it by moving the variable definition into the loop: - -```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 -``` - -Now it works correctly, because every time the code block in `for (let i=0...) {...}` is executed, a new Lexical Environment is created for it, with the corresponding variable `i`. - -So, the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration. That's why now it works. - -![](lexenv-makearmy.svg) - -Here we rewrote `while` into `for`. - -Another trick could be possible, let's see it for better understanding of the subject: - -```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(); - -army[0](); // 0 -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. diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/task.md b/1-js/06-advanced-functions/03-closure/8-make-army/task.md deleted file mode 100644 index ede8fd045..000000000 --- a/1-js/06-advanced-functions/03-closure/8-make-army/task.md +++ /dev/null @@ -1,35 +0,0 @@ -importance: 5 - ---- - -# Army of functions - -The following code creates an array of `shooters`. - -Every function is meant to output its number. But something is wrong... - -```js run -function makeArmy() { - let shooters = []; - - let i = 0; - while (i < 10) { - let shooter = function() { // shooter function - alert( i ); // should show its number - }; - shooters.push(shooter); - i++; - } - - return shooters; -} - -let army = makeArmy(); - -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 all shooters show the same? Fix the code so that they work as intended. - 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 new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md @@ -0,0 +1 @@ + diff --git a/1-js/06-advanced-functions/03-closure/7-sort-by-field/task.md b/1-js/06-advanced-functions/03-closure/9-sort-by-field/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/7-sort-by-field/task.md rename to 1-js/06-advanced-functions/03-closure/9-sort-by-field/task.md diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 64104541c..199887063 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -1,203 +1,102 @@ -# Closure +# Variable scope, closure -JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, copied to another variable or passed as an argument to another function and called from a totally different place later. +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 know that a function can access variables outside of it, this feature is used quite often. +We already know that a function can access variables outside of it ("outer" variables). -But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created? +But what happens if outer variables change since a function is created? Will the function get newer values or the old ones? -Also, what happens when a function travels to another place in the code and is called from there -- does it get access to the outer variables of the new place? +And what if a function is passed along as a parameter and called from another place of code, will it get access to outer variables at the new place? -Different languages behave differently here, and in this chapter we cover the behaviour of JavaScript. +Let's expand our knowledge to understand these scenarios and more complex ones. -## A couple of questions +```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). -Let's consider two situations to begin with, and then study the internal mechanics piece-by-piece, so that you'll be able to answer the following questions and more complex ones in the future. - -1. The function `sayHi` uses an external variable `name`. When the function runs, which value is it going to use? - - ```js - let name = "John"; - - function sayHi() { - alert("Hi, " + name); - } - - name = "Pete"; - - *!* - sayHi(); // what will it show: "John" or "Pete"? - */!* - ``` - - Such situations are common both in browser and server-side development. A function may be scheduled to execute later than it is created, for instance after a user action or a network request. - - So, the question is: does it pick up the latest changes? - - -2. The function `makeWorker` makes another function and returns it. That new function can be called from somewhere else. Will it have access to the outer variables from its creation place, or the invocation place, or both? - - ```js - function makeWorker() { - let name = "Pete"; - - return function() { - alert(name); - }; - } - - let name = "John"; - - // create a function - let work = makeWorker(); - - // call it - *!* - work(); // what will it show? "Pete" (name where created) or "John" (name where called)? - */!* - ``` - - -## Lexical Environment - -To understand what's going on, let's first discuss what a "variable" actually is. - -In JavaScript, every running function, code block `{...}`, and the script as a whole have an internal (hidden) associated object known as the *Lexical Environment*. - -The Lexical Environment object consists of two parts: - -1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`). -2. A reference to the *outer lexical environment*, the one associated with the outer code. - -**A "variable" is just a property of the special internal object, `Environment Record`. "To get or change a variable" means "to get or change a property of that object".** - -For instance, in this simple code, there is only one Lexical Environment: - -![lexical environment](lexical-environment-global.svg) - -This is a so-called global Lexical Environment, associated with the whole script. - -On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, so it points to `null`. - -And that's how it changes when a variable is defined and assigned: - -![lexical environment](lexical-environment-global-2.svg) - -Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution: - -1. When the script starts, the Lexical Environment is empty. -2. The `let phrase` definition appears. It has been assigned no value, so `undefined` is stored. -3. `phrase` is assigned a value. -4. `phrase` changes value. - -Everything looks simple for now, right? - -To summarize: - -- A variable is a property of a special internal object, associated with the currently executing block/function/script. -- Working with variables is actually working with the properties of that object. - -### Function Declaration - -Until now, we only observed variables. Now enter Function Declarations. - -**Unlike `let` variables, they are fully initialized not when the execution reaches them, but earlier, when a Lexical Environment is created.** - -For top-level functions, it means the moment when the script is started. - -That is why we can call a function declaration before it is defined. - -The code below demonstrates that the Lexical Environment is non-empty from the beginning. It has `say`, because that's a Function Declaration. And later it gets `phrase`, declared with `let`: - -![lexical environment](lexical-environment-global-3.svg) - - -### Inner and outer Lexical Environment - -Now let's go on and explore what happens when a function accesses an outer variable. - -During the call, `say()` uses the outer variable `phrase`. Let's look at the details of what's going on. - -When a function runs, a new Lexical Environment is created automatically to store local variables and parameters of the call. - -For instance, for `say("John")`, it looks like this (the execution is at the line, labelled with an arrow): - - - -![lexical environment](lexical-environment-simple.svg) +- 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 . +``` -So, during the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global): +## Code blocks -- The inner Lexical Environment corresponds to the current execution of `say`. +If a variable is declared inside a code block `{...}`, it's only visible inside that block. - It has a single property: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`. -- The outer Lexical Environment is the global Lexical Environment. +For example: - It has `phrase` variable and the function itself. +```js run +{ + // do some job with local variables that should not be seen outside -The inner Lexical Environment has a reference to the `outer` one. + let message = "Hello"; // only visible in this block -**When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.** + alert(message); // Hello +} -If a variable is not found anywhere, that's an error in strict mode. Without `use strict`, an assignment to a non-existing variable like `user = "John"` creates a new global variable `user`. That's for backwards compatibility. +alert(message); // Error: message is not defined +``` -Let's see how the search proceeds in our example: +We can use this to isolate a piece of code that does its own task, with variables that only belong to it: -- When the `alert` inside `say` wants to access `name`, it finds it immediately in the function Lexical Environment. -- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the reference to the enclosing Lexical Environment and finds it there. +```js run +{ + // show message + let message = "Hello"; + alert(message); +} -![lexical environment lookup](lexical-environment-simple-lookup.svg) +{ + // show another message + let message = "Goodbye"; + alert(message); +} +``` -Now we can give the answer to the first question from the beginning of the chapter. +````smart header="There'd be an error without blocks" +Please note, without separate blocks there would be an error, if we use `let` with the existing variable name: -**A function gets outer variables as they are now, it uses the most recent values.** +```js run +// show message +let message = "Hello"; +alert(message); -Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one. +// show another message +*!* +let message = "Goodbye"; // Error: variable already declared +*/!* +alert(message); +``` +```` -So the answer to the first question is `Pete`: +For `if`, `for`, `while` and so on, variables declared in `{...}` are also only visible inside: ```js run -let name = "John"; +if (true) { + let phrase = "Hello!"; -function sayHi() { - alert("Hi, " + name); + alert(phrase); // Hello! } -name = "Pete"; // (*) - -*!* -sayHi(); // Pete -*/!* +alert(phrase); // Error, no such variable! ``` +Here, after `if` finishes, the `alert` below won't see the `phrase`, hence the error. -The execution flow of the code above: - -1. The global Lexical Environment has `name: "John"`. -2. At the line `(*)` the global variable is changed. Now it has `name: "Pete"`. -3. When the function `sayHi()` is executed it takes `name` from outside, the global Lexical Environment, where its value is already `"Pete"`. - +That's great, as it allows us to create block-local variables, specific to an `if` branch. -```smart header="One call -- one Lexical Environment" -Please note that a new function Lexical Environment is created each time a function runs. +The similar thing holds true for `for` and `while` loops: -And if a function is called multiple times, then each invocation will have its own Lexical Environment, with local variables and parameters specific for that very run. -``` +```js run +for (let i = 0; i < 3; i++) { + // the variable i is only visible inside this for + alert(i); // 0, then 1, then 2 +} -```smart header="Lexical Environment is a specification object" -"Lexical Environment" is a specification object: it only exists "theoretically" in the [language specification](https://tc39.es/ecma262/#sec-lexical-environments) to describe how things work. We can't get this object in our code and manipulate it directly. JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, as long as the visible behavior remains as described. +alert(i); // Error, no such variable ``` +Visually, `let i` is outside of `{...}`. But the `for` construct is special here: the variable, declared inside it, is considered a part of the block. ## Nested functions @@ -223,32 +122,16 @@ function sayHiBye(firstName, lastName) { Here the *nested* function `getFullName()` is made for convenience. It can access the outer variables and so can return the full name. Nested functions are quite common in JavaScript. -What's much more interesting, a nested function can be returned: either as a property of a new object (if the outer function creates an object with methods) or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables. - -For instance, here the nested function is assigned to the new object by the [constructor function](info:constructor-new): - -```js run -// constructor function returns a new object -function User(name) { - - // the object method is created as a nested function - this.sayHi = function() { - alert(name); - }; -} - -let user = new User("John"); -user.sayHi(); // the method "sayHi" code has access to the outer "name" -``` +What's much more interesting, a nested function can be returned: either as a property of a new object or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables. -And here we just create and return a "counting" function: +Below, `makeCounter` creates the "counter" function that returns the next number on each invocation: ```js run function makeCounter() { let count = 0; return function() { - return count++; // has access to the outer "count" + return count++; }; } @@ -259,317 +142,198 @@ alert( counter() ); // 1 alert( counter() ); // 2 ``` -Let's go on with the `makeCounter` example. It creates the "counter" function that returns the next number on each invocation. Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator), and more. - -How does the counter work internally? - -When the inner function runs, the variable in `count++` is searched from inside out. For the example above, the order will be: - -![](lexical-search-order.svg) +Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [random number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) to generate random values for automated tests. -1. The locals of the nested function... -2. The variables of the outer function... -3. And so on until it reaches global variables. +How does this work? If we create multiple counters, will they be independent? What's going on with the variables here? -In this example `count` is found on step `2`. When an outer variable is modified, it's changed where it's found. So `count++` finds the outer variable and increases it in the Lexical Environment where it belongs. Like if we had `let count = 1`. +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. -Here are two questions to consider: - -1. Can we somehow reset the counter `count` from the code that doesn't belong to `makeCounter`? E.g. after `alert` calls in the example above. -2. If we call `makeCounter()` multiple times -- it returns many `counter` functions. Are they independent or do they share the same `count`? - -Try to answer them before you continue reading. - -... - -All done? - -Okay, let's go over the answers. - -1. There is no way: `count` is a local function variable, we can't access it from the outside. -2. For every call to `makeCounter()` a new function Lexical Environment is created, with its own `count`. So the resulting `counter` functions are independent. - -Here's the demo: - -```js run -function makeCounter() { - let count = 0; - return function() { - return count++; - }; -} - -let counter1 = makeCounter(); -let counter2 = makeCounter(); +## Lexical Environment -alert( counter1() ); // 0 -alert( counter1() ); // 1 +```warn header="Here be dragons!" +The in-depth technical explanation lies ahead. -alert( counter2() ); // 0 (independent) +As far as I'd like to avoid low-level language details, any understanding without them would be lacking and incomplete, so get ready. ``` +For clarity, the explanation is split into multiple steps. -Hopefully, the situation with outer variables is clear now. For most situations such understanding is enough. There are few details in the specification that we omitted for brevity. So in the next section we cover even more details. - -## Environments in detail - -Here's what's going on in the `makeCounter` example step-by-step. Follow it to make sure that you understand how it works in detail. - -Please note the additional `[[Environment]]` property is covered here. We didn't mention it before for simplicity. - -1. When the script has just started, there is only the global Lexical Environment: - - ![](lexenv-nested-makecounter-1.svg) - - At that starting moment there is only the `makeCounter` function, because it's a Function Declaration. It did not run yet. - - **All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation.** - - We didn't talk about it before. That's how the function knows where it was made. - - Here, `makeCounter` is created in the global Lexical Environment, so `[[Environment]]` keeps a reference to it. - - In other words, a function is "imprinted" with a reference to the Lexical Environment where it was born. And `[[Environment]]` is the hidden function property that has that reference. - -2. The code runs on, the new global variable `counter` is declared and gets the result of the `makeCounter()` call. Here's a snapshot of the moment when the execution is on the first line inside `makeCounter()`: - - ![](lexenv-nested-makecounter-2.svg) - - At the moment of the call of `makeCounter()`, the Lexical Environment is created, to hold its variables and arguments. +### Step 1. Variables - As all Lexical Environments, it stores two things: - 1. An Environment Record with local variables. In our case `count` is the only local variable (appearing when the line with `let count` is executed). - 2. The outer lexical reference, which is set to the value of `[[Environment]]` of the function. Here `[[Environment]]` of `makeCounter` references the global Lexical Environment. - - So, now we have two Lexical Environments: the first one is global, the second one is for the current `makeCounter` call, with the outer reference to global. - -3. During the execution of `makeCounter()`, a tiny nested function is created. - - It doesn't matter whether the function is created using Function Declaration or Function Expression. All functions get the `[[Environment]]` property that references the Lexical Environment in which they were made. So our new tiny nested function gets it as well. - - For our new nested function the value of `[[Environment]]` is the current Lexical Environment of `makeCounter()` (where it was born): +In JavaScript, every running function, code block `{...}`, and the script as a whole have an internal (hidden) associated object known as the *Lexical Environment*. - ![](lexenv-nested-makecounter-3.svg) +The Lexical Environment object consists of two parts: - Please note that on this step the inner function was created, but not yet called. The code inside `return count++;` is not running. +1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`). +2. A reference to the *outer lexical environment*, the one associated with the outer code. -4. As the execution goes on, the call to `makeCounter()` finishes, and the result (the tiny nested function) is assigned to the global variable `counter`: +**A "variable" is just a property of the special internal object, `Environment Record`. "To get or change a variable" means "to get or change a property of that object".** - ![](lexenv-nested-makecounter-4.svg) +In this simple code without functions, there is only one Lexical Environment: - That function has only one line: `return count++`, that will be executed when we run it. +![lexical environment](lexical-environment-global.svg) -5. When `counter()` is called, a new Lexical Environment is created for the call. It's empty, as `counter` has no local variables by itself. But the `[[Environment]]` of `counter` is used as the `outer` reference for it, that provides access to the variables of the former `makeCounter()` call where it was created: +This is the so-called *global* Lexical Environment, associated with the whole script. - ![](lexenv-nested-makecounter-5.svg) +On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, that's why the arrow points to `null`. - Now when the call looks for `count` variable, it first searches its own Lexical Environment (empty), then the Lexical Environment of the outer `makeCounter()` call, where it finds it. +As the code starts executing and goes on, the Lexical Environment changes. - Please note how memory management works here. Although `makeCounter()` call finished some time ago, its Lexical Environment was retained in memory, because there's a nested function with `[[Environment]]` referencing it. +Here's a little bit longer code: - Generally, a Lexical Environment object lives as long as there is a function which may use it. And only when there are none remaining, it is cleared. +![lexical environment](closure-variable-phrase.svg) -6. The call to `counter()` not only returns the value of `count`, but also increases it. Note that the modification is done "in place". The value of `count` is modified exactly in the environment where it was found. +Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution: - ![](lexenv-nested-makecounter-6.svg) +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. +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. +3. `phrase` is assigned a value. +4. `phrase` changes the value. -7. Next `counter()` invocations do the same. +Everything looks simple for now, right? -The answer to the second question from the beginning of the chapter should now be obvious. +- A variable is a property of a special internal object, associated with the currently executing block/function/script. +- Working with variables is actually working with the properties of that object. -The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference: +```smart header="Lexical Environment is a specification object" +"Lexical Environment" is a specification object: it only exists "theoretically" in the [language specification](https://tc39.es/ecma262/#sec-lexical-environments) to describe how things work. We can't get this object in our code and manipulate it directly. -![](lexenv-nested-work.svg) +JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, as long as the visible behavior remains as described. +``` -So, the result is `"Pete"` here. +### Step 2. Function Declarations -But if there were no `let name` in `makeWorker()`, then the search would go outside and take the global variable as we can see from the chain above. In that case it would be `"John"`. +A function is also a value, like a variable. -```smart header="Closures" -There is a general programming term "closure", that developers generally should know. +**The difference is that a Function Declaration is instantly fully initialized.** -A [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exclusion, to be covered in ). +When a Lexical Environment is created, a Function Declaration immediately becomes a ready-to-use function (unlike `let`, that is unusable till the declaration). -That is: they automatically remember where they were created using a hidden `[[Environment]]` property, and all of them can access outer variables. +That's why we can use a function, declared as Function Declaration, even before the declaration itself. -When on an interview, a frontend developer gets a question about "what's a closure?", a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the `[[Environment]]` property and how Lexical Environments work. -``` +For example, here's the initial state of the global Lexical Environment when we add a function: -## Code blocks and loops, IIFE +![](closure-function-declaration.svg) -The examples above concentrated on functions. But a Lexical Environment exists for any code block `{...}`. +Naturally, this behavior only applies to Function Declarations, not Function Expressions where we assign a function to a variable, such as `let say = function(name)...`. -A Lexical Environment is created when a code block runs and contains block-local variables. Here are a couple of examples. +### Step 3. Inner and outer Lexical Environment -### If +When a function runs, at the beginning of the call, a new Lexical Environment is created automatically to store local variables and parameters of the call. -In the example below, the `user` variable exists only in the `if` block: +For instance, for `say("John")`, it looks like this (the execution is at the line, labelled with an arrow): -![](lexenv-if.svg) - -When the execution gets into the `if` block, the new "if-only" Lexical Environment is created for it. - -It has the reference to the outer one, so `phrase` can be found. But all variables and Function Expressions, declared inside `if`, reside in that Lexical Environment and can't be seen from the outside. - -For instance, after `if` finishes, the `alert` below won't see the `user`, hence the error. +![](lexical-environment-simple.svg) -### For, while +During the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global): -For a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for(let ...)`, then it's also in there: +- The inner Lexical Environment corresponds to the current execution of `say`. It has a single property: `name`, the function argument. We called `say("John")`, so the value of the `name` is `"John"`. +- The outer Lexical Environment is the global Lexical Environment. It has the `phrase` variable and the function itself. -```js run -for (let i = 0; i < 10; i++) { - // Each loop has its own Lexical Environment - // {i: value} -} +The inner Lexical Environment has a reference to the `outer` one. -alert(i); // Error, no such variable -``` +**When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.** -Please note: `let i` is visually outside of `{...}`. The `for` construct is special here: each iteration of the loop has its own Lexical Environment with the current `i` in it. +If a variable is not found anywhere, that's an error in strict mode (without `use strict`, an assignment to a non-existing variable creates a new global variable, for compatibility with old code). -Again, similarly to `if`, after the loop `i` is not visible. +In this example the search proceeds as follows: -### Code blocks +- For the `name` variable, the `alert` inside `say` finds it immediately in the inner Lexical Environment. +- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the reference to the outer Lexical Environment and finds it there. -We also can use a "bare" code block `{…}` to isolate variables into a "local scope". +![lexical environment lookup](lexical-environment-simple-lookup.svg) -For instance, in a web browser all scripts (except with `type="module"`) share the same global area. So if we create a global variable in one script, it becomes available to others. But that becomes a source of conflicts if two scripts use the same variable name and overwrite each other. -That may happen if the variable name is a widespread word, and script authors are unaware of each other. +### Step 4. Returning a function -If we'd like to avoid that, we can use a code block to isolate the whole script or a part of it: +Let's return to the `makeCounter` example. -```js run -{ - // do some job with local variables that should not be seen outside - - let message = "Hello"; +```js +function makeCounter() { + let count = 0; - alert(message); // Hello + return function() { + return count++; + }; } -alert(message); // Error: message is not defined +let counter = makeCounter(); ``` -The code outside of the block (or inside another script) doesn't see variables inside the block, because the block has its own Lexical Environment. +At the beginning of each `makeCounter()` call, a new Lexical Environment object is created, to store variables for this `makeCounter` run. -### IIFE +So we have two nested Lexical Environments, just like in the example above: -In the past, there were no block-level lexical environments in JavaScript. +![](closure-makecounter.svg) -So programmers had to invent something. And what they did was called "immediately-invoked function expressions" (abbreviated as IIFE). +What's different is that, during the execution of `makeCounter()`, a tiny nested function is created of only one line: `return count++`. We don't run it yet, only create. -That's not a thing we should use nowadays, but you can find them in old scripts, so it's better to understand them. +All functions remember the Lexical Environment in which they were made. Technically, there's no magic here: all functions have the hidden property named `[[Environment]]`, that keeps the reference to the Lexical Environment where the function was created: -An IIFE looks like this: +![](closure-makecounter-environment.svg) -```js run -(function() { +So, `counter.[[Environment]]` has the reference to `{count: 0}` Lexical Environment. That's how the function remembers where it was created, no matter where it's called. The `[[Environment]]` reference is set once and forever at function creation time. - let message = "Hello"; +Later, when `counter()` is called, a new Lexical Environment is created for the call, and its outer Lexical Environment reference is taken from `counter.[[Environment]]`: - alert(message); // Hello +![](closure-makecounter-nested-call.svg) -})(); -``` +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. -Here a Function Expression is created and immediately called. So the code executes right away and has its own private variables. +**A variable is updated in the Lexical Environment where it lives.** -The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, 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: +Here's the state after the execution: -```js run -// Try to declare and immediately call a function -function() { // <-- Error: Unexpected token ( +![](closure-makecounter-nested-call-2.svg) - let message = "Hello"; +If we call `counter()` multiple times, the `count` variable will be increased to `2`, `3` and so on, at the same place. - 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"); -}*!*)*/!*(); +```smart header="Closure" +There is a general programming term "closure", that developers generally should know. -(function() { - alert("Parentheses around the whole thing"); -}()*!*)*/!*; +A [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exception, to be covered in ). -*!*!*/!*function() { - alert("Bitwise NOT operator starts the expression"); -}(); +That is: they automatically remember where they were created using a hidden `[[Environment]]` property, and then their code can access outer variables. -*!*+*/!*function() { - alert("Unary plus starts the expression"); -}(); +When on an interview, a frontend developer gets a question about "what's a closure?", a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the `[[Environment]]` property and how Lexical Environments work. ``` -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. - ## Garbage collection -Usually, a Lexical Environment is cleaned up and deleted after the function runs. For instance: +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. -```js -function f() { - let value1 = 123; - let value2 = 456; -} - -f(); -``` +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. -Here, two values are technically the properties of the Lexical Environment. But after `f()` finishes, that Lexical Environment becomes unreachable, so it's deleted from the memory. +In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive. -...But if there's a nested function that is still reachable after the end of `f`, then it has `[[Environment]]` property that references the outer lexical environment, so it's also reachable and alive: +For example: ```js function f() { let value = 123; - function g() { alert(value); } - -*!* - return g; -*/!* + return function() { + alert(value); + } } -let func = f(); // func gets a reference to g -// so it stays and memory and its outer lexical environment stays as well +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() { @@ -585,20 +349,20 @@ let arr = [f(), f(), f()]; A Lexical Environment object dies when it becomes unreachable (just like any other object). In other words, it exists only while there's at least one nested function referencing it. -In the code below, after `g` becomes unreachable, its enclosing Lexical Environment (and hence the `value`) is cleaned from memory; +In the code below, after the nested function is removed, its enclosing Lexical Environment (and hence the `value`) is cleaned from memory: ```js function f() { let value = 123; - function g() { alert(value); } - - return g; + return function() { + alert(value); + } } -let func = f(); // while func has a reference to g, it stays in memory +let g = f(); // while g function exists, the value stays in memory -func = null; // ...and now the memory is cleaned up +g = null; // ...and now the memory is cleaned up ``` ### Real-life optimizations @@ -607,7 +371,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. @@ -649,9 +413,6 @@ let g = f(); g(); ``` -```warn header="See ya!" -This feature of V8 is good to know. If you are debugging with Chrome/Opera, sooner or later you will meet it. +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 always can check for it by running the examples on this page. -``` +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. diff --git a/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg b/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg new file mode 100644 index 000000000..3ef787875 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg @@ -0,0 +1 @@ +outernullexecution startphrase: <uninitialized> say: function... \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg new file mode 100644 index 000000000..f78441712 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg @@ -0,0 +1 @@ +null[[Environment]]makeCounter: function counter: undefinedcount: 0outerouter \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg new file mode 100644 index 000000000..3950a8faa --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg @@ -0,0 +1 @@ +count: 1<empty>nullouterouteroutermakeCounter: function counter: functionmodified here \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg new file mode 100644 index 000000000..24315bf21 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg @@ -0,0 +1 @@ +count: 0<empty>nullouterouteroutermakeCounter: function counter: function \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter.svg new file mode 100644 index 000000000..2ca06455a --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter.svg @@ -0,0 +1 @@ +makeCounter: function counter: undefinedcount: 0nullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() callouterouter \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg b/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg new file mode 100644 index 000000000..b9bb12fff --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg @@ -0,0 +1 @@ +phrase: "Bye"phrase: "Hello"phrase: undefinedphrase: <uninitialized>outernullexecution start \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg deleted file mode 100644 index e5b7f83e7..000000000 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg +++ /dev/null @@ -1 +0,0 @@ -makeWorker: function name: "John"<empty>outerouterouternullname: "Pete" \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg b/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg new file mode 100644 index 000000000..f1f1d3b1d --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg @@ -0,0 +1 @@ +functionUser(name){this.sayHi=function(){alert(name);};}letuser=newUser("John");user.sayHi(); \ No newline at end of file diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 02fd43fe5..1579afb62 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -1,37 +1,34 @@ # The old "var" +```smart header="This article is for understanding old scripts" +The information in this article is useful for understanding old scripts. + +That's not how we write new code. +``` + In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration: 1. `let` 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" +var message = "Hi"; +alert(message); // Hi +``` - alert(phrase); // Hello -} +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. -sayHi(); +If you don't plan on meeting such scripts you may even skip this chapter or postpone it. -alert(phrase); // Error, phrase is not defined -``` - -...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: @@ -55,7 +52,7 @@ if (true) { } *!* -alert(test); // Error: test is not defined +alert(test); // ReferenceError: test is not defined */!* ``` @@ -63,11 +60,13 @@ The same thing for loops: `var` cannot be block- or loop-local: ```js 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 */!* ``` @@ -83,12 +82,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. + +## "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 ``` -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. +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 -## "var" declarations are processed at the function start +alert(user); // John +``` + +## "var" variables can be declared below their use `var` declarations are processed when the function starts (or script starts for globals). @@ -147,7 +166,7 @@ So in the example above, `if (false)` branch never executes, but that doesn't ma **Declarations are hoisted, but assignments are not.** -That's better to demonstrate with an example, like this: +That's best demonstrated with an example: ```js run function sayHi() { @@ -186,15 +205,83 @@ 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. ## Summary 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 minor difference related to the global object, we'll cover that in the next chapter. +There's one more very minor difference related to the global object, that we'll cover in the next chapter. These differences make `var` worse than `let` most of the time. Block-level variables is such a great thing. That's why `let` was introduced in the standard long ago, and is now a major way (along with `const`) to declare a variable. 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..40131e339 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. +The same effect have function declarations (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..12342f45a 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", how the function can call itself internally. ```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..984102687 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -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 @@ -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,7 +290,7 @@ 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. 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..6df7af132 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. + +The difference with debounce is that it's completely different decorator: +- `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 373500e13..c5d785493 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. @@ -149,8 +149,8 @@ let user = { name: "John" }; let admin = { name: "Admin" }; // use call to pass different objects as "this" -sayHi.call( user ); // this = John -sayHi.call( admin ); // this = Admin +sayHi.call( user ); // John +sayHi.call( admin ); // Admin ``` And here we use `call` to call `say` with the given context and phrase: @@ -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,18 @@ The only syntax difference between `call` and `apply` is that `call` expects a l So these two calls are almost equivalent: ```js -func.call(context, ...args); // pass an array as list with spread operator -func.apply(context, args); // is same as using apply +func.call(context, ...args); +func.apply(context, args); ``` -There's only a minor difference: +They perform the same call of `func` with given context and arguments. -- The spread operator `...` allows to pass *iterable* `args` as the list to `call`. -- The `apply` accepts only *array-like* `args`. +There's only a subtle difference regarding `args`: -So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works. +- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. +- The `apply` accepts only *array-like* `args`. -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 +346,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/article.md b/1-js/06-advanced-functions/10-bind/article.md index 16a50942d..9d705cdcd 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)](http://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: @@ -279,7 +279,7 @@ What if we'd like to fix some arguments, but not the context `this`? For example The native `bind` does not allow that. We can't just omit the context and jump to arguments. -Fortunately, a helper function `partial` for binding only arguments can be easily implemented. +Fortunately, a function `partial` for binding only arguments can be easily implemented. Like this: @@ -313,7 +313,7 @@ The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that call - Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`) - Then gives it `...args` -- arguments given to the wrapper (`"Hello"`) -So easy to do it with the spread operator, right? +So easy to do it with the spread syntax, right? Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library. 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 e894f0662..bdc693418 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: @@ -66,7 +66,7 @@ Object.defineProperty(obj, propertyName, descriptor) : The object and its property to apply the descriptor. `descriptor` -: Property descriptor to apply. +: Property descriptor object to apply. If the property exists, `defineProperty` updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed `false`. @@ -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 726529c5b..45b9e70ed 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 properties is something new. It's *accessor properties*. 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: @@ -94,11 +94,7 @@ alert(user.name); // Alice alert(user.surname); // Cooper ``` -As the result, we have a "virtual" property `fullName`. It is readable and writable, but in fact does not exist. - -```smart header="No way to handle `delete`" -There's no similar method to handle deletion of an accessor property. Only getter/setter methods may exist. -``` +As the result, we have a "virtual" property `fullName`. It is readable and writable. ## Accessor descriptors @@ -138,7 +134,7 @@ alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ``` -Please note once again that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both. +Please note that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both. If we try to supply both `get` and `value` in the same descriptor, 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 69e7c5f5c..8cb301c80 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -12,11 +12,11 @@ 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. -One of them is to use `__proto__`, like this: +One of them is to use the special name `__proto__`, like this: ```js run let animal = { @@ -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,19 @@ 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 +204,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 +287,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/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md index c106d1d90..b1ef51826 100644 --- a/1-js/08-prototypes/02-function-prototype/article.md +++ b/1-js/08-prototypes/02-function-prototype/article.md @@ -41,7 +41,7 @@ That's the resulting picture: On the picture, `"prototype"` is a horizontal arrow, meaning a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`. ```smart header="`F.prototype` only used at `new F` time" -`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. After that, there's no connection between `F.prototype` and the new object. Think of it as a "one-time gift". +`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. If, after the creation, `F.prototype` property changes (`F.prototype = `), then new objects created by `new F` will have another object as `[[Prototype]]`, but already existing objects keep the old one. ``` diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index 378936c9a..6cf7aebb4 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -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/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index 80f5a956a..a4ce2646c 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -7,7 +7,7 @@ The `__proto__` is considered outdated and somewhat deprecated (in browser-only The modern methods are: -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. +- [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`. @@ -57,7 +57,6 @@ The descriptors are in the same format as described in the chapter = " in the declaration, and that's it. -alert(User.prototype.sayHi); // placed in User.prototype -alert(User.prototype.name); // undefined, not placed in User.prototype +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 ``` -The property `name` is not placed into `User.prototype`. Instead, it is created by `new` before calling the constructor, it's a property of the object itself. +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 +``` + + +### Making bound methods with class fields + +As demonstrated in the chapter functions in JavaScript have a dynamic `this`. It depends on the context of the call. + +So if an object method is passed around and called in another context, `this` won't be a reference to its object any more. + +For instance, this code will show `undefined`: + +```js run +class Button { + constructor(value) { + this.value = value; + } + + click() { + alert(this.value); + } +} + +let button = new Button("hello"); + +*!* +setTimeout(button.click, 1000); // undefined +*/!* +``` + +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)`. +2. Bind the method to object, e.g. in the constructor. + +Class fields provide another, quite elegant syntax: + +```js run +class Button { + constructor(value) { + this.value = value; + } +*!* + click = () => { + alert(this.value); + } +*/!* +} + +let button = new Button("hello"); + +setTimeout(button.click, 1000); // hello +``` + +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. ## Summary diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js index ca613ca5e..be2053cfc 100644 --- a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js @@ -1,7 +1,7 @@ class ExtendedClock extends Clock { constructor(options) { super(options); - let { precision=1000 } = options; + let { precision = 1000 } = options; this.precision = precision; } diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 1000fdd84..48c9e468e 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -16,7 +16,7 @@ class Animal { this.name = name; } run(speed) { - this.speed += speed; + this.speed = speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { @@ -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); } + }; } *!* @@ -124,7 +124,7 @@ class Animal { } run(speed) { - this.speed += speed; + this.speed = speed; alert(`${this.name} runs with speed ${this.speed}.`); } @@ -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. @@ -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: @@ -279,6 +281,102 @@ 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]] ```warn header="Advanced information" @@ -438,7 +536,7 @@ It works as intended, due to `[[HomeObject]]` mechanics. A method, such as `long As we've known before, generally functions are "free", not bound to objects in JavaScript. So they can be copied between objects and called with another `this`. -The very existance of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever. +The very existence of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever. The only place in the language where `[[HomeObject]]` is used -- is `super`. So, if a method does not use `super`, then we can still consider it free and copy between objects. But with `super` things may go wrong. @@ -447,7 +545,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 +559,7 @@ let rabbit = { let plant = { sayHi() { - console.log("I'm a plant"); + alert("I'm a plant"); } }; @@ -478,7 +576,7 @@ tree.sayHi(); // I'm an animal (?!?) */!* ``` -A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong. +A call to `tree.sayHi()` shows "I'm an animal". Definitely wrong. The reason is simple: - In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication? @@ -499,7 +597,7 @@ In the example below a non-method syntax is used for comparison. `[[HomeObject]] ```js run let animal = { - eat: function() { // intentially writing like this instead of eat() {... + eat: function() { // intentionally writing like this instead of eat() {... // ... } }; 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 100% 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 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 92% 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 index b82a4255e..1d0f98a74 100644 --- 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 @@ -1,4 +1,4 @@ -importance: 5 +importance: 3 --- @@ -38,5 +38,5 @@ class Rabbit extends Object { let rabbit = new Rabbit("Rab"); -alert( rabbit.hasOwnProperty('name') ); // true +alert( rabbit.hasOwnProperty('name') ); // Error ``` 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 dd09a0262..c75ec257f 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -20,7 +20,7 @@ User.staticMethod(); // true That actually does the same as assigning it as a property directly: ```js run -class User() { } +class User { } User.staticMethod = function() { alert(this === User); @@ -125,7 +125,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 6d77e8ed7..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; } } @@ -279,7 +285,7 @@ With private fields that's impossible: `this['#name']` doesn't work. That's a sy ## Summary -In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)"). +In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)). It gives the following benefits: 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..06001d900 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() { *!* @@ -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 09b1ee398..a928da289 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,14 @@ -# 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. -But there's a syntax construct `try..catch` that allows to "catch" errors and, 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. -## 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,14 +25,14 @@ try { It works like this: 1. First, the code in `try {...}` is executed. -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 `try` execution is stopped, and the control flows to the beginning of `catch(err)`. The `err` variable (can use any name for it) will contain an error object with details about what happened. +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`. +So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`. -Let's see examples. +Let's look at some examples. - An errorless example: shows `alert` `(1)` and `(2)`: @@ -45,7 +45,7 @@ Let's see examples. alert('End of try runs'); // *!*(2) <--*/!* - } catch(err) { + } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) @@ -64,7 +64,7 @@ Let's see examples. alert('End of try (never reached)'); // (2) - } catch(err) { + } catch (err) { alert(`Error has occurred!`); // *!*(3) <--*/!* @@ -72,45 +72,45 @@ Let's see 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. -So, `try..catch` can only handle errors that occur in the 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". ```` -````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 +125,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 +150,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 +175,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 +205,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 +217,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 +245,7 @@ try { alert( user.name ); // no name! */!* -} catch (e) { +} catch (err) { alert( "doesn't execute" ); } ``` @@ -294,11 +294,11 @@ 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 */!* - alert(e.message); // Unexpected token o in JSON at position 2 + alert(err.message); // Unexpected token b in JSON at position 2 } ``` @@ -323,8 +323,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 +334,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 +345,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 +353,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: + +**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`. -Fortunately, we can find out which error we get, for instance from its `name`: +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 +399,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 +427,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 +439,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 +464,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 +477,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 +513,7 @@ let start = Date.now(); try { result = fib(num); -} catch (e) { +} catch (err) { result = 0; *!* } finally { @@ -527,14 +531,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 +550,7 @@ function func() { return 1; */!* - } catch (e) { + } catch (err) { /* ... */ } finally { *!* @@ -559,9 +563,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 +586,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. @@ -639,14 +643,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 +658,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 +666,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 b48313322..918289319 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 @@ -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 { @@ -215,11 +215,39 @@ Now custom errors are much shorter, especially `ValidationError`, as we got rid The purpose of the function `readUser` in the code above is "to read the user data". There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow and probably generate other kinds of errors. -The code which calls `readUser` should handle these errors. Right now it uses multiple `if`s in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. But if the `readUser` function generates several kinds of errors, then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`? +The code which calls `readUser` should handle these errors. Right now it uses multiple `if`s in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. + +The scheme is like this: + +```js +try { + ... + readUser() // the potential error source + ... +} catch (err) { + if (err instanceof ValidationError) { + // handle validation errors + } else if (err instanceof SyntaxError) { + // handle syntax errors + } else { + throw err; // unknown error, rethrow it + } +} +``` + +In the code above we can see two types of errors, but there can be more. + +If the `readUser` function generates several kinds of errors, then we should ask ourselves: do we really want to check for all error types one-by-one every time? + +Often the answer is "No": we'd like to be "one level above all that". We just want to know if there was a "data reading error" -- why exactly it happened is often irrelevant (the error message describes it). Or, even better, we'd like to have a way to get the error details, but only if we need to. + +The technique that we describe here is called "wrapping exceptions". -Often the answer is "No": the outer code wants to be "one level above all that", it just wants to have some kind of "data reading error" -- why exactly it happened is often irrelevant (the error message describes it). Or, even better, it could have a way to get the error details, but only if we need to. +1. We'll make a new class `ReadError` to represent a generic "data reading" error. +2. The function `readUser` will catch data reading errors that occur inside it, such as `ValidationError` and `SyntaxError`, and generate a `ReadError` instead. +3. The `ReadError` object will keep the reference to the original error in its `cause` property. -So let's make a new class `ReadError` to represent such errors. If an error occurs inside `readUser`, we'll catch it there and generate `ReadError`. We'll also keep the reference to the original error in its `cause` property. Then the outer code will only have to check for `ReadError`. +Then the code that calls `readUser` will only have to check for `ReadError`, not for every kind of data reading errors. And if it needs more details of an error, it can check its `cause` property. Here's the code that defines `ReadError` and demonstrates its use in `readUser` and `try..catch`: @@ -293,7 +321,7 @@ In the code above, `readUser` works exactly as described -- catches syntax and v So the outer code checks `instanceof ReadError` and that's it. No need to list all possible error types. -The approach is called "wrapping exceptions", because we take "low level exceptions" and "wrap" them into `ReadError` that is more abstract and more convenient to use for the calling code. It is widely used in object-oriented programming. +The approach is called "wrapping exceptions", because we take "low level" exceptions and "wrap" them into `ReadError` that is more abstract. It is widely used in object-oriented programming. ## Summary diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index daab93316..5950df051 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -2,15 +2,17 @@ # Introduction: callbacks -```warn header="We use browser methods here" -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. +```warn header="We use browser methods in examples here" +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, or if you would just like to understand them better, you may want to read a few chapters from the [next part](/document) of the tutorial. +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 actions in JavaScript are *asynchronous*. In other words, we initiate them now, but they finish later. +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, we can schedule such actions using `setTimeout`. +For instance, one such function is the `setTimeout` function. There are other real-world examples of asynchronous actions, e.g. loading scripts and modules (we'll cover them in later chapters). @@ -18,13 +20,15 @@ Take a look at the function `loadScript(src)`, that loads a script with the give ```js function loadScript(src) { + // creates a ``` -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. -That has important consequences. Let's see that on examples. +The one-time evaluation has important consequences, that we should be aware of. + +Let's see a couple of examples. 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 +146,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 +175,67 @@ 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`. -Such behavior allows 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 modules will see that. -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 +261,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 @@ -256,7 +288,7 @@ Compare to regular script below: @@ -268,11 +300,11 @@ Please note: the second script actually runs before the first! So we'll see `und That's because modules are deferred, so we wait for the document to be processed. The regular script runs immediately, so we see its output first. -When using modules, we should be aware that HTML-page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that. +When using modules, we should be aware that the HTML page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that. ### Async works on inline scripts -For non-module scripts, `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document. +For non-module scripts, the `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document. For module scripts, it works on inline scripts as well. diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index f5465831d..10e47820f 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -2,7 +2,7 @@ Export and import directives have several syntax variants. -In the previous chapter we saw a simple use, now let's explore more examples. +In the previous article we saw a simple use, now let's explore more examples. ## Export before declarations @@ -26,7 +26,7 @@ For instance, here all exports are valid: ``` ````smart header="No semicolons after export class/function" -Please note that `export` before a class or a function does not make it a [function expression](info:function-expressions-arrows). It's still a function declaration, albeit exported. +Please note that `export` before a class or a function does not make it a [function expression](info:function-expressions). It's still a function declaration, albeit exported. Most JavaScript style guides don't recommend semicolons after function and class declarations. @@ -162,7 +162,7 @@ Mostly, the second approach is preferred, so that every "thing" resides in its o Naturally, that requires a lot of files, as everything wants its own module, but that's not a problem at all. Actually, code navigation becomes easier if files are well-named and structured into folders. -Modules provide special `export default` ("the default export") syntax to make the "one thing per module" way look better. +Modules provide a special `export default` ("the default export") syntax to make the "one thing per module" way look better. Put `export default` before the entity to export: @@ -216,9 +216,9 @@ export default function(user) { // no function name export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; ``` -Not giving a name is fine, because `export default` is only one per file, so `import` without curly braces knows what to import. +Not giving a name is fine, because there is only one `export default` per file, so `import` without curly braces knows what to import. -Without `default`, such export would give an error: +Without `default`, such an export would give an error: ```js export class { // Error! (non-default export needs a name) @@ -241,7 +241,7 @@ function sayHi(user) { export {sayHi as default}; ``` -Or, another situation, let's say a module `user.js` exports one main "default" thing and a few named ones (rarely the case, but happens): +Or, another situation, let's say a module `user.js` exports one main "default" thing, and a few named ones (rarely the case, but it happens): ```js // 📁 user.js @@ -277,9 +277,9 @@ new User('John'); ### A word against default exports -Named exports are explicit. They exactly name what they import, so we have that information from them, that's a good thing. +Named exports are explicit. They exactly name what they import, so we have that information from them; that's a good thing. -Named exports enforce us to use exactly the right name to import: +Named exports force us to use exactly the right name to import: ```js import {User} from './user.js'; @@ -321,7 +321,7 @@ export {default as User} from './user.js'; // re-export default Why would that be needed? Let's see a practical use case. -Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow to publish and distribute such packages), and many modules are just "helpers", for the 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. The file structure could be like this: ``` @@ -337,13 +337,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 +372,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,7 +395,9 @@ 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. @@ -399,11 +409,11 @@ export default class User { 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 -Here are all types of `export` that we covered in this and previous chapters. +Here are all types of `export` that we covered in this and previous articles. You can check yourself by reading them and recalling what they mean: @@ -418,14 +428,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. @@ -439,7 +449,7 @@ sayHi(); import {sayHi} from './say.js'; // import at the end of the file ``` -In practice imports are usually at the start of the file, but that's only for better convenience. +In practice imports are usually at the start of the file, but that's only for more convenience. **Please note that import/export statements don't work if inside `{...}`.** @@ -452,4 +462,4 @@ if (something) { ...But what if we really need to import something conditionally? Or at the right time? Like, load a module upon request, when it's really needed? -We'll see dynamic imports in the next chapter. +We'll see dynamic imports in the next article. diff --git a/1-js/13-modules/03-modules-dynamic-imports/article.md b/1-js/13-modules/03-modules-dynamic-imports/article.md index b638fd347..e48144a3e 100644 --- a/1-js/13-modules/03-modules-dynamic-imports/article.md +++ b/1-js/13-modules/03-modules-dynamic-imports/article.md @@ -94,5 +94,5 @@ Dynamic imports work in regular scripts, they don't require `script type="module ```smart Although `import()` looks like a function call, it's a special syntax that just happens to use parentheses (similar to `super()`). -So we can't copy `import` to a variable or use `call/apply` with it. That's not a function. +So we can't copy `import` to a variable or use `call/apply` with it. It's not a function. ``` 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 648958530..9db69cb2f 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,5 @@ function wrap(target) { user = wrap(user); alert(user.name); // John -alert(user.age); // ReferenceError: Property doesn't exist +alert(user.age); // ReferenceError: Property doesn't exist: "age" ``` 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 f890256ce..47985e1a7 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,6 @@ user = wrap(user); alert(user.name); // John *!* -alert(user.age); // Error: Property doesn't exist +alert(user.age); // ReferenceError: Property doesn't exist: "age" */!* ``` diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md index 0ae375f77..1f84912e5 100644 --- a/1-js/99-js-misc/01-proxy/article.md +++ b/1-js/99-js-misc/01-proxy/article.md @@ -2,7 +2,9 @@ A `Proxy` object wraps another object and intercepts operations, like reading/writing properties and others, optionally handling them on its own, or transparently allowing the object to handle them. -Proxies are used in many libraries and some browser frameworks. We'll see many practical applications in this chapter. +Proxies are used in many libraries and some browser frameworks. We'll see many practical applications in this article. + +## Proxy The syntax: @@ -37,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`. @@ -59,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. @@ -244,7 +246,7 @@ If we forget to do it or return any falsy value, the operation triggers `TypeErr Such methods differ in details: - `Object.getOwnPropertyNames(obj)` returns non-symbol keys. - `Object.getOwnPropertySymbols(obj)` returns symbol keys. -- `Object.keys/values()` returns non-symbol keys/values with `enumerable` flag (property flags were explained in the chapter ). +- `Object.keys/values()` returns non-symbol keys/values with `enumerable` flag (property flags were explained in the article ). - `for..in` loops over non-symbol keys with `enumerable` flag, and also prototype keys. ...But all of them start with that list. @@ -333,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 `_`. @@ -374,7 +376,7 @@ user = new Proxy(user, { }, *!* deleteProperty(target, prop) { // to intercept property deletion -*/!* +*/!* if (prop.startsWith('_')) { throw new Error("Access denied"); } else { @@ -435,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. @@ -446,7 +448,7 @@ Besides, an object may be proxied multiple times (multiple proxies may add diffe So, such a proxy shouldn't be used everywhere. ```smart header="Private properties of a class" -Modern JavaScript engines natively support private properties in classes, prefixed with `#`. They are described in the chapter . No proxies required. +Modern JavaScript engines natively support private properties in classes, prefixed with `#`. They are described in the article . No proxies required. Such properties have their own issues though. In particular, they are not inherited. ``` @@ -485,7 +487,7 @@ range = new Proxy(range, { *!* has(target, prop) { */!* - return prop >= target.start && prop <= target.end + return prop >= target.start && prop <= target.end; } }); @@ -507,9 +509,9 @@ The `apply(target, thisArg, args)` trap handles calling a proxy as function: - `thisArg` is the value of `this`. - `args` is a list of arguments. -For example, let's recall `delay(f, ms)` decorator, that we did in the chapter . +For example, let's recall `delay(f, ms)` decorator, that we did in the article . -In that chapter we did it without proxies. A call to `delay(f, ms)` returned a function that forwards all calls to `f` after `ms` milliseconds. +In that article we did it without proxies. A call to `delay(f, ms)` returned a function that forwards all calls to `f` after `ms` milliseconds. Here's the previous, function-based implementation: @@ -587,7 +589,7 @@ The result is the same, but now not only calls, but all operations on the proxy We've got a "richer" wrapper. -Other traps exist: the full list is in the beginning of this chapter. Their usage pattern is similar to the above. +Other traps exist: the full list is in the beginning of this article. Their usage pattern is similar to the above. ## Reflect @@ -619,7 +621,7 @@ alert(user.name); // John In particular, `Reflect` allows us to call operators (`new`, `delete`...) as functions (`Reflect.construct`, `Reflect.deleteProperty`, ...). That's an interesting capability, but here another thing is important. -**For every internal method, trappable by `Proxy`, there's a corresponding method in `Reflect`, with the same name and arguments as `Proxy` trap.** +**For every internal method, trappable by `Proxy`, there's a corresponding method in `Reflect`, with the same name and arguments as the `Proxy` trap.** So we can use `Reflect` to forward an operation to the original object. @@ -660,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. @@ -838,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: @@ -961,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 *!* @@ -978,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 @@ -1014,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 1ae2a12a0..d71ac23f8 100644 --- a/1-js/99-js-misc/03-currying-partials/article.md +++ b/1-js/99-js-misc/03-currying-partials/article.md @@ -39,7 +39,7 @@ alert( curriedSum(1)(2) ); // 3 As you can see, the implementation is straightforward: it's just two wrappers. - The result of `curry(func)` is a wrapper `function(a)`. -- When it is called like `sum(1)`, the argument is saved in the Lexical Environment, and a new wrapper is returned `function(b)`. +- When it is called like `curriedSum(1)`, the argument is saved in the Lexical Environment, and a new wrapper is returned `function(b)`. - Then this wrapper is called with `2` as an argument, and it passes the call to the original `sum`. More advanced implementations of currying, such as [_.curry](https://lodash.com/docs#curry) from lodash library, return a wrapper that allows a function to be called both normally and partially: @@ -73,7 +73,7 @@ Let's curry it! log = _.curry(log); ``` -After that `log` work normally: +After that `log` works normally: ```js log(new Date(), "DEBUG", "some debug"); // log(a, b, c) @@ -111,7 +111,7 @@ So: ## Advanced curry implementation -In case you'd like to get in details, here's the "advanced" curry implementation for multi-argument functions that we could use above. +In case you'd like to get in to the details, here's the "advanced" curry implementation for multi-argument functions that we could use above. It's pretty short: @@ -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,18 +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. -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)`: - -1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`. -2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together. As the argument count is still less than 3, `curry` returns `pass`. -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 the paper. +Then, if we call it, again, we'll get either a new partial (if not enough arguments) or, finally, the result. ```smart header="Fixed-length functions only" The currying requires the function to have a fixed number of arguments. @@ -191,6 +183,6 @@ But most implementations of currying in JavaScript are advanced, as described: t ## Summary -*Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if arguments count is not enough. +*Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if the arguments count is not enough. -Currying allows to easily get partials. As we've seen in the logging example: the universal function `log(date, importance, message)` after currying gives us partials when called with one argument like `log(date)` or two arguments `log(date, importance)`. +Currying allows us to easily get partials. As we've seen in the logging example, after currying the three argument universal function `log(date, importance, message)` gives us partials when called with one argument (like `log(date)`) or two arguments (like `log(date, importance)`). 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 81% 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 0534202a8..ba5d3bf04 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,4 @@ let user = { (user.go)() // John ``` -Please note that brackets 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. +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. 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 65% 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 89bc0d722..e4ee78748 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 @@ -3,9 +3,9 @@ Here's the explanations. 1. That's a regular object method call. -2. The same, brackets do not change the order of operations here, the dot is first anyway. +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).method()`. The call works as if it were split into two lines: +3. Here we have a more complex call `(expression)()`. The call works as if it were split into two lines: ```js no-beautify f = obj.go; // calculate the expression @@ -14,7 +14,7 @@ Here's the explanations. Here `f()` is executed as a function, without `this`. -4. The similar thing as `(3)`, to the left of the dot `.` we have an expression. +4. The similar thing as `(3)`, to the left of the parentheses `()` we have an expression. To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type. 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..1ec378059 --- /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 new file mode 100644 index 000000000..2a1cfc843 --- /dev/null +++ b/1-js/99-js-misc/05-bigint/article.md @@ -0,0 +1,130 @@ +# BigInt + +[recent caniuse="bigint"] + +`BigInt` is a special numeric type that provides support for integers of arbitrary length. + +A bigint is created by appending `n` to the end of an integer literal or by calling the function `BigInt` that creates bigints from strings, numbers etc. + +```js +const bigint = 1234567890123456789012345678901234567890n; + +const sameBigint = BigInt("1234567890123456789012345678901234567890"); + +const bigintFromNumber = BigInt(10); // same as 10n +``` + +## Math operators + +`BigInt` can mostly be used like a regular number, for example: + +```js run +alert(1n + 2n); // 3 + +alert(5n / 2n); // 2 +``` + +Please note: the division `5/2` returns the result rounded towards zero, without the decimal part. All operations on bigints return bigints. + +We can't mix bigints and regular numbers: + +```js run +alert(1n + 2); // Error: Cannot mix BigInt and other types +``` + +We should explicitly convert them if needed: using either `BigInt()` or `Number()`, like this: + +```js run +let bigint = 1n; +let number = 2; + +// number to bigint +alert(bigint + BigInt(number)); // 3 + +// bigint to number +alert(Number(bigint) + number); // 3 +``` + +The conversion operations are always silent, never give errors, but if the bigint is too huge and won't fit the number type, then extra bits will be cut off, so we should be careful doing such conversion. + +````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. + +In order to avoid confusion, it's not supported on bigints: +```js run +let bigint = 1n; + +alert( +bigint ); // error +``` +So we should use `Number()` to convert a bigint to a number. +```` + +## Comparisons + +Comparisons, such as `<`, `>` work with bigints and numbers just fine: + +```js run +alert( 2n > 1n ); // true + +alert( 2n > 1 ); // true +``` + +Please note though, as numbers and bigints belong to different types, they can be equal `==`, but not strictly equal `===`: + +```js run +alert( 1 == 1n ); // true + +alert( 1 === 1n ); // false +``` + +## Boolean operations + +When inside `if` or other boolean operations, bigints behave like numbers. + +For instance, in `if`, bigint `0n` is falsy, other values are truthy: + +```js run +if (0n) { + // never executes +} +``` + +Boolean operators, such as `||`, `&&` and others also work with bigints similar to numbers: + +```js run +alert( 1n || 2 ); // 1 (1n is considered truthy) + +alert( 0n || 2 ); // 2 (0n is considered falsy) +``` + +## Polyfills + +Polyfilling bigints is tricky. The reason is that many JavaScript operators, such as `+`, `-` and so on behave differently with bigints compared to regular numbers. + +For example, division of bigints always returns a bigint (rounded if necessary). + +To emulate such behavior, a polyfill would need to analyze the code and replace all such operators with its functions. But doing so is cumbersome and would cost a lot of performance. + +So, there's no well-known good polyfill. + +Although, the other way around is proposed by the developers of [JSBI](https://github.com/GoogleChromeLabs/jsbi) library. + +This library implements big numbers using its own methods. We can use them instead of native bigints: + +| Operation | native `BigInt` | JSBI | +|-----------|-----------------|------| +| Creation from Number | `a = BigInt(789)` | `a = JSBI.BigInt(789)` | +| Addition | `c = a + b` | `c = JSBI.add(a, b)` | +| Subtraction | `c = a - b` | `c = JSBI.subtract(a, b)` | +| ... | ... | ... | + +...And then use the polyfill (Babel plugin) to convert JSBI calls to native bigints for those browsers that support them. + +In other words, this approach suggests that we write code in JSBI instead of native bigints. But JSBI works with numbers as with bigints internally, emulates them closely following the specification, so the code will be "bigint-ready". + +We can use such JSBI code "as is" for engines that don't support bigints and for those that do support - the polyfill will convert the calls to native bigints. + +## References + +- [MDN docs on BigInt](mdn:/JavaScript/Reference/Global_Objects/BigInt). +- [Specification](https://tc39.es/ecma262/#sec-bigint-objects). diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index f680554dd..43dec976a 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -2,11 +2,11 @@ The JavaScript language was initially created for web browsers. Since then it has evolved and become 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*, even a "smart" coffee machine, if it can run JavaScript. Each of them 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. -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) @@ -17,7 +17,7 @@ There's a "root" object called `window`. It has two roles: For instance, here we use it as a global object: -```js run +```js run global function sayHi() { alert("Hello"); } @@ -49,9 +49,7 @@ 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. @@ -60,9 +58,9 @@ For instance, server-side scripts that download HTML pages and process them can ``` ```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. +CSSOM is used together with DOM when we modify style rules for the document. In practice though, 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) diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index 019398be9..e18335f38 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. diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index 332f57827..b5f03098c 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -201,7 +201,7 @@ The parent is available as `parentNode`. For example: -```js +```js run // parent of is alert( document.body.parentNode === document.documentElement ); // true @@ -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 cc878009f..5af6435ce 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -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] @@ -103,7 +103,7 @@ Here we look for all `
  • ` elements that are last children: This method is indeed powerful, because any CSS selector can be used. ```smart header="Can use pseudo-classes as well" -Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `` to the most nested one). +Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `` to the most nested one). ``` ## querySelector [#querySelector] @@ -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. @@ -178,7 +178,7 @@ So here we cover them mainly for completeness, while you can still find them in - `elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` parameter can also be a star `"*"` for "any tags". - `elem.getElementsByClassName(className)` returns elements that have the given CSS class. -- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. very rarely used. +- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. Very rarely used. For instance: ```js @@ -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 78bc3fd88..b1d6486f4 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 @@ -20,12 +20,14 @@ The classes are: - [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`. +- [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: - [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. @@ -36,7 +38,7 @@ It gets properties and methods as a superposition of (listed in inheritance orde - `HTMLInputElement` -- this class provides input-specific properties, - `HTMLElement` -- it provides common HTML element methods (and getters/setters), - `Element` -- provides generic element methods, -- `Node` -- provides common DOM node properties,. +- `Node` -- provides common DOM node properties, - `EventTarget` -- gives the support for events (to be covered), - ...and finally it inherits from `Object`, so "plain object" methods like `hasOwnProperty` are also available. @@ -128,7 +130,7 @@ For instance: ```html run - diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/solution.md b/2-ui/1-document/07-modifying-document/12-sort-table/solution.md index 31bcf8760..49243e8e3 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/solution.md +++ b/2-ui/1-document/07-modifying-document/12-sort-table/solution.md @@ -1,19 +1,18 @@ The solution is short, yet may look a bit tricky, so here I provide it with extensive comments: - ```js -let sortedRows = Array.from(table.rows) - .slice(1) - .sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1); +let sortedRows = Array.from(table.tBodies[0].rows) // 1 + .sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML)); -table.tBodies[0].append(...sortedRows); +table.tBodies[0].append(...sortedRows); // (3) ``` -1. Get all ``, like `table.querySelectorAll('tr')`, then make an array from them, cause we need array methods. -2. The first TR (`table.rows[0]`) is actually a table header, so we take the rest by `.slice(1)`. -3. Then sort them comparing by the content of the first `` (the name field). -4. Now insert nodes in the right order by `.append(...sortedRows)`. +The step-by-step algorthm: + +1. Get all ``, from ``. +2. Then sort them comparing by the content of the first `` (the name field). +3. Now insert nodes in the right order by `.append(...sortedRows)`. - Tables always have an implicit `` element, so we need to take it and insert into it: a simple `table.append(...)` would fail. +We don't have to remove row elements, just "re-insert", they leave the old place automatically. - Please note: we don't have to remove them, just "re-insert", they leave the old place automatically. +P.S. In our case, there's an explicit `` in the table, but even if HTML table doesn't have ``, the DOM structure always has it. diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html b/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html index 81e985748..40692031a 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html @@ -1,37 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - -
    NameSurnameAge
    JohnSmith10
    PeteBrown15
    AnnLee5
    + + + + + + + + + + + + + + + + + + + + +
    NameSurnameAge
    JohnSmith10
    PeteBrown15
    AnnLee5
    .........
    - - - - + table.tBodies[0].append(...sortedRows); + diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html b/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html index e41eb229f..9071c88ee 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html +++ b/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html @@ -1,33 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - -
    NameSurnameAge
    JohnSmith10
    PeteBrown15
    AnnLee5
    + + + + + + + + + + + + + + + + + + + + +
    NameSurnameAge
    JohnSmith10
    PeteBrown15
    AnnLee5
    .........
    - - - - + diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/task.md b/2-ui/1-document/07-modifying-document/12-sort-table/task.md index 41d6fca29..7cdba35bc 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/task.md +++ b/2-ui/1-document/07-modifying-document/12-sort-table/task.md @@ -6,33 +6,29 @@ importance: 5 There's a table: +```html run - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + +
    NameSurnameAge
    JohnSmith10
    PeteBrown15
    AnnLee5
    .........
    NameSurnameAge
    JohnSmith10
    PeteBrown15
    AnnLee5
    .........
    +``` There may be more rows in it. 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/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/9-calendar-table/solution.md b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md index 67bb5e13d..de8be56e9 100644 --- a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md +++ b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md @@ -3,7 +3,7 @@ We'll create the table as a string: `"
  • ...
    "`, and then assign it t The algorithm: 1. Create the table header with `` and weekday names. -1. Create the date object `d = new Date(year, month-1)`. That's the first day of `month` (taking into account that months in JavaScript start from `0`, not `1`). -2. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with ``. -3. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `` to the calendar. If that's a Sunday, then add a newline "</tr><tr>". -4. If the month has finished, but the table row is not yet full, add empty `` into it, to make it square. +2. Create the date object `d = new Date(year, month-1)`. That's the first day of `month` (taking into account that months in JavaScript start from `0`, not `1`). +3. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with ``. +4. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `` to the calendar. If that's a Sunday, then add a newline "</tr><tr>". +5. If the month has finished, but the table row is not yet full, add empty `` into it, to make it square. diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index b624da47c..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 @@ -540,7 +553,7 @@ So if we need to add a lot of text into HTML dynamically, and we're at page load - `"beforeend"` -- insert `html` into `elem`, at the end, - `"afterend"` -- insert `html` right after `elem`. - Also there are also similar methods, `elem.insertAdjacentText` and `elem.insertAdjacentElement`, that insert text strings and elements, but they are rarely used. + Also there are similar methods, `elem.insertAdjacentText` and `elem.insertAdjacentElement`, that insert text strings and elements, but they are rarely used. - To append HTML to the page before it has finished loading: - `document.write(html)` 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 34d441ae5..9154d43d6 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -249,7 +249,7 @@ For instance: ```smart header="Computed and resolved values" There are two concepts in [CSS](https://drafts.csswg.org/cssom/#resolved-values): -1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. It can look like `height:1em` or `font-size:125%`. +1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. It can look like `height:1em` or `font-size:125%`. 2. A *resolved* style value is the one finally applied to the element. Values like `1em` or `125%` are relative. The browser takes the computed value and makes all units fixed and absolute, for instance: `height:20px` or `font-size:16px`. For geometry properties resolved values may have a floating point, like `width:50.5px`. A long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient, and the standard changed. diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html index ca9c4d579..8f855ecfa 100755 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html @@ -9,7 +9,7 @@ background-color: #00FF00; position: relative; } - + #ball { position: absolute; } @@ -20,7 +20,7 @@
    - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md index c6fe6c3bb..afa1d8f50 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md @@ -24,17 +24,22 @@ ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px'; ``` -**Attention: the pitfall!** +Now the ball is finally centered. + +````warn header="Attention: the pitfall!" The code won't work reliably while `` has no width/height: ```html ``` +```` When the browser does not know the width/height of an image (from tag attributes or CSS), then it assumes them to equal `0` until the image finishes loading. -After the first load browser usually caches the image, and on next loads it will have the size immediately. But on the first load the value of `ball.offsetWidth` is `0`. That leads to wrong coordinates. +So the value of `ball.offsetWidth` will be `0` until the image loads. That leads to wrong coordinates in the code above. + +After the first load, the browser usually caches the image, and on reloads it will have the size immediately. But on the first load the value of `ball.offsetWidth` is `0`. We should fix that by adding `width/height` to ``: diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html index 9ebe6001e..9f21e5421 100755 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html @@ -26,7 +26,7 @@ ``` +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: @@ -201,7 +197,7 @@ Assign a handler to `elem.onclick`, not `elem.ONCLICK`, because DOM properties a The fundamental problem of the aforementioned ways to assign handlers -- 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,12 +207,12 @@ 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 special methods `addEventListener` and `removeEventListener`. They are free of such a problem. The syntax to add a handler: ```js -element.addEventListener(event, handler[, options]); +element.addEventListener(event, handler, [options]); ``` `event` @@ -229,19 +225,18 @@ 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`: ```js -element.removeEventListener(event, handler[, options]); +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: @@ -291,47 +286,33 @@ 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`. - -For instance, the event `transitionend` (CSS animation finished) is like that. - -Try the code below. In most browsers only the second handler works, not the first one. - -```html run - +There exist events that can't be assigned via a DOM-property. Only with `addEventListener`. - +For instance, the `DOMContentLoaded` event, that triggers when the document is loaded and DOM is built. - +```js +// this way it works +document.addEventListener("DOMContentLoaded", function() { + alert("DOM built"); +}); ``` +So `addEventListener` is more universal. Although, such events are an exception rather than the rule. ```` ## 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 @@ -354,11 +335,11 @@ Some properties of `event` object: : 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. +: 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 we come to different events in details. -````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 @@ -380,15 +361,17 @@ 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: @@ -462,7 +445,7 @@ HTML attributes are used sparingly, because JavaScript in the middle of an HTML DOM properties are ok to use, but we can't assign more than one handler of the particular event. In many cases that limitation is not pressing. -The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transtionend` and `DOMContentLoaded` (to be covered). Also `addEventListener` supports objects as event handlers. In that case the method `handleEvent` is called in case of the event. +The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transitionend` and `DOMContentLoaded` (to be covered). Also `addEventListener` supports objects as event handlers. In that case the method `handleEvent` is called in case of the event. No matter how you assign the handler -- it gets an event object as the first argument. That object contains the details about what's happened. 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 1ac989c79..5d854a59f 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -181,7 +181,7 @@ 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) +2. `P` (target phase, triggers two times, as we've set two listeners: capturing and bubbling) 3. `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). 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. @@ -204,9 +204,9 @@ elem.addEventListener("click", e => alert(2)); When an event happens -- the most nested element where it happens gets labeled as the "target element" (`event.target`). -- 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 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: @@ -220,6 +220,6 @@ The capturing phase is used very rarely, usually we handle events on bubbling. A 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/4-behavior-tooltip/task.md b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md index 73102a92b..3001b9915 100644 --- a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md +++ b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md @@ -22,8 +22,10 @@ In this task we assume that all elements with `data-tooltip` have only text insi Details: +- The distance between the element and the tooltip should be `5px`. +- The tooltip should be centered relative to the element, if possible. - The tooltip should not cross window edges. Normally it should be above the element, but if the element is at the page top and there's no space for the tooltip, then below it. -- The tooltip is given in the `data-tooltip` attribute. It can be arbitrary HTML. +- The tooltip content is given in the `data-tooltip` attribute. It can be arbitrary HTML. You'll need two events here: - `mouseover` triggers when a pointer comes over an element. diff --git a/2-ui/2-events/03-event-delegation/article.md b/2-ui/2-events/03-event-delegation/article.md index 3d8beda0f..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. @@ -121,7 +121,7 @@ The first idea may be to assign a separate handler to each button. But there's a The handler reads the attribute and executes the method. Take a look at the working example: -```html autorun height=60 run +```html autorun height=60 run untrusted

    ``` -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) @@ -125,7 +125,7 @@ If there are some actions upon leaving the parent element, e.g. an animation run To avoid it, we can check `relatedTarget` in the handler and, if the mouse is still inside the element, then ignore such event. -Alternatively we can use other events: `mouseenter` и `mouseleave`, that we'll be covering now, as they don't have such problems. +Alternatively we can use other events: `mouseenter` and `mouseleave`, that we'll be covering now, as they don't have such problems. ## Events mouseenter and mouseleave @@ -145,7 +145,7 @@ When the pointer leaves an element -- `mouseleave` triggers. ```online This example is similar to the one above, but now the top element has `mouseenter/mouseleave` instead of `mouseover/mouseout`. -As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignores +As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignored [codetabs height=340 src="mouseleave"] ``` 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/2-drag-heroes/solution.view/soccer.js b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js index 1c1443a7e..10ae2eeed 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js @@ -7,7 +7,7 @@ document.addEventListener('mousedown', function(event) { if (!dragElement) return; event.preventDefault(); - + dragElement.ondragstart = function() { return false; }; @@ -19,7 +19,7 @@ document.addEventListener('mousedown', function(event) { function onMouseUp(event) { finishDrag(); }; - + function onMouseMove(event) { moveAt(event.clientX, event.clientY); } @@ -31,9 +31,9 @@ document.addEventListener('mousedown', function(event) { if(isDragging) { return; } - + isDragging = true; - + document.addEventListener('mousemove', onMouseMove); element.addEventListener('mouseup', onMouseUp); @@ -50,10 +50,10 @@ document.addEventListener('mousedown', function(event) { if(!isDragging) { return; } - + isDragging = false; - dragElement.style.top = parseInt(dragElement.style.top) + pageYOffset + 'px'; + dragElement.style.top = parseInt(dragElement.style.top) + window.pageYOffset + 'px'; dragElement.style.position = 'absolute'; document.removeEventListener('mousemove', onMouseMove); @@ -113,4 +113,4 @@ document.addEventListener('mousedown', function(event) { dragElement.style.top = newY + 'px'; } -}); \ No newline at end of file +}); 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 2418ffd66..49ab88be2 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 @@ -1,12 +1,12 @@ # Drag'n'Drop with mouse events -Drag'n'Drop is a great interface solution. Taking something, dragging and dropping is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (drop into cart). +Drag'n'Drop is a great interface solution. Taking something and dragging and dropping it is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (dropping items into a cart). -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. +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. -They are interesting because they allow to solve simple tasks easily, and also allow to handle 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. Then JavaScript gains access to its contents. +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 limit dragging by a certain area. Also we can't make it "horizontal" or "vertical" only. There are other drag'n'drop tasks that can't be done using that API. Besides, mobile devices support for such events is almost non-existant. +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. So here we'll see how to implement Drag'n'Drop using mouse events. @@ -14,26 +14,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 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); // centers the ball at (pageX, pageY) coordinates function moveAt(pageX, pageY) { @@ -41,14 +38,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 +64,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: @@ -124,7 +124,7 @@ Let's update our algorithm: ```js // onmousemove - // у мяча ball стоит position:absoute + // ball has position:absolute ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` @@ -276,7 +276,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 +300,4 @@ 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. -There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to described above, so it should be easy to understand them now. Or roll our own, as you can see 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. 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..86e9b83fd 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/3-load-visible-img/source.view/index.html b/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html index 5c6027a6f..9953ace66 100644 --- a/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html +++ b/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html @@ -169,38 +169,8 @@

    Neptune

    * It's enough that the top or bottom edge of the element are visible */ function isVisible(elem) { - - let coords = elem.getBoundingClientRect(); - - let windowHeight = document.documentElement.clientHeight; - - // top elem edge is visible OR bottom elem edge is visible - let topVisible = coords.top > 0 && coords.top < windowHeight; - let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0; - - return topVisible || bottomVisible; - } - - /** - A variant of the test that considers the element visible if it's no more than - one page after/behind the current screen. - - function isVisible(elem) { - - let coords = elem.getBoundingClientRect(); - - let windowHeight = document.documentElement.clientHeight; - - let extendedTop = -windowHeight; - let extendedBottom = 2 * windowHeight; - - // top visible || bottom visible - let topVisible = coords.top > extendedTop && coords.top < extendedBottom; - let bottomVisible = coords.bottom < extendedBottom && coords.bottom > extendedTop; - - return topVisible || bottomVisible; + // todo: your code } - */ function showVisible() { for (let img of document.querySelectorAll('img')) { diff --git a/2-ui/3-event-details/8-onscroll/article.md b/2-ui/3-event-details/8-onscroll/article.md index 22ff8d859..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. @@ -10,7 +10,7 @@ Here's a small function to show the current scroll: ```js autorun window.addEventListener('scroll', function() { - document.getElementById('showScroll').innerHTML = pageYOffset + 'px'; + document.getElementById('showScroll').innerHTML = window.pageYOffset + 'px'; }); ``` @@ -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/1-add-select-option/task.md b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md index 9b218aa7f..a0e74da57 100644 --- a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md +++ b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md @@ -18,3 +18,5 @@ Use JavaScript to: 1. Show the value and the text of the selected option. 2. Add an option: ``. 3. Make it selected. + +Note, if you've done everything right, your alert should show `blues`. 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 d989a5fb8..689301e4d 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,14 +119,13 @@ 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. ```` ## Backreference: element.form -For any element, the form is available as `element.form`. So a form references all elements, and elements -reference the form. +For any element, the form is available as `element.form`. So a form references all elements, and elements reference the form. Here's the picture: @@ -178,18 +177,16 @@ It stores only the HTML that was initially on the page, not the current value. A ``: -1. Find the corresponding `
    `. So, popups is not something we use everyday. @@ -15,7 +15,7 @@ Also, popups are tricky on mobile devices, that don't show multiple windows simu Still, there are tasks where popups are still used, e.g. for OAuth authorization (login with Google/Facebook/...), because: -1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe. +1. A popup is a separate window which has its own independent JavaScript environment. So opening a popup from a third-party, non-trusted site is safe. 2. It's very easy to open a popup. 3. A popup can navigate (change URL) and send messages to the opener window. @@ -69,7 +69,7 @@ name : A name of the new window. Each window has a `window.name`, and here we can specify which window to use for the popup. If there's already a window with such name -- the given URL opens in it, otherwise a new window is opened. params -: The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: `width:200,height=100`. +: The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: `width=200,height=100`. Settings for `params`: @@ -89,7 +89,7 @@ There is also a number of less supported browser-specific features, which are us ## Example: a minimalistic window -Let's open a window with minimal set of features just to see which of them browser allows to disable: +Let's open a window with minimal set of features, just to see which of them browser allows to disable: ```js run let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no, @@ -154,7 +154,7 @@ Windows may freely access content of each other only if they come from the same Otherwise, e.g. if the main window is from `site.com`, and the popup from `gmail.com`, that's impossible for user safety reasons. For the details, see chapter . ``` -## Accessing window from popup +## Accessing window from popup A popup may access the "opener" window as well using `window.opener` reference. It is `null` for all windows except popups. @@ -192,7 +192,7 @@ newWindow.onload = function() { ``` -## Scrolling and resizing +## Moving and resizing There are methods to move/resize a window: @@ -237,21 +237,23 @@ There's also `window.onscroll` event. ## Focus/blur on a window -Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. Also there are `focus/blur` events that allow to focus a window and catch the moment when the visitor switches elsewhere. +Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. And there are also `focus/blur` events that allow to catch the moment when the visitor focuses on a window and switches elsewhere. -In the past evil pages abused those. For instance, look at this code: +Although, in practice they are severely limited, because in the past evil pages abused them. + +For instance, look at this code: ```js run window.onblur = () => window.focus(); ``` -When a user attempts to switch out of the window (`blur`), it brings it back to focus. The intention is to "lock" the user within the `window`. +When a user attempts to switch out of the window (`window.onblur`), it brings the window back into focus. The intention is to "lock" the user within the `window`. -So, there are limitations that forbid the code like that. There are many limitations to protect the user from ads and evils pages. They depend on the browser. +So browsers had to introduce many limitations to forbid the code like that and protect the user from ads and evils pages. They depend on the browser. -For instance, a mobile browser usually ignores that call completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window. +For instance, a mobile browser usually ignores `window.focus()` completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window. -Still, there are some things that can be done. +Still, there are some use cases when such calls do work and can be useful. For instance: @@ -268,7 +270,7 @@ If we're going to open a popup, a good practice is to inform the user about it. - Browsers block `open` calls from the code outside of user actions. Usually a notification appears, so that a user may allow them. - Browsers open a new tab by default, but if sizes are provided, then it'll be a popup window. - The popup may access the opener window using the `window.opener` property. -- The main window and the popup can freely read and modify each other if they havee the same origin. Otherwise, they can change location of each other and [exchange messages. +- The main window and the popup can freely read and modify each other if they have the same origin. Otherwise, they can change location of each other and [exchange messages](info:cross-window-communication). To close the popup: use `close()` call. Also the user may close them (just like any other windows). The `window.closed` is `true` after that. diff --git a/3-frames-and-windows/03-cross-window-communication/article.md b/3-frames-and-windows/03-cross-window-communication/article.md index 0a89521b3..0aa9abfe7 100644 --- a/3-frames-and-windows/03-cross-window-communication/article.md +++ b/3-frames-and-windows/03-cross-window-communication/article.md @@ -116,6 +116,13 @@ document.domain = 'site.com'; That's all. Now they can interact without limitations. Again, that's only possible for pages with the same second-level domain. +```warn header="Deprecated, but still working" +The `document.domain` property is in the process of being removed from the [specification](https://html.spec.whatwg.org/multipage/origin.html#relaxing-the-same-origin-restriction). The cross-window messaging (explained soon below) is the suggested replacement. + +That said, as of now all browsers support it. And the support will be kept for the future, not to break old code that relies on `document.domain`. +``` + + ## Iframe: wrong document pitfall When an iframe comes from the same origin, and we may access its `document`, there's a pitfall. It's not related to cross-origin things, but important to know. @@ -263,7 +270,7 @@ The window that wants to send a message calls [postMessage](mdn:api/Window.postM Arguments: `data` -: The data to send. Can be any object, the data is cloned using the "structured cloning algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser. +: The data to send. Can be any object, the data is cloned using the "structured serialization algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser. `targetOrigin` : Specifies the origin for the target window, so that only a window from the given origin will get the message. @@ -335,10 +342,6 @@ The full example: [codetabs src="postmessage" height=120] -```smart header="There's no delay" -There's totally no delay between `postMessage` and the `message` event. The event triggers synchronously, faster than `setTimeout(...,0)`. -``` - ## Summary To call methods and access the content of another window, we should first have a reference to it. diff --git a/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html b/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html index 478830610..46dd7b5cc 100644 --- a/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html +++ b/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html @@ -7,7 +7,7 @@ -
    The iframe below is has sandbox attribute.
    +
    The iframe below has the sandbox attribute.
    diff --git a/4-binary/01-arraybuffer-binary-arrays/article.md b/4-binary/01-arraybuffer-binary-arrays/article.md index b1d0bbb0d..95c18dda9 100644 --- a/4-binary/01-arraybuffer-binary-arrays/article.md +++ b/4-binary/01-arraybuffer-binary-arrays/article.md @@ -34,7 +34,7 @@ A view object does not store anything on it's own. It's the "eyeglasses" that gi For instance: -- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". +- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". - **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer". - **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer". - **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from 5.0x10-324 to 1.8x10308. @@ -73,8 +73,11 @@ for(let num of view) { The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities. -They are much more like regular arrays: have indexes and iterable. +Please note, there's no constructor called `TypedArray`, it's just a common "umbrella" term to represent one of views over `ArrayBuffer`: `Int8Array`, `Uint8Array` and so on, the full list will soon follow. +When you see something like `new TypedArray`, it means any of `new Int8Array`, `new Uint8Array`, etc. + +Typed arrays behave like regular arrays: have indexes and are iterable. A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types. @@ -123,9 +126,9 @@ new TypedArray(); We can create a `TypedArray` directly, without mentioning `ArrayBuffer`. But a view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these cases except the first one (when provided). -To access the `ArrayBuffer`, there are properties: -- `arr.buffer` -- references the `ArrayBuffer`. -- `arr.byteLength` -- the length of the `ArrayBuffer`. +To access the underlying `ArrayBuffer`, there are following properties in `TypedArray`: +- `buffer` -- references the `ArrayBuffer`. +- `byteLength` -- the length of the `ArrayBuffer`. So, we can always move from one view to another: ```js @@ -206,7 +209,7 @@ These methods allow us to copy typed arrays, mix them, create new arrays from ex ## DataView -[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format. +[DataView](mdn:/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format. - For typed arrays, the constructor dictates what the format is. The whole array is supposed to be uniform. The i-th number is `arr[i]`. - With `DataView` we access the data with methods like `.getUint8(i)` or `.getUint16(i)`. We choose the format at method call time instead of the construction time. @@ -232,7 +235,7 @@ let dataView = new DataView(buffer); // get 8-bit number at offset 0 alert( dataView.getUint8(0) ); // 255 -// now get 16-bit number at offset 0, it consists of 2 bytes, together iterpreted as 65535 +// now get 16-bit number at offset 0, it consists of 2 bytes, together interpreted as 65535 alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int) // get 32-bit number at offset 0 @@ -241,7 +244,7 @@ alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int) dataView.setUint32(0, 0); // set 4-byte number to zero, thus setting all bytes to 0 ``` -`DataView` is great when we store mixed-format data in the same buffer. E.g we store a sequence of pairs (16-bit integer, 32-bit float). Then `DataView` allows to access them easily. +`DataView` is great when we store mixed-format data in the same buffer. For example, when we store a sequence of pairs (16-bit integer, 32-bit float), `DataView` allows to access them easily. ## Summary @@ -256,7 +259,7 @@ To do almost any operation on `ArrayBuffer`, we need a view. - `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits. - Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`. -In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed. +In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common denominator". We can access it as `.buffer` and make another view if needed. There are also two additional terms, that are used in descriptions of methods that operate on binary data: - `ArrayBufferView` is an umbrella term for all these kinds of views. diff --git a/4-binary/02-text-decoder/article.md b/4-binary/02-text-decoder/article.md index d9f5e8fa5..a0c80145c 100644 --- a/4-binary/02-text-decoder/article.md +++ b/4-binary/02-text-decoder/article.md @@ -2,7 +2,7 @@ What if the binary data is actually a string? For instance, we received a file with textual data. -The build-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows to read the value into an actual JavaScript string, given the buffer and the encoding. +The built-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows one to read the value into an actual JavaScript string, given the buffer and the encoding. We first need to create it: ```js @@ -12,7 +12,7 @@ let decoder = new TextDecoder([label], [options]); - **`label`** -- the encoding, `utf-8` by default, but `big5`, `windows-1251` and many other are also supported. - **`options`** -- optional object: - **`fatal`** -- boolean, if `true` then throw an exception for invalid (non-decodable) characters, otherwise (default) replace them with character `\uFFFD`. - - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order unicode mark), rarely needed. + - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order Unicode mark), rarely needed. ...And then decode: @@ -58,7 +58,7 @@ alert( new TextDecoder().decode(binaryString) ); // Hello The syntax is: -```js run +```js let encoder = new TextEncoder(); ``` diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index 062e1834b..e3bd32b38 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -55,7 +55,7 @@ This behavior is similar to JavaScript strings: we can't change a character in a ## Blob as URL -A Blob can be easily used as an URL for `
    `, `` or other tags, to show its contents. +A Blob can be easily used as a URL for ``, `` or other tags, to show its contents. Thanks to `type`, we can also download/upload `Blob` objects, and the `type` naturally becomes `Content-Type` in network requests. @@ -74,7 +74,7 @@ link.href = URL.createObjectURL(blob); We can also create a link dynamically in JavaScript and simulate a click by `link.click()`, then download starts automatically. -Here's the similar code that causes user to download the dynamicallly created `Blob`, without any HTML: +Here's the similar code that causes user to download the dynamically created `Blob`, without any HTML: ```js run let link = document.createElement('a'); @@ -97,9 +97,9 @@ That's what the value of `link.href` looks like: blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273 ``` -The browser for each URL generated by `URL.createObjectURL` stores an the URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. +For each URL generated by `URL.createObjectURL` the browser stores a URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. -A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects an url. +A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects a URL. There's a side-effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it. @@ -119,7 +119,7 @@ An alternative to `URL.createObjectURL` is to convert a `Blob` into a base64-enc That encoding represents binary data as a string of ultra-safe "readable" characters with ASCII-codes from 0 to 64. And what's more important -- we can use this encoding in "data-urls". -A [data url](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls. +A [data url](mdn:/http/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls. For instance, here's a smiley: @@ -151,7 +151,7 @@ reader.onload = function() { }; ``` -Both ways of making an URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. +Both ways of making a URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. ```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url" + We need to revoke them if care about memory. @@ -166,8 +166,8 @@ We can create a `Blob` of an image, an image part, or even make a page screensho Image operations are done via `` element: -1. Draw an image (or its part) on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage). -2. Call canvas method [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done. +1. Draw an image (or its part) on canvas using [canvas.drawImage](mdn:/api/CanvasRenderingContext2D/drawImage). +2. Call canvas method [.toBlob(callback, format, quality)](mdn:/api/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done. In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob: @@ -186,7 +186,7 @@ let context = canvas.getContext('2d'); context.drawImage(img, 0, 0); // we can context.rotate(), and do many other things on canvas -// toBlob is async opereation, callback is called when done +// toBlob is async operation, callback is called when done canvas.toBlob(function(blob) { // blob ready, download it let link = document.createElement('a'); @@ -211,21 +211,44 @@ For screenshotting a page, we can use a library such as /* process the ArrayBuffer */); ``` +## From Blob to stream + +When we read and write to a blob of more than `2 GB`, the use of `arrayBuffer` becomes more memory intensive for us. At this point, we can directly convert the blob to a stream. + +A stream is a special object that allows to read from it (or write into it) portion by portion. It's outside of our scope here, but here's an example, and you can read more at . Streams are convenient for data that is suitable for processing piece-by-piece. + +The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the `Blob`. + +Then we can read from it, like this: + +```js +// get readableStream from blob +const readableStream = blob.stream(); +const stream = readableStream.getReader(); + +while (true) { + // for each iteration: data is the next blob fragment + let { done, data } = await stream.read(); + if (done) { + // no more data in the stream + console.log('all blob processed.'); + break; + } + + // do something with the data portion we've just read from the blob + console.log(data); +} +``` ## Summary @@ -235,7 +258,9 @@ That makes Blobs convenient for upload/download operations, that are so common i Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types. -We can easily convert betweeen `Blob` and low-level binary data types: +We can easily convert between `Blob` and low-level binary data types: + +- We can make a `Blob` from a typed array using `new Blob(...)` constructor. +- We can get back `ArrayBuffer` from a Blob using `blob.arrayBuffer()`, and then create a view over it for low-level binary processing. -- We can make a Blob from a typed array using `new Blob(...)` constructor. -- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing. +Conversion streams are very useful when we need to handle large blob. You can easily create a `ReadableStream` from a blob. The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the blob. diff --git a/4-binary/04-file/article.md b/4-binary/04-file/article.md index 43c0ca583..20878b650 100644 --- a/4-binary/04-file/article.md +++ b/4-binary/04-file/article.md @@ -61,8 +61,8 @@ The main methods: The choice of `read*` method depends on which format we prefer, how we're going to use the data. -- `readAsArrayBuffer` - for binary files, to do low-level binary operations. For high-level operations, like slicing, `File` inherits from `Blob`, so we can call them directly, without reading. -- `readAsText` - for text files, when we'd like to get a string. +- `readAsArrayBuffer` -- for binary files, to do low-level binary operations. For high-level operations, like slicing, `File` inherits from `Blob`, so we can call them directly, without reading. +- `readAsText` -- for text files, when we'd like to get a string. - `readAsDataURL` -- when we'd like to use this data in `src` for `img` or another tag. There's an alternative to reading a file for that, as discussed in chapter : `URL.createObjectURL(file)`. As the reading proceeds, there are events: diff --git a/5-network/01-fetch/01-fetch-users/solution.md b/5-network/01-fetch/01-fetch-users/solution.md index b8dfb62a2..3cb88e4ea 100644 --- a/5-network/01-fetch/01-fetch-users/solution.md +++ b/5-network/01-fetch/01-fetch-users/solution.md @@ -3,7 +3,7 @@ To fetch a user we need: `fetch('https://api.github.com/users/USERNAME')`. If the response has status `200`, call `.json()` to read the JS object. -Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting arrray. +Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting array. So here's the code: diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index 7d10954f7..688db2ba5 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -1,7 +1,7 @@ # Fetch -JavaScript can send network requests to the server and load new information whenever is needed. +JavaScript can send network requests to the server and load new information whenever it's needed. For example, we can use a network request to: @@ -27,7 +27,7 @@ let promise = fetch(url, [options]) - **`url`** -- the URL to access. - **`options`** -- optional parameters: method, headers etc. -Without `options`, that is a simple GET request, downloading the contents of the `url`. +Without `options`, this is a simple GET request, downloading the contents of the `url`. The browser starts the request right away and returns a promise that the calling code should use to get the result. @@ -65,8 +65,8 @@ if (response.ok) { // if HTTP-status is 200-299 - **`response.json()`** -- parse the response as JSON, - **`response.formData()`** -- return the response as `FormData` object (explained in the [next chapter](info:formdata)), - **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), -- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representaion of binary data), -- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk, we'll see an example later. +- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representation of binary data), +- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows you to read the body chunk-by-chunk, we'll see an example later. For instance, let's get a JSON-object with latest commits from GitHub: @@ -130,6 +130,7 @@ If we've already got the response with `response.text()`, then `response.json()` ```js let text = await response.text(); // response body consumed let parsed = await response.json(); // fails (already consumed) +``` ```` ## Response headers @@ -230,7 +231,7 @@ But, as we're going to send JSON, we use `headers` option to send `application/j We can also submit binary data with `fetch` using `Blob` or `BufferSource` objects. -In this example, there's a `` where we can draw by moving a mouse over it. A click on the "submit" button sends the image to server: +In this example, there's a `` where we can draw by moving a mouse over it. A click on the "submit" button sends the image to the server: ```html run autorun height="90" diff --git a/5-network/02-formdata/article.md b/5-network/02-formdata/article.md index fb028f5c8..a73d554b1 100644 --- a/5-network/02-formdata/article.md +++ b/5-network/02-formdata/article.md @@ -12,7 +12,7 @@ let formData = new FormData([form]); If HTML `form` element is provided, it automatically captures its fields. -The special thing about `FormData` is that network methods, such as `fetch`, can accept a `FormData` object as a body. It's encoded and sent out with `Content-Type: form/multipart`. +The special thing about `FormData` is that network methods, such as `fetch`, can accept a `FormData` object as a body. It's encoded and sent out with `Content-Type: multipart/form-data`. From the server point of view, that looks like a usual form submission. @@ -47,21 +47,21 @@ As you can see, that's almost one-liner: ``` -In this example, the server code is not presented, as it's beyound our scope. The server accepts the POST request and replies "User saved". +In this example, the server code is not presented, as it's beyond our scope. The server accepts the POST request and replies "User saved". ## FormData Methods We can modify fields in `FormData` with methods: - `formData.append(name, value)` - add a form field with the given `name` and `value`, -- `formData.append(name, blob, fileName)` - add a field as if it were ``, the third argument `fileName` sets file name (not form field name), as it it were a name of the file in user's filesystem, +- `formData.append(name, blob, fileName)` - add a field as if it were ``, the third argument `fileName` sets file name (not form field name), as it were a name of the file in user's filesystem, - `formData.delete(name)` - remove the field with the given `name`, - `formData.get(name)` - get the value of the field with the given `name`, - `formData.has(name)` - if there exists a field with the given `name`, returns `true`, otherwise `false` A form is technically allowed to have many fields with the same `name`, so multiple calls to `append` add more same-named fields. -There's also method `set`, with the same syntax as `append`. The difference is that `.set` removes all fields with the given `name`, and then appends a new field. So it makes sure there's only field with such `name`, the rest is just like `append`: +There's also method `set`, with the same syntax as `append`. The difference is that `.set` removes all fields with the given `name`, and then appends a new field. So it makes sure there's only one field with such `name`, the rest is just like `append`: - `formData.set(name, value)`, - `formData.set(name, blob, fileName)`. @@ -75,13 +75,13 @@ formData.append('key2', 'value2'); // List key/value pairs for(let [name, value] of formData) { - alert(`${name} = ${value}`); // key1=value1, then key2=value2 + alert(`${name} = ${value}`); // key1 = value1, then key2 = value2 } ``` ## Sending a form with a file -The form is always sent as `Content-Type: form/multipart`, this encoding allows to send files. So, `` fields are sent also, similar to a usual form submission. +The form is always sent as `Content-Type: multipart/form-data`, this encoding allows to send files. So, `` fields are sent also, similar to a usual form submission. Here's an example with such form: @@ -168,7 +168,7 @@ The server reads form data and the file, as if it were a regular form submission [FormData](https://xhr.spec.whatwg.org/#interface-formdata) objects are used to capture HTML form and submit it using `fetch` or another network method. -We can either create `new FormData(form)` from an HTML form, or create a object without a form at all, and then append fields with methods: +We can either create `new FormData(form)` from an HTML form, or create an object without a form at all, and then append fields with methods: - `formData.append(name, value)` - `formData.append(name, blob, fileName)` diff --git a/5-network/03-fetch-progress/article.md b/5-network/03-fetch-progress/article.md index b26a8a0ce..76b05d514 100644 --- a/5-network/03-fetch-progress/article.md +++ b/5-network/03-fetch-progress/article.md @@ -5,11 +5,11 @@ The `fetch` method allows to track *download* progress. Please note: there's currently no way for `fetch` to track *upload* progress. For that purpose, please use [XMLHttpRequest](info:xmlhttprequest), we'll cover it later. -To track download progress, we can use `response.body` property. It's `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. +To track download progress, we can use `response.body` property. It's a `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can count how much is consumed at any moment. -Here's the sketch of code that reads the reponse from `response.body`: +Here's the sketch of code that reads the response from `response.body`: ```js // instead of response.json() and other methods @@ -107,6 +107,8 @@ Let's explain that step-by-step: let blob = new Blob(chunks); ``` -At we end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process. +At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process. Once again, please note, that's not for *upload* progress (no way now with `fetch`), only for *download* progress. + +Also, if the size is unknown, we should check `receivedLength` in the loop and break it once it reaches a certain limit. So that the `chunks` won't overflow the memory. diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md index 757846287..af66c37cc 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -1,61 +1,81 @@ # Fetch: Abort -As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we abort a `fetch`? +As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we cancel an ongoing `fetch`? E.g. if the user actions on our site indicate that the `fetch` isn't needed any more. -There's a special built-in object for such purposes: `AbortController`, that can be used to abort not only `fetch`, but other asynchronous tasks as well. +There's a special built-in object for such purposes: `AbortController`. It can be used to abort not only `fetch`, but other asynchronous tasks as well. -The usage is pretty simple: +The usage is very straightforward: -- Step 1: create a controller: +## The AbortController object - ```js - let controller = new AbortController(); - ``` +Create a controller: - A controller is an extremely simple object. +```js +let controller = new AbortController(); +``` - - It has a single method `abort()`, and a single property `signal`. - - When `abort()` is called: - - `abort` event triggers on `controller.signal` - - `controller.signal.aborted` property becomes `true`. +A controller is an extremely simple object. - All parties interested to learn about `abort()` call set listeners on `controller.signal` to track it. +- It has a single method `abort()`, +- And a single property `signal` that allows to set event listeners on it. - Like this (without `fetch` yet): +When `abort()` is called: +- `controller.signal` emits the `"abort"` event. +- `controller.signal.aborted` property becomes `true`. - ```js run - let controller = new AbortController(); - let signal = controller.signal; +Generally, we have two parties in the process: +1. The one that performs a cancelable operation, it sets a listener on `controller.signal`. +2. The one that cancels: it calls `controller.abort()` when needed. - // triggers when controller.abort() is called - signal.addEventListener('abort', () => alert("abort!")); +Here's the full example (without `fetch` yet): - controller.abort(); // abort! +```js run +let controller = new AbortController(); +let signal = controller.signal; + +// The party that performs a cancelable operation +// gets the "signal" object +// and sets the listener to trigger when controller.abort() is called +signal.addEventListener('abort', () => alert("abort!")); - alert(signal.aborted); // true - ``` +// The other party, that cancels (at any point later): +controller.abort(); // abort! -- Step 2: pass the `signal` property to `fetch` option: +// The event triggers and signal.aborted becomes true +alert(signal.aborted); // true +``` - ```js - let controller = new AbortController(); - fetch(url, { - signal: controller.signal - }); - ``` +As we can see, `AbortController` is just a mean to pass `abort` events when `abort()` is called on it. - The `fetch` method knows how to work with `AbortController`, it listens to `abort` on `signal`. +We could implement the same kind of event listening in our code on our own, without the `AbortController` object. -- Step 3: to abort, call `controller.abort()`: +But what's valuable is that `fetch` knows how to work with the `AbortController` object. It's integrated in it. - ```js - controller.abort(); - ``` +## Using with fetch - We're done: `fetch` gets the event from `signal` and aborts the request. +To be able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: -When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`: +```js +let controller = new AbortController(); +fetch(url, { + signal: controller.signal +}); +``` + +The `fetch` method knows how to work with `AbortController`. It will listen to `abort` events on `signal`. + +Now, to abort, call `controller.abort()`: + +```js +controller.abort(); +``` + +We're done: `fetch` gets the event from `signal` and aborts the request. + +When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`. + +Here's the full example with `fetch` aborted after 1 second: ```js run async // abort in 1 second @@ -75,28 +95,31 @@ try { } ``` -**`AbortController` is scalable, it allows to cancel multiple fetches at once.** +## AbortController is scalable -For instance, here we fetch many `urls` in parallel, and the controller aborts them all: +`AbortController` is scalable. It allows to cancel multiple fetches at once. + +Here's a sketch of code that fetches many `urls` in parallel, and uses a single controller to abort them all: ```js let urls = [...]; // a list of urls to fetch in parallel let controller = new AbortController(); +// an array of fetch promises let fetchJobs = urls.map(url => fetch(url, { signal: controller.signal })); let results = await Promise.all(fetchJobs); -// if controller.abort() is called from elsewhere, +// if controller.abort() is called from anywhere, // it aborts all fetches ``` -If we have our own asynchronous jobs, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches. +If we have our own asynchronous tasks, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches. -We just need to listen to its `abort` event: +We just need to listen to its `abort` event in our tasks: ```js let urls = [...]; @@ -114,8 +137,12 @@ let fetchJobs = urls.map(url => fetch(url, { // fetches // Wait for fetches and our task in parallel let results = await Promise.all([...fetchJobs, ourJob]); -// if controller.abort() is called from elsewhere, +// if controller.abort() is called from anywhere, // it aborts all fetches and ourJob ``` -So `AbortController` is not only for `fetch`, it's a universal object to abort asynchronous tasks, and `fetch` has built-in integration with it. +## Summary + +- `AbortController` is a simple object that generates an `abort` event on it's `signal` property when the `abort()` method is called (and also sets `signal.aborted` to `true`). +- `fetch` integrates with it: we pass the `signal` property as the option, and then `fetch` listens to it, so it's possible to abort the `fetch`. +- We can use `AbortController` in our code. The "call `abort()`" -> "listen to `abort` event" interaction is simple and universal. We can use it even without `fetch`. diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index 1394433d9..d45ee391d 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -28,7 +28,7 @@ Seriously. Let's make a very brief historical digression. **For many years a script from one site could not access the content of another site.** -That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access user's mailbox at website `gmail.com`. People felt safe. +That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access the user's mailbox at website `gmail.com`. People felt safe. JavaScript also did not have any special methods to perform network requests at that time. It was a toy language to decorate a web page. @@ -97,43 +97,43 @@ After a while, networking methods appeared in browser JavaScript. At first, cross-origin requests were forbidden. But as a result of long discussions, cross-origin requests were allowed, but with any new capabilities requiring an explicit allowance by the server, expressed in special headers. -## Simple requests +## Safe requests There are two types of cross-origin requests: -1. Simple requests. +1. Safe requests. 2. All the others. -Simple Requests are, well, simpler to make, so let's start with them. +Safe Requests are simpler to make, so let's start with them. -A [simple request](http://www.w3.org/TR/cors/#terminology) is a request that satisfies two conditions: +A request is safe if it satisfies two conditions: -1. [Simple method](http://www.w3.org/TR/cors/#simple-method): GET, POST or HEAD -2. [Simple headers](http://www.w3.org/TR/cors/#simple-header) -- the only allowed custom headers are: +1. [Safe method](https://fetch.spec.whatwg.org/#cors-safelisted-method): GET, POST or HEAD +2. [Safe headers](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) -- the only allowed custom headers are: - `Accept`, - `Accept-Language`, - `Content-Language`, - `Content-Type` with the value `application/x-www-form-urlencoded`, `multipart/form-data` or `text/plain`. -Any other request is considered "non-simple". For instance, a request with `PUT` method or with an `API-Key` HTTP-header does not fit the limitations. +Any other request is considered "unsafe". For instance, a request with `PUT` method or with an `API-Key` HTTP-header does not fit the limitations. -**The essential difference is that a "simple request" can be made with a `` or a ` ``` -Now let's cover animation properties one by one. +Now, let's cover animation properties one by one. ## transition-property -In `transition-property` we write a list of property to animate, for instance: `left`, `margin-left`, `height`, `color`. +In `transition-property`, we write a list of properties to animate, for instance: `left`, `margin-left`, `height`, `color`. Or we could write `all`, which means "animate all properties". -Not all properties can be animated, but [many of them](http://www.w3.org/TR/css3-transitions/#animatable-properties-). The value `all` means "animate all properties". +Do note that, there are properties which can not be animated. However, [most of the generally used properties are animatable](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties). ## transition-duration @@ -84,11 +84,11 @@ In `transition-duration` we can specify how long the animation should take. The ## transition-delay -In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay: 1s`, then animation starts after 1 second after the change. +In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay` is `1s` and `transition-duration` is `2s`, then the animation starts 1 second after the property change and the total duration will be 2 seconds. -Negative values are also possible. Then the animation starts from the middle. For instance, if `transition-duration` is `2s`, and the delay is `-1s`, then the animation takes 1 second and starts from the half. +Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if `transition-delay` is `-1s` and `transition-duration` is `2s`, then animation starts from the halfway point and total duration will be 1 second. -Here's the animation shifts numbers from `0` to `9` using CSS `translate` property: +Here the animation shifts numbers from `0` to `9` using CSS `translate` property: [codetabs src="digits"] @@ -108,13 +108,13 @@ In the example above JavaScript adds the class `.animate` to the element -- and stripe.classList.add('animate'); ``` -We can also start it "from the middle", from the exact number, e.g. corresponding to the current second, using the negative `transition-delay`. +We could also start it from somewhere in the middle of the transition, from an exact number, e.g. corresponding to the current second, using a negative `transition-delay`. Here if you click the digit -- it starts the animation from the current second: [codetabs src="digits-negative-delay"] -JavaScript does it by an extra line: +JavaScript does it with an extra line: ```js stripe.onclick = function() { @@ -129,25 +129,25 @@ stripe.onclick = function() { ## transition-timing-function -Timing function describes how the animation process is distributed along the time. Will it start slowly and then go fast or vise versa. +The timing function describes how the animation process is distributed along its timeline. Will it start slowly and then go fast, or vice versa. -That's the most complicated property from the first sight. But it becomes very simple if we devote a bit time to it. +It appears to be the most complicated property at first. But it becomes very simple if we devote a bit time to it. -That property accepts two kinds of values: a Bezier curve or steps. Let's start from the curve, as it's used more often. +That property accepts two kinds of values: a Bezier curve or steps. Let's start with the curve, as it's used more often. ### Bezier curve -The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfies the conditions: +The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfy the conditions: 1. First control point: `(0,0)`. 2. Last control point: `(1,1)`. -3. For intermediate points values of `x` must be in the interval `0..1`, `y` can be anything. +3. For intermediate points, the values of `x` must be in the interval `0..1`, `y` can be anything. The syntax for a Bezier curve in CSS: `cubic-bezier(x2, y2, x3, y3)`. Here we need to specify only 2nd and 3rd control points, because the 1st one is fixed to `(0,0)` and the 4th one is `(1,1)`. -The timing function describes how fast the animation process goes in time. +The timing function describes how fast the animation process goes. -- The `x` axis is the time: `0` -- the starting moment, `1` -- the last moment of `transition-duration`. +- The `x` axis is the time: `0` -- the start, `1` -- the end of `transition-duration`. - The `y` axis specifies the completion of the process: `0` -- the starting value of the property, `1` -- the final value. The simplest variant is when the animation goes uniformly, with the same linear speed. That can be specified by the curve `cubic-bezier(0, 0, 1, 1)`. @@ -168,7 +168,7 @@ The CSS `transition` is based on that curve: .train { left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); - /* JavaScript sets left to 450px */ + /* click on a train sets left to 450px, thus triggering the animation */ } ``` @@ -191,13 +191,13 @@ CSS: .train { left: 0; transition: left 5s cubic-bezier(0, .5, .5, 1); - /* JavaScript sets left to 450px */ + /* click on a train sets left to 450px, thus triggering the animation */ } ``` There are several built-in curves: `linear`, `ease`, `ease-in`, `ease-out` and `ease-in-out`. -The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, we saw it already. +The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, which we described above. Other names are shorthands for the following `cubic-bezier`: @@ -215,22 +215,22 @@ So we could use `ease-out` for our slowing down train: .train { left: 0; transition: left 5s ease-out; - /* transition: left 5s cubic-bezier(0, .5, .5, 1); */ + /* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */ } ``` But it looks a bit differently. -**A Bezier curve can make the animation "jump out" of its range.** +**A Bezier curve can make the animation exceed its range.** -The control points on the curve can have any `y` coordinates: even negative or huge. Then the Bezier curve would also jump very low or high, making the animation go beyond its normal range. +The control points on the curve can have any `y` coordinates: even negative or huge ones. Then the Bezier curve would also extend very low or high, making the animation go beyond its normal range. In the example below the animation code is: ```css .train { left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); - /* JavaScript sets left to 400px */ + /* click on a train sets left to 450px */ } ``` @@ -244,21 +244,21 @@ But if you click the train, you'll see that: [codetabs src="train-over"] -Why it happens -- pretty obvious if we look at the graph of the given Bezier curve: +Why it happens is pretty obvious if we look at the graph of the given Bezier curve: ![](bezier-train-over.svg) -We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made put it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`. +We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`. -As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property lower than the starting `left` and `y>1` -- over the final `left`. +As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property beyond the starting `left` and `y>1` -- past the final `left`. That's a "soft" variant for sure. If we put `y` values like `-99` and `99` then the train would jump out of the range much more. -But how to make the Bezier curve for a specific task? There are many tools. For instance, we can do it on the site . +But how do we make a Bezier curve for a specific task? There are many tools. For instance, we can do it on the site . ### Steps -Timing function `steps(number of steps[, start/end])` allows to split animation into steps. +The timing function `steps(number of steps[, start/end])` allows splitting an transition into multiple steps. Let's see that in an example with digits. @@ -285,7 +285,7 @@ The first argument of `steps(9, start)` is the number of steps. The transform wi The second argument is one of two words: `start` or `end`. -The `start` means that in the beginning of animation we need to do make the first step immediately. +The `start` means that in the beginning of animation we need to make the first step immediately. We can observe that during the animation: when we click on the digit it changes to `1` (the first step) immediately, and then changes in the beginning of the next second. @@ -299,15 +299,15 @@ The process is progressing like this: The alternative value `end` would mean that the change should be applied not in the beginning, but at the end of each second. -So the process would go like this: +So the process for `steps(9, end)` would go like this: -- `0s` -- `0` +- `0s` -- `0` (during the first second nothing changes) - `1s` -- `-10%` (first change at the end of the 1st second) - `2s` -- `-20%` - ... - `9s` -- `-90%` -Here's `step(9, end)` in action (note the pause between the first digit change): +Here's `steps(9, end)` in action (note the pause between the first digit change): [codetabs src="step-end"] @@ -324,11 +324,11 @@ When the CSS animation finishes the `transitionend` event triggers. It is widely used to do an action after the animation is done. Also we can join animations. -For instance, the ship in the example below starts to swim there and back on click, each time farther and farther to the right: +For instance, the ship in the example below starts to sail there and back when clicked, each time farther and farther to the right: [iframe src="boat" height=300 edit link] -The animation is initiated by the function `go` that re-runs each time when the transition finishes and flips the direction: +The animation is initiated by the function `go` that re-runs each time the transition finishes, and flips the direction: ```js boat.onclick = function() { @@ -337,11 +337,11 @@ boat.onclick = function() { function go() { if (times % 2) { - // swim to the right + // sail to the right boat.classList.remove('back'); boat.style.marginLeft = 100 * times + 200 + 'px'; } else { - // swim to the left + // sail to the left boat.classList.add('back'); boat.style.marginLeft = 100 * times - 200 + 'px'; } @@ -357,7 +357,7 @@ boat.onclick = function() { }; ``` -The event object for `transitionend` has few specific properties: +The event object for `transitionend` has a few specific properties: `event.propertyName` : The property that has finished animating. Can be good if we animate multiple properties simultaneously. @@ -369,7 +369,7 @@ The event object for `transitionend` has few specific properties: We can join multiple simple animations together using the `@keyframes` CSS rule. -It specifies the "name" of the animation and rules: what, when and where to animate. Then using the `animation` property we attach the animation to the element and specify additional parameters for it. +It specifies the "name" of the animation and rules - what, when and where to animate. Then using the `animation` property, we can attach the animation to the element and specify additional parameters for it. Here's an example with explanations: @@ -405,11 +405,92 @@ Here's an example with explanations: There are many articles about `@keyframes` and a [detailed specification](https://drafts.csswg.org/css-animations/). -Probably you won't need `@keyframes` often, unless everything is in the constant move on your sites. +You probably won't need `@keyframes` often, unless everything is in constant motion on your sites. + +## Performance + +Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect. + +However, not all animations will look as smooth as you'd like, because different CSS properties cost differently to change. + +In more technical details, when there's a style change, the browser goes through 3 steps to render the new look: + +1. **Layout**: re-compute the geometry and position of each element, then +2. **Paint**: re-compute how everything should look like at their places, including background, colors, +3. **Composite**: render the final results into pixels on screen, apply CSS transforms if they exist. + +During a CSS animation, this process repeats every frame. However, CSS properties that never affect geometry or position, such as `color`, may skip the Layout step. If a `color` changes, the browser doesn't calculate any new geometry, it goes to Paint -> Composite. And there are few properties that directly go to Composite. You can find a longer list of CSS properties and which stages they trigger at . + +The calculations may take time, especially on pages with many elements and a complex layout. And the delays are actually visible on most devices, leading to "jittery", less fluid animations. + +Animations of properties that skip the Layout step are faster. It's even better if Paint is skipped too. + +The `transform` property is a great choice, because: +- CSS transforms affect the target element box as a whole (rotate, flip, stretch, shift it). +- CSS transforms never affect neighbour elements. + +...So browsers apply `transform` "on top" of existing Layout and Paint calculations, in the Composite stage. + +In other words, the browser calculates the Layout (sizes, positions), paints it with colors, backgrounds, etc at the Paint stage, and then applies `transform` to element boxes that need it. + +Changes (animations) of the `transform` property never trigger Layout and Paint steps. More than that, the browser leverages the graphics accelerator (a special chip on the CPU or graphics card) for CSS transforms, thus making them very efficient. + +Luckily, the `transform` property is very powerful. By using `transform` on an element, you could rotate and flip it, stretch and shrink it, move it around, and [much more](https://developer.mozilla.org/docs/Web/CSS/transform#syntax). So instead of `left/margin-left` properties we can use `transform: translateX(…)`, use `transform: scale` for increasing element size, etc. + +The `opacity` property also never triggers Layout (also skips Paint in Mozilla Gecko). We can use it for show/hide or fade-in/fade-out effects. + +Paring `transform` with `opacity` can usually solve most of our needs, providing fluid, good-looking animations. + +For example, here clicking on the `#boat` element adds the class with `transform: translateX(300)` and `opacity: 0`, thus making it move `300px` to the right and disappear: + +```html run height=260 autorun no-beautify + + + + +``` + +Here's a more complex example, with `@keyframes`: + +```html run height=80 autorun no-beautify +

    click me to start / stop

    + +``` ## Summary -CSS animations allow to smoothly (or not) animate changes of one or multiple CSS properties. +CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties. They are good for most animation tasks. We're also able to use JavaScript for animations, the next chapter is devoted to that. @@ -419,9 +500,11 @@ Limitations of CSS animations compared to JavaScript animations: + Simple things done simply. + Fast and lightweight for CPU. - JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element. -- Not just property changes. We can create new elements in JavaScript for purposes of animation. +- Not just property changes. We can create new elements in JavaScript as part of the animation. ``` -The majority of animations can be implemented using CSS as described in this chapter. And `transitionend` event allows to run JavaScript after the animation, so it integrates fine with the code. +In early examples in this chapter, we animate `font-size`, `left`, `width`, `height`, etc. In real life projects, we should use `transform: scale()` and `transform: translate()` for better performance. + +The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code. But in the next chapter we'll do some JavaScript animations to cover more complex cases. diff --git a/7-animation/3-js-animation/1-animate-ball/solution.md b/7-animation/3-js-animation/1-animate-ball/solution.md index 5d3f08eef..0dc67b8bd 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.md +++ b/7-animation/3-js-animation/1-animate-ball/solution.md @@ -2,7 +2,7 @@ To bounce we can use CSS property `top` and `position:absolute` for the ball ins The bottom coordinate of the field is `field.clientHeight`. The CSS `top` property refers to the upper edge of the ball. So it should go from `0` till `field.clientHeight - ball.clientHeight`, that's the final lowest position of the upper edge of the ball. -To to get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. +To get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. Here's the final code for the animation: diff --git a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html index 7e031e8d1..146033cf7 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html +++ b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html @@ -21,7 +21,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html index b246f422f..f587ff607 100644 --- a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html +++ b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html @@ -21,7 +21,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/article.md b/7-animation/3-js-animation/article.md index 004954340..5450d9045 100644 --- a/7-animation/3-js-animation/article.md +++ b/7-animation/3-js-animation/article.md @@ -77,7 +77,7 @@ setInterval(animate3, 20); These several independent redraws should be grouped together, to make the redraw easier for the browser and hence load less CPU load and look smoother. -There's one more thing to keep in mind. Sometimes when CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. +There's one more thing to keep in mind. Sometimes CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. But how do we know about that in JavaScript? There's a specification [Animation timing](http://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more. @@ -227,7 +227,7 @@ See in action (click to activate): [iframe height=40 src="quad" link] -...Or the cubic curve or event greater `n`. Increasing the power makes it speed up faster. +...Or the cubic curve or even greater `n`. Increasing the power makes it speed up faster. Here's the graph for `progress` in the power `5`: @@ -283,7 +283,7 @@ The `bounce` function does the same, but in the reverse order: "bouncing" starts ```js function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } @@ -397,7 +397,7 @@ The effect is clearly seen if we compare the graphs of `easeIn`, `easeOut` and ` ![](circ-ease.svg) -- Red is the regular variantof `circ` (`easeIn`). +- Red is the regular variant of `circ` (`easeIn`). - Green -- `easeOut`. - Blue -- `easeInOut`. @@ -405,7 +405,7 @@ As we can see, the graph of the first half of the animation is the scaled down ` ## More interesting "draw" -Instead of moving the element we can do something else. All we need is to write the write the proper `draw`. +Instead of moving the element we can do something else. All we need is to write the proper `draw`. Here's the animated "bouncing" text typing: diff --git a/7-animation/3-js-animation/bounce-easeinout.view/index.html b/7-animation/3-js-animation/bounce-easeinout.view/index.html index 837c50db1..aed3d9d08 100644 --- a/7-animation/3-js-animation/bounce-easeinout.view/index.html +++ b/7-animation/3-js-animation/bounce-easeinout.view/index.html @@ -26,7 +26,7 @@ function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/bounce-easeout.view/index.html b/7-animation/3-js-animation/bounce-easeout.view/index.html index e52eae8de..69dbb7ce0 100644 --- a/7-animation/3-js-animation/bounce-easeout.view/index.html +++ b/7-animation/3-js-animation/bounce-easeout.view/index.html @@ -22,7 +22,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/bounce.view/index.html b/7-animation/3-js-animation/bounce.view/index.html index 1be2580d9..3575ed820 100644 --- a/7-animation/3-js-animation/bounce.view/index.html +++ b/7-animation/3-js-animation/bounce.view/index.html @@ -19,7 +19,7 @@ animate({ duration: 3000, timing: function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/text.view/index.html b/7-animation/3-js-animation/text.view/index.html index e404fe5c4..001b0ae94 100644 --- a/7-animation/3-js-animation/text.view/index.html +++ b/7-animation/3-js-animation/text.view/index.html @@ -36,7 +36,7 @@ function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/8-web-components/1-webcomponents-intro/article.md b/8-web-components/1-webcomponents-intro/article.md index 3279cb133..c3522dea9 100644 --- a/8-web-components/1-webcomponents-intro/article.md +++ b/8-web-components/1-webcomponents-intro/article.md @@ -26,9 +26,9 @@ The International Space Station: ...And this thing flies, keeps humans alive in space! -How such complex devices are created? +How are such complex devices created? -Which principles we could borrow to make our development same-level reliable and scalable? Or, at least, close to it. +Which principles could we borrow to make our development same-level reliable and scalable? Or, at least, close to it? ## Component architecture diff --git a/8-web-components/2-custom-elements/article.md b/8-web-components/2-custom-elements/article.md index 443731914..a84ed1192 100644 --- a/8-web-components/2-custom-elements/article.md +++ b/8-web-components/2-custom-elements/article.md @@ -115,7 +115,7 @@ customElements.define("time-formatted", TimeFormatted); // (2) > ``` -1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time. +1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](mdn:/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time. 2. We need to register our new element by `customElements.define(tag, class)`. 3. And then we can use it everywhere. @@ -149,7 +149,7 @@ The `connectedCallback` triggers when the element is added to the document. Not In the current implementation of ``, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like `a.href`, we expect the change to be immediately visible. So let's fix this. -We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for an attribute for performance reasons. +We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for other, unlisted attributes (that's for performance reasons). Here's a new ``, that auto-updates when attributes change: @@ -320,7 +320,7 @@ For example, buttons are instances of `HTMLButtonElement`, let's build upon it. class HelloButton extends HTMLButtonElement { /* custom element methods */ } ``` -2. Provide an third argument to `customElements.define`, that specifies the tag: +2. Provide the third argument to `customElements.define`, that specifies the tag: ```js customElements.define('hello-button', HelloButton, *!*{extends: 'button'}*/!*); ``` @@ -365,7 +365,7 @@ Our new button extends the built-in one. So it keeps the same styles and standar ## References - HTML Living Standard: . -- Compatiblity: . +- Compatiblity: . ## Summary @@ -397,4 +397,4 @@ Custom elements can be of two types: /*
    - About me + Other information *!* -
    Hello
    -
    I am John!
    +
    I like to swim.
    +
    ...And play volleyball too!
    */!*
    @@ -408,7 +408,7 @@ customElements.define('custom-menu', class extends HTMLElement {
    `; - // slottable is added/removed/replaced + // triggers when slot content changes *!* this.shadowRoot.firstElementChild.addEventListener('slotchange', e => { let slot = e.target; @@ -446,7 +446,7 @@ Composition does not really move nodes, from JavaScript point of view the DOM is JavaScript can access slots using methods: - `slot.assignedNodes/Elements()` -- returns nodes/elements inside the `slot`. -- `node.assignedSlot` -- the reverse meethod, returns slot by a node. +- `node.assignedSlot` -- the reverse property, returns slot by a node. If we'd like to know what we're showing, we can track slot contents using: - `slotchange` event -- triggers the first time a slot is filled, and on any add/remove/replace operation of the slotted element, but not its children. The slot is `event.target`. diff --git a/8-web-components/6-shadow-dom-style/article.md b/8-web-components/6-shadow-dom-style/article.md index 2be81fbb2..98e246a7f 100644 --- a/8-web-components/6-shadow-dom-style/article.md +++ b/8-web-components/6-shadow-dom-style/article.md @@ -111,22 +111,7 @@ customElements.define('custom-dialog', class extends HTMLElement { Now the additional centering styles are only applied to the first dialog: ``. -## :host-context(selector) - -Same as `:host`, but applied only if the shadow host or any of its ancestors in the outer document matches the `selector`. - -E.g. `:host-context(.dark-theme)` matches only if there's `dark-theme` class on `` on anywhere above it: - -```html - - - ... - -``` - -To summarize, we can use `:host`-family of selectors to style the main element of the component, depending on the context. These styles (unless `!important`) can be overridden by the document. +To summarize, we can use `:host`-family of selectors to style the main element of the component. These styles (unless `!important`) can be overridden by the document. ## Styling slotted content @@ -259,7 +244,6 @@ For example, in shadow DOM we can use `--user-card-field-color` CSS variable to
    Name:
    Birthday:
    - ``` Then, we can declare this property in the outer document for ``: @@ -318,7 +302,7 @@ Shadow DOM can include styles, such as `