diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..490051876 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index c8b457585..9395c9fe1 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -35,7 +35,7 @@ Výše uvedené pojmy je dobré si pamatovat, protože se používají ve vývoj Enginy jsou složité, ale jejich základy jsou jednoduché. 1. Engine (v případě prohlížeče zahrnutý do něj) načte („rozebere“, „parsuje“) skript. -2. Pak přeloží („zkompiluje“) skript do strojového jazyka. +2. Pak přeloží („zkompiluje“) skript do strojového kódu. 3. A pak se strojový kód hezky rychle spustí. Při každém kroku procesu engine aplikuje optimalizace. Dokonce sleduje, jak zkompilovaný skript běží, analyzuje data, která jím protékají, a podle těchto znalostí dále optimalizuje strojový kód. @@ -59,7 +59,7 @@ Například JavaScript v prohlížeči může: ## Co NEMŮŽE JavaScript v prohlížeči dělat? -Schopnosti JavaScriptu v prohlížeči jsou omezeny v zájmu bezpečnosti uživatele. Cílem je zabránit zlé webové stránce v přístupu k soukromým informacím nebo v poškození uživatelových dat. +Schopnosti JavaScriptu v prohlížeči jsou omezeny, aby byla chráněna bezpečnost uživatele. Cílem je zabránit zlé webové stránce v přístupu k soukromým informacím nebo v poškození uživatelových dat. Příklady takových omezení: @@ -70,9 +70,9 @@ Příklady takových omezení: Existují způsoby, jak komunikovat s kamerou, mikrofonem nebo jinými zařízeními, ale ty vyžadují výslovné svolení uživatele. Stránka s povoleným JavaScriptem tedy nemůže bez vědomí uživatele zapnout webovou kameru, nasnímat okolí a nahrávku poslat do [NSA](https://cs.wikipedia.org/wiki/Národní_bezpečnostní_agentura). - Různé záložky a okna o sobě navzájem obvykle nevědí. Někdy ano, například tehdy, když jedno okno používá JavaScript k otevření druhého. Ale ani v tomto případě JavaScript z jedné stránky nemůže přistupovat k jiné, pokud pocházejí z různých webových sídel (z jiné domény, protokolu nebo portu). - Tento postup se nazývá „politika stejného původu“. Je možné ji obejít tak, že *obě stránky* souhlasí s výměnou dat a obsahují speciální JavaScriptový kód, který to umožňuje. V tomto tutoriálu to budeme probírat. + Tento postup se nazývá „politika stejného původu“. Je možné ji obejít tak, že *obě stránky* musí souhlasit s výměnou dat a musí obsahovat speciální JavaScriptový kód, který to umožňuje. V tomto tutoriálu to budeme probírat. - Toto omezení je zde opět pro bezpečnost uživatele. Stránka z `http://anysite.com`, kterou uživatel otevřel, nesmí mít možnost přistupovat k jiné záložce prohlížeče s URL `http://gmail.com` a krást odtamtud informace. + Toto omezení je zde opět pro bezpečnost uživatele. Stránka z `http://anysite.com`, kterou uživatel otevřel, nesmí mít možnost přistupovat k jiné záložce prohlížeče s URL například `http://gmail.com` a krást odtamtud informace. - JavaScript může jednoduše komunikovat po síti se serverem, z něhož přišla aktuální stránka, ale jeho schopnost získávat data z jiných sídel/domén je značně omezená. Přestože je to možné, vyžaduje to výslovný souhlas (uvedený v HTTP hlavičce) vzdálené strany. I to je bezpečnostní omezení. ![](limitations.svg) @@ -92,7 +92,7 @@ JavaScript je jediná prohlížečová technologie, která má všechny tyto tř To vše činí JavaScript unikátním. To vše je důvodem, proč je dnes nejrozšířenějším nástrojem pro vytváření prohlížečových rozhraní. -Při tom všem však JavaScript umožňuje vytvářet i servery, mobilní aplikace a podobně. +Při tom všem však lze JavaScript použít i k vytváření serverů, mobilních aplikací a podobně. ## Jazyky „nad“ JavaScriptem @@ -113,7 +113,7 @@ Příklady takových jazyků: - [Brython](https://brython.info/) je transpiler Pythonu do JavaScriptu, který umožňuje psát aplikace v čistém Pythonu bez JavaScriptu. - [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) je moderní, stručný a bezpečný programovací jazyk, jehož cílem může být prohlížeč nebo Node. -Jsou i další. Samozřejmě i když používáme některý z transpilovaných jazyků, měli bychom znát i JavaScript, abychom skutečně porozuměli tomu, co se děje. +Jsou i další. Samozřejmě i když používáme některý z těchto transpilovaných jazyků, měli bychom znát i JavaScript, abychom skutečně porozuměli tomu, co se děje. ## Shrnutí 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 1990a20cd..53831d82d 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -29,7 +29,7 @@ Hlavní rozdíl mezi „textovým editorem“ a „IDE“ je, že „IDE“ prac V praxi mají textové editory obvykle velké množství zásuvných modulů a rozšíření včetně syntaktických analyzátorů a automatického doplňování na adresářové úrovni, takže mezi textovými editory a vývojovými prostředími neexistuje pevně daná hranice. -Vaši pozornost si zasluhují následující možnosti: +Možností je mnoho, například: - [Sublime Text](http://www.sublimetext.com) (různé platformy, shareware). - [Notepad++](https://notepad-plus-plus.org/) (Windows, zdarma). @@ -41,4 +41,9 @@ Ve výše uvedených seznamech jsou zmíněny editory, které já nebo moji př Svět je velký a existují v něm i jiné skvělé editory. Vyberte si prosím ten, který se vám nejvíce líbí. -Výběr editoru, stejně jako výběr každého nástroje, je individuální záležitost a závisí na projektech, zvyklostech a osobním vkusu každého vývojáře. +Výběr editoru, stejně jako výběr kteréhokoli jiného nástroje, je individuální záležitost a závisí na vašich projektech, zvyklostech a osobním vkusu. + +Autorovo osobní mínění: + +- Když vyvíjím převážně frontend, používám [Visual Studio Code](https://code.visualstudio.com/). +- Jinak, pokud je to většinou jiný jazyk/platforma a částečně frontend, zvažte jiné editory, například XCode (Mac), Visual Studio (Windows) nebo rodinu Jetbrains (Webstorm, PHPStorm, RubyMine atd. v závislosti na jazyce). diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index 47762391c..6edffeb3e 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -12,13 +12,14 @@ const datumNarození = '18.04.1982'; const věk = nějakýKód(datumNarození); ``` -Zde máme konstantu `datumNarození` a pomocí nějakého kódu se z této proměnné vypočítá `věk` (kód není pro stručnost uveden, na podrobnostech zde nezáleží). +Zde máme konstantu `datumNarození` a také konstantu `věk`. + +Konstanta `věk` se vypočítá z konstanty `datumNarození` pomocí `nějakýKód()`, což znamená volání funkce, které jsme zatím nevysvětlili (ale brzy tak učiníme!), ale na podrobnostech zde nezáleží, podstatné je, že `věk` se vypočítá nějak podle `datumNarození`. Bylo by správné použít pro název proměnné `datumNarození` velká písmena? A pro `věk`? Nebo dokonce pro obě? ```js -const DATUM_NAROZENÍ = '18.04.1982'; // velkými písmeny? +const DATUM_NAROZENÍ = '18.04.1982'; // napsat DATUM_NAROZENÍ velkými písmeny? -const VĚK = nějakýKód(DATUM_NAROZENÍ); // velkými písmeny? +const VĚK = nějakýKód(DATUM_NAROZENÍ); // napsat VĚK velkými písmeny? ``` - diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 9340c5512..b313c6c2c 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -151,11 +151,11 @@ Proměnnou bychom tedy měli deklarovat jen jednou a pak se na ni odkazovat bez ```` ```smart header="Funkcionální jazyky" -Stojí za zmínku, že existují [funkcionální](https://cs.wikipedia.org/wiki/Funkcionální_programování) programovací jazyky, například [Scala](http://www.scala-lang.org/) nebo [Erlang](http://www.erlang.org/), které nedovolují měnit hodnotu proměnných. +Stojí za zmínku, že existují tzv. [čistě funkcionální](https://en.wikipedia.org/wiki/Purely_functional_programming) programovací jazyky, například [Haskell](https://cs.wikipedia.org/wiki/Haskell_(programovací_jazyk)), které nedovolují měnit hodnotu proměnných. Když je v takových jazycích hodnota jednou uložena „do krabičky“, zůstane tam navždy. Pokud chceme uložit něco jiného, jazyk nás přinutí vytvořit novou krabičku (deklarovat novou proměnnou). Nemůžeme znovu použít starou. -Ačkoli to na první pohled může vypadat trochu zvláštně, i v takových jazycích je seriózní programování docela dobře možné. Kromě toho existují oblasti, např. paralelní výpočty, v nichž právě toto omezení přináší určité výhody. Prostudovat si takový jazyk (i když jej neplánujete v dohledné době používat) se doporučuje k rozšíření obzorů. +Ačkoli to na první pohled může vypadat trochu zvláštně, i v takových jazycích je seriózní programování docela dobře možné. Kromě toho existují oblasti, např. paralelní výpočty, v nichž právě toto omezení přináší určité výhody. ``` ## Názvy proměnných [#variable-naming] @@ -198,7 +198,7 @@ Proměnné s názvy `jablko` a `JABLKO` jsou dvě různé proměnné. ``` ````smart header="Nelatinská písmena jsou povolena, ale nedoporučují se" -Je dovoleno použít písmena z jakéhokoli jazyka, včetně písmen z kyrilice nebo dokonce čínských znaků, třeba takto: +Je dovoleno použít písmena z jakéhokoli jazyka, včetně písmen z kyrilice, čínských znaků a podobně, třeba takto: ```js let имя = '...'; @@ -289,7 +289,7 @@ Výhody: Kdy bychom měli používat pro konstantu velká písmena a kdy bychom ji měli pojmenovat obvyklým způsobem? Ujasníme si to. -Být „konstanta“ znamená prostě to, že hodnota proměnné se nikdy nezmění. Existují však konstanty, které jsou známy již před spuštěním programu (například hexadecimální hodnota červené barvy), a pak jsou konstanty, které se *vypočítají* až za běhu programu, ale jejich vypočtená hodnota se nikdy nezmění. +Být „konstanta“ znamená prostě to, že hodnota proměnné se nikdy nezmění. Některé konstanty jsou však známy již před spuštěním programu (například hexadecimální hodnota červené barvy) a pak jsou konstanty, které se *vypočítají* až za běhu programu, ale jejich vypočtená hodnota se nikdy nezmění. Příklad: diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 02d056f8a..ac27efda1 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -95,12 +95,6 @@ const bigInt = 1234567890123456789012345678901234567890n; Protože čísla typu `BigInt` potřebujeme jen málokdy, nebudeme je tady probírat, ale věnujeme jim zvláštní kapitolu . Až budete tak velká čísla potřebovat, přečtěte si ji. -```smart header="Otázka kompatibility" -Právě nyní je `BigInt` podporován ve Firefoxu/Chrome/Edge/Safari, ale ne v IE. -``` - -Nahlédnutím do [*MDN* tabulky kompatibility pro BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) zjistíte, které verze prohlížeče jej podporují. - ## Řetězec Řetězec (anglicky string) v JavaScriptu musí být uzavřen do uvozovek. @@ -224,7 +218,7 @@ Typ `symbol` se používá k vytváření unikátních identifikátorů pro obje ## Operátor typeof [#type-typeof] -Operátor `typeof` vrací typ argumentu. Je užitečný, když chceme hodnoty různých typů zpracovávat různě anebo si jen chceme typ rychle ověřit. +Operátor `typeof` vrací typ operandu. Je užitečný, když chceme hodnoty různých typů zpracovávat různě anebo si jen chceme typ rychle ověřit. Volání `typeof x` vrátí řetězec s názvem typu: diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md index 2cabd01b2..57f4cd364 100644 --- a/1-js/02-first-steps/07-type-conversions/article.md +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -70,7 +70,7 @@ Pravidla pro konverzi na číslo: |`undefined`|`NaN`| |`null`|`0`| |true a false | `1` a `0` | -| `řetězec` | Odstraní se bílé znaky (mezery, tabulátory, konce řádku) ze začátku a konce. Je-li výsledný řetězec prázdný, výsledkem je `0`. Jinak se číslo „přečte“ z řetězce. Při chybě je vydáno `NaN`. | +| `řetězec` | Odstraní se bílé znaky (mezery, tabulátory `\t`, konce řádku `\n` atd.) ze začátku a konce. Je-li výsledný řetězec prázdný, výsledkem je `0`. Jinak se číslo „přečte“ z řetězce. Při chybě je vydáno `NaN`. | Příklady: @@ -130,7 +130,7 @@ Konverze se řídí těmito pravidly: |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | -| `řetězec` | Načte se „doslovně“, bílé znaky na obou stranách se ignorují. Z prázdného řetězce se stane `0`. Při chybě je vydáno `NaN`. | +| `řetězec` | Načte se „doslovně“, bílé znaky (mezery, tabulátory `\t`, konce řádku `\n` atd.) na obou stranách se ignorují. Z prázdného řetězce se stane `0`. Při chybě je vydáno `NaN`. | **`Konverze na boolean`** -- Nastává při logických operacích. Můžeme ji provést pomocí `Boolean(hodnota)`. diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index ff417168e..8ade3f3d7 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -16,10 +16,10 @@ undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) ``` -1. Sčítání s řetězcem `"" + 1` převede `1` na řetězec: `"" + 1 = "1"`, pak tedy budeme mít `"1" + 0` a použije se stejné pravidlo. +1. Sčítání s řetězcem `"" + 1` převede `1` na řetězec: `"" + 1 = "1"` a pak budeme mít `"1" + 0`, použije se stejné pravidlo. 2. Odčítání `-` (stejně jako většina matematických operací) pracuje jen s čísly, takže převede prázdný řetězec `""` na `0`. 3. Sčítání s řetězcem připojí k řetězci číslo `5`. 4. Odčítání vždy převádí operandy na čísla, takže vyrobí z `" -9 "` číslo `-9` (mezery okolo něj se ignorují). -5. `null` se převede na číslo `0`. -6. `undefined` se převede na číslo `NaN`. +5. Z `null` se po konverzi na číslo stane `0`. +6. Z `undefined` se po konverzi na číslo stane `NaN`. 7. Když se řetězec převádí na číslo, mezerové znaky se z jeho začátku a konce odříznou. V tomto případě se celý řetězec skládá z mezerových znaků, konkrétně `\t`, `\n` a „obyčejné“ mezery mezi nimi. Stejně jako prázdný řetězec se tedy převede na `0`. diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index 1ce09db33..c600e73fa 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -52,6 +52,7 @@ Příklad: ```js run alert( 5 % 2 ); // 1, zbytek po dělení 5 děleno 2 alert( 8 % 3 ); // 2, zbytek po dělení 8 děleno 3 +alert( 8 % 4 ); // 0, zbytek po dělení 8 děleno 4 ``` ### Umocňování ** @@ -195,18 +196,18 @@ Následuje výtažek z [tabulky priorit](https://developer.mozilla.org/en-US/doc | Priorita | Název | Znak | |----------|-------|------| | ... | ... | ... | -| 15 | unární plus | `+` | -| 15 | unární negace | `-` | -| 14 | umocňování | `**` | -| 13 | násobení | `*` | -| 13 | dělení | `/` | -| 12 | sčítání | `+` | -| 12 | odčítání | `-` | +| 14 | unární plus | `+` | +| 14 | unární negace | `-` | +| 13 | umocňování | `**` | +| 12 | násobení | `*` | +| 12 | dělení | `/` | +| 11 | sčítání | `+` | +| 11 | odčítání | `-` | | ... | ... | ... | | 2 | přiřazení | `=` | | ... | ... | ... | -Jak vidíme, „unární plus“ má prioritu `15`, která je vyšší než priorita `12` „sčítání“ (binární plus). To je důvod, proč se ve výrazu `"+jablka + +pomeranče"` unární plusy vyhodnotí před sčítáním. +Jak vidíme, „unární plus“ má prioritu `14`, která je vyšší než priorita `11` „sčítání“ (binární plus). To je důvod, proč se ve výrazu `"+jablka + +pomeranče"` unární plusy vyhodnotí před sčítáním. ## Přiřazení @@ -304,9 +305,9 @@ Tyto operátory mají stejnou prioritu jako běžné přiřazení, takže se pro ```js run let n = 2; -n *= 3 + 5; +n *= 3 + 5; // napřed se provede část vpravo, totéž jako n *= 8 -alert( n ); // 16 (napřed se provede část vpravo, totéž jako n *= 8) +alert( n ); // 16 ``` ## Inkrementace a dekrementace diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 869bc72b1..2022f9070 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -68,7 +68,7 @@ if (podmínka) { ## Klauzule „else“ -Příkaz `if` může obsahovat nepovinný blok „else“, který se vykoná, když podmínka je nepravdivá. +Příkaz `if` může obsahovat nepovinný blok `else`, který se vykoná, když podmínka je nepravdivá. Příklad: ```js run @@ -181,9 +181,9 @@ alert( zpráva ); Na první pohled může být obtížné pochopit, o co tady jde. Po bližším prozkoumání však vidíme, že to je jen obyčejná posloupnost testů: 1. První otazník ověří, zda `věk < 3`. -2. Pokud ano, vrátí `'Nazdar, mimino!'`. Jinak pokračuje výrazem za dvojtečkou `:` a ověří `věk < 18`. -3. Pokud to platí, vrátí `'Ahoj!'`. Jinak pokračuje výrazem za další dvojtečkou `:` a ověří `věk < 100`. -4. Pokud to platí, vrátí `'Dobrý den!'`. Jinak pokračuje výrazem za poslední dvojtečkou `:` a vrátí `'To je ale vysoký věk!'`. +2. Pokud ano, vrátí `'Nazdar, mimino!'`. Jinak pokračuje výrazem za dvojtečkou ":" a ověří `věk < 18`. +3. Pokud to platí, vrátí `'Ahoj!'`. Jinak pokračuje výrazem za další dvojtečkou ":" a ověří `věk < 100`. +4. Pokud to platí, vrátí `'Dobrý den!'`. Jinak pokračuje výrazem za poslední dvojtečkou ":" a vrátí `'To je ale vysoký věk!'`. Při použití `if..else` by to vypadalo následovně: 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 index 0e5ff46c4..4af2b3195 100644 --- a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -29,7 +29,7 @@ Například zde zobrazíme `anonym`, jestliže proměnná `uživatel` není defi ```js run let uživatel; -alert(uživatel ?? "anonym"); // anonym (uživatel není definován) +alert(uživatel ?? "Anonym"); // Anonym (uživatel má hodnotu undefined) ``` Zde je příklad, kde má proměnná `uživatel` přiřazenou hodnotu: @@ -37,7 +37,7 @@ Zde je příklad, kde má proměnná `uživatel` přiřazenou hodnotu: ```js run let uživatel = "Jan"; -alert(uživatel ?? "anonym"); // Jan (uživatel je definován) +alert(uživatel ?? "Anonym"); // Jan (uživatel nemá hodnotu null/undefined) ``` Můžeme také použít sekvenci `??` k výběru první hodnoty ze seznamu, která není `null/undefined`. @@ -105,7 +105,7 @@ V praxi je nulová výška často platnou hodnotou, takže by neměla být nahra ## Priorita -Priorita operátoru `??` je stejná jako `||`. Oba operátory mají v [tabulce MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table) prioritu `4`. +Priorita operátoru `??` je stejná jako `||`. Oba operátory mají v [tabulce MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table) prioritu `3`. To znamená, že operátor koalescence `??` se stejně jako `||` vyhodnocuje před `=` a `?`, ale až po většině ostatních operací, například `+`, `*`. diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md index 3d2dcc67e..4392c8571 100644 --- a/1-js/02-first-steps/15-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -24,7 +24,7 @@ Napřed je uvedeno klíčové slovo `function`, pak *název funkce*, potom máme ```js function název(parametr1, parametr2, ... parametrN) { - ...tělo... + // tělo } ``` @@ -209,6 +209,12 @@ zobrazZprávu("Anna"); // Anna: text není zadán Jestliže argument k parametru `text` nebude předán, bude mít hodnotu `"text není zadán"`. +Výchozí hodnota bude nastavena i tehdy, když parametr existuje, ale je striktně roven `undefined`, například: + +```js +showMessage("Anna", undefined); // Anna: text není zadán +``` + Zde `"text není zadán"` je řetězec, ale může to být složitější výraz, který se vyhodnotí a přiřadí jen tehdy, jestliže argument chybí. Je tedy možné i toto: ```js run @@ -258,7 +264,7 @@ function zobrazZprávu(odKoho, text) { ```` ### Alternativní výchozí argumenty -Někdy má smysl nenastavovat výchozí hodnoty argumentů v deklaraci funkce, ale až později. +Někdy má smysl nastavovat výchozí hodnoty argumentů až později po deklaraci funkce. Abychom během provádění funkce ověřili, zda argument byl předán, můžeme jej porovnat s `undefined`: @@ -453,7 +459,7 @@ Tyto příklady předpokládají běžný význam prefixů. Se svým týmem se m ```smart header="Ultrakrátké názvy funkcí" Funkce, které se používají *velmi často*, mají někdy ultrakrátké názvy. -Například knihovna [jQuery](http://jquery.com) definuje funkci s názvem `$`. Knihovna [Lodash](http://lodash.com/) má svou ústřední funkci pojmenovanou `_`. +Například knihovna [jQuery](https://jquery.com) definuje funkci s názvem `$`. Knihovna [Lodash](https://lodash.com/) má svou ústřední funkci pojmenovanou `_`. To jsou však výjimky. Obecně by názvy funkcí měly být stručné a popisné. ``` diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md index 041db18bc..26fe35019 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/solution.md @@ -1,17 +1,17 @@ ```js run -function ask(question, yes, no) { - if (confirm(question)) yes(); - else no(); +function zeptejSe(otázka, ano, ne) { + if (confirm(otázka)) ano(); + else ne(); } -ask( - "Do you agree?", +zeptejSe( + "Souhlasíte?", *!* - () => alert("You agreed."), - () => alert("You canceled the execution.") + () => alert("Souhlasil jste."), + () => alert("Zrušil jste provádění.") */!* ); ``` -Looks short and clean, right? +Vypadá to stručně a čistě, že? diff --git a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md index e18c08a83..92dd7538c 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/1-rewrite-arrow/task.md @@ -1,17 +1,17 @@ -# Rewrite with arrow functions +# Přepište na šipkové funkce -Replace Function Expressions with arrow functions in the code below: +Přepište funkční výrazy v následujícím kódu na šipkové funkce: ```js run -function ask(question, yes, no) { - if (confirm(question)) yes(); - else no(); +function zeptejSe(otázka, ano, ne) { + if (confirm(otázka)) ano(); + else ne(); } -ask( - "Do you agree?", - function() { alert("You agreed."); }, - function() { alert("You canceled the execution."); } +zeptejSe( + "Souhlasíte?", + function() { alert("Souhlasil jste."); }, + function() { alert("Zrušil jste provádění."); } ); ``` diff --git a/1-js/02-first-steps/17-arrow-functions-basics/article.md b/1-js/02-first-steps/17-arrow-functions-basics/article.md index 50c0d475d..2e013b0e4 100644 --- a/1-js/02-first-steps/17-arrow-functions-basics/article.md +++ b/1-js/02-first-steps/17-arrow-functions-basics/article.md @@ -1,111 +1,110 @@ -# Arrow functions, the basics +# Šipkové funkce (arrow funkce) – základy +Existuje ještě jedna velice jednoduchá a výstižná syntaxe vytváření funkcí, která často bývá lepší než funkční výrazy. -There's another very simple and concise syntax for creating functions, that's often better than Function Expressions. - -It's called "arrow functions", because it looks like this: +Nazývá se „šipková funkce“, jelikož vypadá takto: ```js -let func = (arg1, arg2, ..., argN) => expression; +let funkce = (arg1, arg2, ..., argN) => výraz; ``` -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. +Tím se vytvoří funkce `funkce`, která přijímá argumenty `arg1..argN`, pak s jejich použitím vyhodnotí `výraz` na pravé straně a vrátí jeho výsledek. -In other words, it's the shorter version of: +Jinými slovy, je to kratší verze tohoto: ```js -let func = function(arg1, arg2, ..., argN) { - return expression; +let funkce = function(arg1, arg2, ..., argN) { + return výraz; }; ``` -Let's see a concrete example: +Podívejme se na konkrétní příklad: ```js run -let sum = (a, b) => a + b; +let součet = (a, b) => a + b; -/* This arrow function is a shorter form of: +/* Tato šipková funkce je zkrácenou formou této funkce: -let sum = function(a, b) { +let součet = function(a, b) { return a + b; }; */ -alert( sum(1, 2) ); // 3 +alert( součet(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. +Jak vidíte, `(a, b) => a + b` značí funkci, která má dva parametry `a` a `b`. Když je vykonána, vyhodnotí výraz `a + b` a vrátí jeho výsledek. -- If we have only one argument, then parentheses around parameters can be omitted, making that even shorter. +- Máme-li pouze jeden parametr, můžeme závorky kolem něj vynechat, čímž se zápis ještě zkrátí. - For example: + Příklad: ```js run *!* - let double = n => n * 2; - // roughly the same as: let double = function(n) { return n * 2 } + let dvojnásobek = n => n * 2; + // zhruba totéž jako: let dvojnásobek = function(n) { return n * 2 } */!* - alert( double(3) ); // 6 + alert( dvojnásobek(3) ); // 6 ``` -- If there are no arguments, parentheses are empty, but they must be present: +- Nejsou-li žádné parametry, závorky budou prázdné, ale musí být uvedeny: ```js run - let sayHi = () => alert("Hello!"); + let řekniAhoj = () => alert("Ahoj!"); - sayHi(); + řekniAhoj(); ``` -Arrow functions can be used in the same way as Function Expressions. +Šipkové funkce můžeme používat stejným způsobem jako funkční výrazy. -For instance, to dynamically create a function: +Například k dynamickému vytvoření funkce: ```js run -let age = prompt("What is your age?", 18); +let věk = prompt("Kolik je vám let?", 18); -let welcome = (age < 18) ? - () => alert('Hello!') : - () => alert("Greetings!"); +let uvítání = (věk < 18) ? + () => alert('Ahoj!') : + () => alert("Dobrý den!"); -welcome(); +uvítání(); ``` -Arrow functions may appear unfamiliar and not very readable at first, but that quickly changes as the eyes get used to the structure. +Šipkové funkce mohou na první pohled vypadat podivně a nepříliš čitelně, ale to se rychle změní, jakmile si oči na tuto strukturu zvyknou. -They are very convenient for simple one-line actions, when we're just too lazy to write many words. +Jsou velmi vhodné pro jednoduché jednořádkové akce, kdy se nám prostě nechce psát příliš mnoho slov. -## Multiline arrow functions +## Víceřádkové šipkové funkce -The arrow functions that we've seen so far were very simple. They took arguments from the left of `=>`, evaluated and returned the right-side expression with them. +Šipkové funkce, které jsme doposud viděli, byly velmi jednoduché. Přebíraly argumenty z levé strany `=>`, vyhodnotily s nimi výraz na pravé straně a vrátily jeho hodnotu. -Sometimes we need a more complex function, with multiple expressions and statements. In that case, we can enclose them in curly braces. The major difference is that curly braces require a `return` within them to return a value (just like a regular function does). +Někdy potřebujeme složitější funkci s více výrazy a příkazy. V takovém případě je můžeme uzavřít do složených závorek. Hlavní rozdíl je v tom, že složené závorky vyžadují uvnitř `return`, aby mohly vrátit hodnotu (stejně jako běžná funkce). -Like this: +Například takto: ```js run -let sum = (a, b) => { // the curly brace opens a multiline function - let result = a + b; +let součet = (a, b) => { // složená závorka uvozuje víceřádkovou funkci + let výsledek = a + b; *!* - return result; // if we use curly braces, then we need an explicit "return" + return výsledek; // když používáme složené závorky, musíme výslovně uvést „return“ */!* }; -alert( sum(1, 2) ); // 3 +alert( součet(1, 2) ); // 3 ``` -```smart header="More to come" -Here we praised arrow functions for brevity. But that's not all! +```smart header="Bude toho víc" +Zde jsme chválili šipkové funkce pro jejich stručnost, ale to ještě není všechno! -Arrow functions have other interesting features. +Šipkové funkce mají i jiné zajímavé vlastnosti. -To study them in-depth, we first need to get to know some other aspects of JavaScript, so we'll return to arrow functions later in the chapter . +Abychom je mohli prostudovat do hloubky, musíme napřed poznat některé další prvky JavaScriptu. K šipkovým funkcím se tedy vrátíme později v kapitole . -For now, we can already use arrow functions for one-line actions and callbacks. +Prozatím už můžeme používat šipkové funkce pro jednořádkové akce a callbacky. ``` -## Summary +## Shrnutí -Arrow functions are handy for simple actions, especially for one-liners. They come in two flavors: +Šipkové funkce se hodí pro jednoduché akce, zvláště pro jednořádkové funkce. Dají se napsat dvěma způsoby: -1. Without curly braces: `(...args) => expression` -- the right side is an expression: the function evaluates it and returns the result. Parentheses can be omitted, if there's only a single argument, e.g. `n => n*2`. -2. With curly braces: `(...args) => { body }` -- brackets allow us to write multiple statements inside the function, but we need an explicit `return` to return something. +1. Bez složených závorek: `(...args) => výraz` -- na pravé straně je výraz: funkce jej vyhodnotí a vrátí jeho výsledek. Kulaté závorky můžeme vynechat, má-li funkce pouze jeden parametr, např. `n => n*2`. +2. Se složenými závorkami: `(...args) => { tělo }` -- složené závorky nám umožňují uvést ve funkci více příkazů, ale aby funkce něco vrátila, musíme výslovně uvést `return`. diff --git a/1-js/02-first-steps/18-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md index 016214e3b..94f1e9840 100644 --- a/1-js/02-first-steps/18-javascript-specials/article.md +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -1,51 +1,51 @@ -# JavaScript specials +# Speciality JavaScriptu -This chapter briefly recaps the features of JavaScript that we've learned by now, paying special attention to subtle moments. +Tato kapitola krátce opakuje prvky JavaScriptu, které jsme se doposud naučili. Zvláštní pozornost věnuje citlivým místům. -## Code structure +## Struktura kódu -Statements are delimited with a semicolon: +Příkazy jsou ukončeny středníkem: ```js run no-beautify -alert('Hello'); alert('World'); +alert('Ahoj'); alert('světe'); ``` -Usually, a line-break is also treated as a delimiter, so that would also work: +Konec řádku se zpravidla také považuje za středník, takže fungovat bude i toto: ```js run no-beautify -alert('Hello') -alert('World') +alert('Ahoj') +alert('světe') ``` -That's called "automatic semicolon insertion". Sometimes it doesn't work, for instance: +To se nazývá „automatické vkládání středníku“. Někdy to však nefunguje, například: ```js run -alert("There will be an error after this message") +alert("Po této zprávě bude ohlášena chyba") [1, 2].forEach(alert) ``` -Most codestyle guides agree that we should put a semicolon after each statement. +Většina průvodců stylem kódu se shoduje, že bychom měli uvádět středník za každým příkazem. -Semicolons are not required after code blocks `{...}` and syntax constructs with them like loops: +Středníky nejsou vyžadovány za bloky kódu `{...}` a syntaktickými konstrukcemi, které je obsahují, například cykly: ```js function f() { - // no semicolon needed after function declaration + // za deklarací funkce není středník zapotřebí } for(;;) { - // no semicolon needed after the loop + // za cyklem není středník zapotřebí } ``` -...But even if we can put an "extra" semicolon somewhere, that's not an error. It will be ignored. +...Ale i když někde uvedeme středník „navíc“, není to chyba. Středník bude ignorován. -More in: . +Více v kapitole: . -## Strict mode +## Striktní režim -To fully enable all features of modern JavaScript, we should start scripts with `"use strict"`. +Abychom plně využili všechny vlastnosti moderního JavaScriptu, měli bychom zahajovat skripty direktivou `"use strict"`. ```js 'use strict'; @@ -53,143 +53,143 @@ To fully enable all features of modern JavaScript, we should start scripts with ... ``` -The directive must be at the top of a script or at the beginning of a function body. +Tato direktiva musí být uvedena na začátku skriptu nebo těla funkce. -Without `"use strict"`, everything still works, but some features behave in the old-fashioned, "compatible" way. We'd generally prefer the modern behavior. +Bez `"use strict"` bude všechno stále fungovat, ale některé prvky se budou chovat staromódně, „kompatibilně“. Obvykle dáváme přednost modernímu chování. -Some modern features of the language (like classes that we'll study in the future) enable strict mode implicitly. +Některé moderní vlastnosti jazyka (např. třídy, které budeme probírat v budoucnu) implicitně umožňují striktní režim. -More in: . +Více v kapitole: . -## Variables +## Proměnné -Can be declared using: +Lze je deklarovat pomocí: - `let` -- `const` (constant, can't be changed) -- `var` (old-style, will see later) +- `const` (konstanta, nemůže být měněna) +- `var` (ve starém stylu, uvidíme později) -A variable name can include: -- Letters and digits, but the first character may not be a digit. -- Characters `$` and `_` are normal, on par with letters. -- Non-Latin alphabets and hieroglyphs are also allowed, but commonly not used. +Název proměnné může obsahovat: +- Písmena a číslice, ale první znak nesmí být číslice. +- Znaky `$` a `_`, které lze používat běžně jako písmena. +- Povoleny jsou i znaky nelatinských abeced a hieroglyfy, ale obvykle se nepoužívají. -Variables are dynamically typed. They can store any value: +Proměnné jsou dynamicky typovány. Může do nich být uložena jakákoli hodnota: ```js let x = 5; -x = "John"; +x = "Jan"; ``` -There are 8 data types: +Existuje osm datových typů: -- `number` for both floating-point and integer numbers, -- `bigint` for integer numbers of arbitrary length, -- `string` for strings, -- `boolean` for logical values: `true/false`, -- `null` -- a type with a single value `null`, meaning "empty" or "does not exist", -- `undefined` -- a type with a single value `undefined`, meaning "not assigned", -- `object` and `symbol` -- for complex data structures and unique identifiers, we haven't learnt them yet. +- `number` pro čísla, a to jak celá, tak s pohyblivou řádovou čárkou, +- `bigint` pro celá čísla libovolné délky, +- `string` pro řetězce, +- `boolean` pro logické hodnoty: `true/false`, +- `null` -- typ s jedinou hodnotou `null`, která znamená „prázdná“ nebo „neexistující“, +- `undefined` -- typ s jedinou hodnotou `undefined`, která znamená „nepřiřazená“, +- `object` a `symbol` -- pro komplexní datové struktury a unikátní identifikátory, zatím jsme je neprobírali. -The `typeof` operator returns the type for a value, with two exceptions: +Operátor `typeof` vrací typ hodnoty se dvěma výjimkami: ```js -typeof null == "object" // error in the language -typeof function(){} == "function" // functions are treated specially +typeof null == "object" // chyba v jazyce +typeof function(){} == "function" // s funkcemi se zachází odlišně ``` -More in: and . +Více v kapitole: a . -## Interaction +## Interakce -We're using a browser as a working environment, so basic UI functions will be: +Jako pracovní prostředí používáme prohlížeč, takže základní funkce uživatelského rozhraní budou: -[`prompt(question, [default])`](mdn:api/Window/prompt) -: Ask a `question`, and return either what the visitor entered or `null` if they clicked "cancel". +[`prompt(otázka, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt) +: Položí otázku `otázka` a vrátí buď to, co návštěvník zadal, nebo `null`, jestliže stiskl „Storno“. -[`confirm(question)`](mdn:api/Window/confirm) -: Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`. +[`confirm(otázka)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) +: Položí otázku `otázka` a nabídne na výběr mezi OK a Storno. Zvolená možnost je vrácena jako `true/false`. -[`alert(message)`](mdn:api/Window/alert) -: Output a `message`. +[`alert(zpráva)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert) +: Vypíše zprávu `zpráva`. -All these functions are *modal*, they pause the code execution and prevent the visitor from interacting with the page until they answer. +Všechny tyto funkce jsou *modální*. Pozastaví vykonávání kódu a znemožní uživateli jinou interakci se stránkou, dokud neodpoví. -For instance: +Příklad: ```js run -let userName = prompt("Your name?", "Alice"); -let isTeaWanted = confirm("Do you want some tea?"); +let uživatelskéJméno = prompt("Jak se jmenujete?", "Alice"); +let dáSiČaj = confirm("Dáte si čaj?"); -alert( "Visitor: " + userName ); // Alice -alert( "Tea wanted: " + isTeaWanted ); // true +alert( "Návštěvník: " + uživatelskéJméno ); // Alice +alert( "Dá si čaj: " + dáSiČaj ); // true ``` -More in: . +Více v kapitole: . -## Operators +## Operátory -JavaScript supports the following operators: +JavaScript podporuje následující operátory: -Arithmetical -: Regular: `* + - /`, also `%` for the remainder and `**` for power of a number. +Aritmetické +: Obvyklé: `* + - /`, dále `%` pro zbytek po dělení a `**` pro mocninu. - The binary plus `+` concatenates strings. And if any of the operands is a string, the other one is converted to string too: + Binární plus `+` spojuje řetězce. Jestliže je jeden z operandů řetězec, bude na řetězec převeden i druhý operand: ```js run - alert( '1' + 2 ); // '12', string - alert( 1 + '2' ); // '12', string + alert( '1' + 2 ); // '12', řetězec + alert( 1 + '2' ); // '12', řetězec ``` -Assignments -: There is a simple assignment: `a = b` and combined ones like `a *= 2`. +Přiřazení +: Existuje jednoduché přiřazení `a = b` a kombinovaná přiřazení, např. `a *= 2`. -Bitwise -: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed. +Bitové +: Bitové operátory pracují se 32-bitovými celými čísly na nejnižší, bitové úrovni: až je budete potřebovat, viz [dokumentaci](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators). -Conditional -: The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`. +Podmíněný +: Jediný operátor se třemi parametry: `podmínka ? výsledekA : výsledekB`. Je-li `podmínka` pravdivá, vrátí `výsledekA`, jinak vrátí `výsledekB`. -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. +Logické +: Logické AND `&&` a OR `||` provádějí zkrácené vyhodnocování a pak vrátí hodnotu, na které se zastavily (nemusí to nutně být `true`/`false`). Logické NOT `!` převádí operand na typ boolean a vrací opačnou hodnotu. -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`. +Koalescence +: Operátor `??` poskytuje způsob, jak vybrat definovanou hodnotu ze seznamu proměnných. Výsledek `a ?? b` je `a`, pokud není `null/undefined`; v takovém případě je výsledek `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: +Porovnávání +: Test rovnosti `==` pro hodnoty různých typů je převede na číslo (s výjimkou `null` a `undefined`, které se rovnají sobě navzájem a ničemu jinému), takže tyto hodnoty jsou si rovny: ```js run alert( 0 == false ); // true alert( 0 == '' ); // true ``` - Other comparisons convert to a number as well. + Jiná porovnání se převádí také na čísla. - The strict equality operator `===` doesn't do the conversion: different types always mean different values for it. + Operátor striktní rovnosti `===` tuto konverzi neprovádí: různé typy pro něj vždy znamenají, že hodnoty se nerovnají. - Values `null` and `undefined` are special: they equal `==` each other and don't equal anything else. + Hodnoty `null` a `undefined` jsou speciální: rovnají se `==` sobě navzájem a nerovnají se ničemu jinému. - Greater/less comparisons compare strings character-by-character, other types are converted to a number. + Operátory porovnání větší/menší porovnávají řetězce znak po znaku, ostatní typy se převádějí na čísla. -Other operators -: There are few others, like a comma operator. +Ostatní +: Existuje několik dalších operátorů, např. operátor čárky. -More in: , , , . +Více v kapitolách: , , , . -## Loops +## Cykly -- We covered 3 types of loops: +- Probrali jsme tři druhy cyklů: ```js // 1 - while (condition) { + while (podmínka) { ... } // 2 do { ... - } while (condition); + } while (podmínka); // 3 for(let i = 0; i < 10; i++) { @@ -197,88 +197,88 @@ More in: , , , . +Více v kapitole: . -Later we'll study more types of loops to deal with objects. +Později prostudujeme další druhy cyklů, které pracují s objekty. -## The "switch" construct +## Konstrukce „switch“ -The "switch" construct can replace multiple `if` checks. It uses `===` (strict equality) for comparisons. +Konstrukce „switch“ dokáže nahradit několik testů `if`. K porovnávání používá `===` (striktní rovnost). -For instance: +Příklad: ```js run -let age = prompt('Your age?', 18); +let věk = prompt('Kolik je vám let?', 18); -switch (age) { +switch (věk) { case 18: - alert("Won't work"); // the result of prompt is a string, not a number + alert("Tohle nefunguje"); // výsledkem dotazu prompt je řetězec, ne číslo break; case "18": - alert("This works!"); + alert("Tohle funguje!"); break; default: - alert("Any value not equal to one above"); + alert("Jakákoli hodnota, která se nerovná žádné výše uvedené"); } ``` -Details in: . +Více v kapitole: . -## Functions +## Funkce -We covered three ways to create a function in JavaScript: +Uvedli jsme tři způsoby, jakými lze v JavaScriptu vytvořit funkci: -1. Function Declaration: the function in the main code flow +1. Deklarace funkce: funkce v hlavním kódu ```js - function sum(a, b) { - let result = a + b; + function součet(a, b) { + let výsledek = a + b; - return result; + return výsledek; } ``` -2. Function Expression: the function in the context of an expression +2. Funkční výraz: funkce v kontextu výrazu ```js - let sum = function(a, b) { - let result = a + b; + let součet = function(a, b) { + let výsledek = a + b; - return result; + return výsledek; }; ``` -3. Arrow functions: +3. Šipkové funkce: ```js - // expression on the right side - let sum = (a, b) => a + b; + // výraz na pravé straně + let součet = (a, b) => a + b; - // or multi-line syntax with { ... }, need return here: - let sum = (a, b) => { + // nebo víceřádková syntaxe s { ... }, zde potřebujeme return: + let součet = (a, b) => { // ... return a + b; } - // without arguments - let sayHi = () => alert("Hello"); + // bez argumentů + let řekniAhoj = () => alert("Ahoj"); - // with a single argument - let double = n => n * 2; + // s jediným argumentem + let dvojnásobek = n => n * 2; ``` -- 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`. +- Funkce mohou obsahovat lokální proměnné: ty jsou deklarovány uvnitř jejich těla nebo v seznamu parametrů. Tyto proměnné jsou viditelné jen uvnitř funkce. +- Parametry mohou mít nastavené výchozí hodnoty: `function součet(a = 1, b = 2) {...}`. +- Funkce vždy něco vrátí. Neobsahuje-li příkaz `return`, jejím výsledkem je `undefined`. -Details: see , . +Více v kapitolách: , . -## More to come +## Bude toho víc -That was a brief list of JavaScript features. As of now we've studied only basics. Further in the tutorial you'll find more specials and advanced features of JavaScript. +Toto byl krátký seznam prvků JavaScriptu. Dosud jsme prostudovali pouze základy. Dále v tomto tutoriálu naleznete další speciální a pokročilé prvky JavaScriptu. 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 4f50fb428..49b6ba7e2 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -1,195 +1,195 @@ -# Debugging in the browser +# Ladění v prohlížeči -Before writing more complex code, let's talk about debugging. +Než začneme psát složitější kód, pohovořme si o ladění. -[Debugging](https://en.wikipedia.org/wiki/Debugging) is the process of finding and fixing errors within a script. All modern browsers and most other environments support debugging tools -- a special UI in developer tools that makes debugging much easier. It also allows to trace the code step by step to see what exactly is going on. +[Ladění](https://cs.wikipedia.org/wiki/Ladění_(programování)) je proces hledání a opravování chyb ve skriptu. Ve všech moderních prohlížečích i ve většině ostatních prostředí jsou podporovány ladicí nástroje -- speciální uživatelské rozhraní ve vývojářských nástrojích, které ladění znatelně ulehčuje. Umožňuje také procházet kód krok za krokem, abychom viděli, co přesně se v něm děje. -We'll be using Chrome here, because it has enough features, most other browsers have a similar process. +Zde budeme používat Chrome, protože má dostatek potřebných prvků. Většina ostatních prohlížečů má podobný proces. -## The "Sources" panel +## Panel „Zdroje“ -Your Chrome version may look a little bit different, but it still should be obvious what's there. +Vaše verze Chrome může vypadat trochu odlišně, ale i tak by mělo být zřejmé, o co jde. -- Open the [example page](debugging/index.html) in Chrome. -- Turn on developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`). -- Select the `Sources` panel. +- Otevřete v Chrome [stránku s příkladem](debugging/index.html). +- Zapněte vývojářské nástroje klávesou `key:F12` (Mac: `key:Cmd+Opt+I`). +- Zvolte panel `Sources` (v české verzi `Zdroje`). -Here's what you should see if you are doing it for the first time: +Když to uděláte napoprvé, měli byste vidět toto: ![](chrome-open-sources.svg) -The toggler button opens the tab with files. +Přepínač otevírá záložku se soubory. -Let's click it and select `hello.js` in the tree view. Here's what should show up: +Klikněme na něj a a zvolme `hello.js` ve stromovém zobrazení. Mělo by se zobrazit toto: ![](chrome-tabs.svg) -The Sources panel has 3 parts: +Panel zdrojů má 3 části: -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. +1. Záložka **File Navigator** (**Navigační panel**) zobrazuje soubory HTML, JavaScript, CSS a jiné, včetně obrázků, které jsou připojeny ke stránce. Zde se mohou objevit i rozšíření Chrome. +2. Záložka **Code Editor** (**Editor kódu**) zobrazuje zdrojový kód. +3. Záložka **JavaScript Debugging** (**Ladicí nástroj**) slouží k ladění. Brzy ji prozkoumáme. -Now you could click the same toggler again to hide the resources list and give the code some space. +Nyní můžete kliknout na stejný přepínač znovu, abyste skryli seznam zdrojů a poskytli prostor kódu. -## Console +## Konzole -If we press `key:Esc`, then a console opens below. We can type commands there and press `key:Enter` to execute. +Pokud stiskneme `key:Esc`, otevře se dole konzole. Můžeme tam psát příkazy a pak stisknout `key:Enter`, aby se vykonaly. -After a statement is executed, its result is shown below. +Poté, co se příkaz vykoná, se dole zobrazí jeho výsledek. -For example, here `1+2` results in `3`, while the function call `hello("debugger")` returns nothing, so the result is `undefined`: +Například `1+2` zde vydá výsledek `3`, zatímco volání funkce `ahoj("debugger")` nevrátí nic, takže výsledek bude `undefined`: ![](chrome-sources-console.svg) -## Breakpoints +## Zarážky -Let's examine what's going on within the code of the [example page](debugging/index.html). In `hello.js`, click at line number `4`. Yes, right on the `4` digit, not on the code. +Nyní prozkoumáme, co se přesně děje v kódu na [stránce s příkladem](debugging/index.html). V `hello.js` klikněte na číslo řádku `4`. Ano, přímo na číslici `4`, ne na kód. -Congratulations! You've set a breakpoint. Please also click on the number for line `8`. +Gratulujeme! Právě jste nastavili zarážku. Klikněte prosím také na číslo řádku `8`. -It should look like this (blue is where you should click): +Mělo by to vypadat takto (modrá barva označuje, kam byste měli kliknout): ![](chrome-sources-breakpoint.svg) -A *breakpoint* is a point of code where the debugger will automatically pause the JavaScript execution. +*Zarážka* je místo, na němž ladicí nástroj automaticky pozastaví běh JavaScriptu. -While the code is paused, we can examine current variables, execute commands in the console etc. In other words, we can debug it. +Když je kód pozastaven, můžeme prozkoumávat aktuální proměnné, spouštět příkazy v konzoli a podobně. Jinými slovy, můžeme kód ladit. -We can always find a list of breakpoints in the right panel. That's useful when we have many breakpoints in various files. It allows us to: -- Quickly jump to the breakpoint in the code (by clicking on it in the right panel). -- Temporarily disable the breakpoint by unchecking it. -- Remove the breakpoint by right-clicking and selecting Remove. -- ...And so on. +V pravém panelu vždy najdeme seznam zarážek. To je užitečné, když máme mnoho zarážek v různých souborech. Seznam nám umožňuje: +- Rychle přeskočit na zarážku v kódu (když na ni klikneme v pravém panelu). +- Dočasně zarážku zakázat tím, že zrušíme její zaškrtnutí. +- Odstranit zarážku tím, že na ni klikneme pravým tlačítkem myši a zvolíme Remove (Odstranit zarážku). +- ...A tak dále. -```smart header="Conditional breakpoints" -*Right click* on the line number allows to create a *conditional* breakpoint. It only triggers when the given expression, that you should provide when you create it, is truthy. +```smart header="Podmíněné zarážky" +*Kliknutím pravým tlačítkem* na číslo řádku můžeme vytvořit *podmíněnou* zarážku. Ta se aktivuje, jen když je zadaný výraz, který byste měli uvést při jejím vytvoření, pravdivý. -That's handy when we need to stop only for a certain variable value or for certain function parameters. +To se hodí, když potřebujeme pozastavit skript jen při určité hodnotě proměnné nebo při určitých argumentech pro funkci. ``` -## The command "debugger" +## Příkaz „debugger“ -We can also pause the code by using the `debugger` command in it, like this: +Pozastavit kód můžeme také tím, že v něm použijeme příkaz `debugger`, například: ```js -function hello(name) { - let phrase = `Hello, ${name}!`; +function ahoj(jméno) { + let věta = `Ahoj, ${jméno}!`; *!* - debugger; // <-- the debugger stops here + debugger; // <-- ladicí nástroj se tady zastaví */!* - say(phrase); + řekni(věta); } ``` -Such command works only when the development tools are open, otherwise the browser ignores it. +Tento příkaz funguje jen tehdy, když jsou otevřené vývojářské nástroje, jinak jej prohlížeč ignoruje. -## Pause and look around +## Pozastavení a rozhlédnutí -In our example, `hello()` is called during the page load, so the easiest way to activate the debugger (after we've set the breakpoints) is to reload the page. So let's press `key:F5` (Windows, Linux) or `key:Cmd+R` (Mac). +V našem příkladu je během nahrání stránky zavoláno `ahoj()`, takže nejjednodušším způsobem, jak aktivovat ladicí nástroj (po nastavení zarážek), je znovu načíst stránku. Stiskneme tedy `key:F5` (Windows, Linux) nebo `key:Cmd+R` (Mac). -As the breakpoint is set, the execution pauses at the 4th line: +Když je zarážka nastavena, běh se zastaví na 4. řádku: ![](chrome-sources-debugger-pause.svg) -Please open the informational dropdowns to the right (labeled with arrows). They allow you to examine the current code state: +Rozevřete si rozbalovací menu, které se nachází vpravo (označené šipkami). Umožní vám prozkoumávat aktuální stav kódu: -1. **`Watch` -- shows current values for any expressions.** +1. **`Watch` (`Sledované výrazy`) -- zobrazuje aktuální hodnoty libovolných výrazů.** - You can click the plus `+` and input an expression. The debugger will show its value, automatically recalculating it in the process of execution. + Můžete kliknout na plus `+` a zadat výraz. Ladicí nástroj bude zobrazovat jeho hodnotu, kterou při běhu skriptu automaticky přepočítá. -2. **`Call Stack` -- shows the nested calls chain.** +2. **`Call Stack` (`Zásobník volání`) -- zobrazuje řetězec vnořených volání funkcí.** - At the current moment the debugger is inside `hello()` call, called by a script in `index.html` (no function there, so it's called "anonymous"). + V aktuálním okamžiku je ladicí nástroj uvnitř volání `ahoj()`, kterou zavolal skript v `index.html` (není zde žádná funkce, takže se nazývá „anonymní“). - If you click on a stack item (e.g. "anonymous"), the debugger jumps to the corresponding code, and all its variables can be examined as well. -3. **`Scope` -- current variables.** + Když kliknete na prvek zásobníku (např. „anonymní“), ladicí nástroj přeskočí na odpovídající kód a vy můžete prozkoumávat všechny jeho proměnné. +3. **`Scope` (`Rozsah`) -- aktuální proměnné.** - `Local` shows local function variables. You can also see their values highlighted right over the source. + `Local` (`Místní`) zobrazuje lokální funkční proměnné. Vidíte také jejich hodnoty zvýrazněné přímo nad zdrojovým kódem. - `Global` has global variables (out of any functions). + `Global` (`Globální`) obsahuje globální proměnné (mimo všechny funkce). - There's also `this` keyword there that we didn't study yet, but we'll do that soon. + Je zde také klíčové slovo `this`, které jsme zatím neprobírali, ale brzy tak učiníme. -## Tracing the execution +## Sledování běhu skriptu -Now it's time to *trace* the script. +Nyní nastal čas *krokovat* skript. -There are buttons for it at the top of the right panel. Let's engage them. +K tomu slouží tlačítka na vrchu pravého panelu. Podívejme se na ně. - -- "Resume": continue the execution, hotkey `key:F8`. -: Resumes the execution. If there are no additional breakpoints, then the execution just continues and the debugger loses control. + -- „Resume“ („Obnovit běh skriptu“): pokračuje v běhu, klávesa `key:F8`. +: Pokračuje v běhu skriptu. Nejsou-li už žádné další zarážky, skript se bude prostě vykonávat dál a ladicí nástroj nad ním ztratí kontrolu. - Here's what we can see after a click on it: + Toto uvidíme poté, co na něj klikneme: ![](chrome-sources-debugger-trace-1.svg) - The execution has resumed, reached another breakpoint inside `say()` and paused there. Take a look at the "Call Stack" at the right. It has increased by one more call. We're inside `say()` now. + Běh skriptu se obnovil, dosáhl další zarážky uvnitř `řekni()` a zastavil se tam. Podívejte se na zásobník volání („Call Stack“) vpravo. Zvětšil se o jedno volání. Nyní jsme uvnitř `řekni()`. - -- "Step": run the next command, hotkey `key:F9`. -: Run the next statement. If we click it now, `alert` will be shown. + -- „\“ („Krokovat“): spustí další příkaz, klávesa `key:F9`. +: Spustí další příkaz. Když na něj nyní klikneme, zobrazí se `alert`. - Clicking this again and again will step through all script statements one by one. + Dalším a dalším klikáním můžeme krokovat všechny příkazy skriptu jeden po druhém. - -- "Step over": run the next command, but *don't go into a function*, hotkey `key:F10`. -: Similar to the previous "Step" command, but behaves differently if the next statement is a function call (not a built-in, like `alert`, but a function of our own). + -- „Step over“ („Překročit“): spustí další příkaz, ale *nevstoupí do funkce*, klávesa `key:F10`. +: Podobá se předchozímu příkazu „Krokovat“, ale chová se jinak, jestliže dalším příkazem je volání funkce (ne vestavěné funkce jako `alert`, ale naší vlastní funkce). - If we compare them, the "Step" command goes into a nested function call and pauses the execution at its first line, while "Step over" executes the nested function call invisibly to us, skipping the function internals. + Když si je srovnáme, příkaz „Krokovat“ vstoupí do vnořeného volání funkce a pozastaví běh na jejím prvním řádku, zatímco „Překročit“ spustí vnořené volání funkce a překročí její vnitřek. - The execution is then paused immediately after that function call. + Běh se pak ihned po volání této funkce pozastaví. - That's good if we're not interested to see what happens inside the function call. + To se hodí, když nás nezajímá, co se děje uvnitř volané funkce. - -- "Step into", hotkey `key:F11`. -: That's similar to "Step", but behaves differently in case of asynchronous function calls. If you're only starting to learn JavaScript, then you can ignore the difference, as we don't have asynchronous calls yet. + -- „Step into“ („Vnořit“), klávesa `key:F11`. +: Podobá se variantě „Krokovat“, ale chová se jinak v případě asynchronního volání funkce. Jestliže se teprve začínáte učit JavaScript, můžete tento rozdíl ignorovat, jelikož asynchronní volání funkcí ještě neznáme. - For the future, just note that "Step" command ignores async actions, such as `setTimeout` (scheduled function call), that execute later. The "Step into" goes into their code, waiting for them if necessary. See [DevTools manual](https://developers.google.com/web/updates/2018/01/devtools#async) for more details. + Do budoucna si jen poznamenejme, že příkaz „Krokovat“ ignoruje asynchronní akce, například `setTimeout` (vyvolání funkce za určitou dobu), které se vykonávají později. Příkaz „Vnořit“ vstoupí do jejich kódu a počká na ně, pokud je to nutné. Podrobnosti viz [manuál k vývojářským nástrojům](https://developers.google.com/web/updates/2018/01/devtools#async). - -- "Step out": continue the execution till the end of the current function, hotkey `key:Shift+F11`. -: Continue the execution and stop it at the very last line of the current function. That's handy when we accidentally entered a nested call using , but it does not interest us, and we want to continue to its end as soon as possible. + -- „Step out“ („Vyskočit“) pokračuje v běhu až do konce vykonávané funkce, klávesa `key:Shift+F11`. +: Pokračuje v běhu a zastaví se až na posledním řádku právě vykonávané funkce. To se hodí, když jsme omylem vstoupili do volání funkce pomocí , ale ta nás nezajímá, a tak chceme co nejrychleji dospět k jejímu konci. - -- enable/disable all breakpoints. -: That button does not move the execution. Just a mass on/off for breakpoints. + -- povolí/zakáže všechny zarážky. +: Toto tlačítko nemá vliv na běh skriptu, jen hromadně zapne/vypne všechny zarážky. - -- enable/disable automatic pause in case of an error. -: When enabled, if the developer tools is open, an error during the script execution automatically pauses it. Then we can analyze variables in the debugger to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment. + -- povolí/zakáže automatické pozastavení v případě chyby. +: Když je povoleno a vývojářské nástroje jsou otevřeny, chyba během výkonu skriptu jej automaticky pozastaví. Pak můžeme v ladicím nástroji analyzovat proměnné, abychom viděli, co se pokazilo. Když tedy náš skript skončí s chybou, můžeme otevřít ladicí nástroj, povolit tuto možnost a znovu načíst stránku, abychom viděli, kde spadl a jaký je v tom okamžiku jeho kontext. -```smart header="Continue to here" -Right click on a line of code opens the context menu with a great option called "Continue to here". +```smart header="Pokračovat až sem" +Po kliknutí pravým tlačítkem myši na řádek kódu se otevře kontextové menu s výtečnou možností „Continue to here“ („Pokračovat sem“). -That's handy when we want to move multiple steps forward to the line, but we're too lazy to set a breakpoint. +To se hodí, když chceme provést větší množství kroků až k tomuto řádku, ale nechce se nám nastavovat zarážku. ``` -## Logging +## Logování -To output something to console from our code, there's `console.log` function. +Chceme-li vypsat něco z našeho kódu na konzoli, máme k tomu funkci `console.log`. -For instance, this outputs values from `0` to `4` to console: +Například tento kód vypíše na konzoli hodnoty od `0` do `4`: ```js run -// open console to see +// otevřete si konzoli a uvidíte for (let i = 0; i < 5; i++) { - console.log("value,", i); + console.log("hodnota,", i); } ``` -Regular users don't see that output, it is in the console. To see it, either open the Console panel of developer tools or press `key:Esc` while in another panel: that opens the console at the bottom. +Běžní uživatelé tento výstup neuvidí, vypíše se na konzoli. Abyste jej viděli, otevřete panel Console (Konzole) vývojářských nástrojů nebo stiskněte `key:Esc`, když jste v jiném panelu: tím se dole otevře konzole. -If we have enough logging in our code, then we can see what's going on from the records, without the debugger. +Máme-li v kódu dostatek logování, uvidíme z těchto záznamů, co se děje, i bez ladicího nástroje. -## Summary +## Shrnutí -As we can see, there are three main ways to pause a script: -1. A breakpoint. -2. The `debugger` statements. -3. An error (if dev tools are open and the button is "on"). +Zjistili jsme, že existují tři hlavní způsoby, jak pozastavit skript: +1. Zarážkou. +2. Pomocí příkazu `debugger`. +3. Když se vyskytne chyba (jsou-li vývojářské nástroje otevřené a přepínač je „zapnutý“). -When paused, we can debug: examine variables and trace the code to see where the execution goes wrong. +Když je skript pozastaven, můžeme jej ladit: prozkoumávat proměnné a krokovat kód, abychom viděli, kde a co se při jeho vykonávání pokazilo. -There are many more options in developer tools than covered here. The full manual is at . +Vývojářské nástroje obsahují mnohem více možností, než jsme zde uvedli. Kompletní manuál najdete na . -The information from this chapter is enough to begin debugging, but later, especially if you do a lot of browser stuff, please go there and look through more advanced capabilities of developer tools. +Informace o ladění, zmíněné v této kapitole, vám do začátku postačí, ale později, zvlášť když budete hodně pracovat s prohlížeči, se tam prosím podívejte a prostudujte si pokročilejší možnosti vývojářských nástrojů. -Oh, and also you can click at various places of dev tools and just see what's showing up. That's probably the fastest route to learn dev tools. Don't forget about the right click and context menus! +A také můžete klikat na různá místa vývojářských nástrojů a zkoušet, co se objeví. To je asi nejrychlejší způsob, jak se je naučit. Nezapomínejte na pravé tlačítko myši a kontextová menu! diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg index 5fc6dce3a..a0e9057c7 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg @@ -1 +1,34 @@ -open sources \ No newline at end of file + + + + + + + + + + + + + + Otevřete záložku se zdrojovými soubory + + + + + 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 4facc8b29..2e6870983 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 @@ -1,48 +1,48 @@ -You could note the following: +Měli byste si všimnout následujícího: ```js no-beautify -function pow(x,n) // <- no space between arguments -{ // <- figure bracket on a separate line - let result=1; // <- no spaces before or after = - for(let i=0;i -Now let's discuss the rules and reasons for them in detail. +Nyní si tato pravidla a jejich důvody probereme podrobně. -```warn header="There are no \"you must\" rules" -Nothing is set in stone here. These are style preferences, not religious dogmas. +```warn header="Toto nejsou žádná povinná pravidla" +Nic tady není zasazeno do kamene. Toto jsou stylové preference, ne náboženská dogmata. ``` -### Curly Braces +### Složené závorky -In most JavaScript projects curly braces are written in "Egyptian" style with the opening brace on the same line as the corresponding keyword -- not on a new line. There should also be a space before the opening bracket, like this: +Ve většině JavaScriptových projektů se složené závorky píší „egyptským“ stylem, kdy je levá závorka na stejném řádku jako odpovídající klíčové slovo -- ne na novém řádku. Před levou závorkou by také měla být mezera, jako zde: ```js -if (condition) { - // do this - // ...and that - // ...and that +if (podmínka) { + // dělej tohle + // ...a tohle + // ...a tohle } ``` -A single-line construct, such as `if (condition) doSomething()`, is an important edge case. Should we use braces at all? +Důležitým krajním případem je jednořádková konstrukce, například `if (podmínka) dělejNěco()`. Měli bychom vůbec použít závorky? -Here are the annotated variants so you can judge their readability for yourself: +Uvádíme jednotlivé varianty s komentářem, takže můžete sami posoudit jejich čitelnost: -1. 😠 Beginners sometimes do that. Bad! Curly braces are not needed: +1. 😠 Začátečníci někdy píší takto. Špatně! Složené závorky nejsou zapotřebí: ```js - if (n < 0) *!*{*/!*alert(`Power ${n} is not supported`);*!*}*/!* + if (n < 0) *!*{*/!*alert(`${n}-tá mocnina není podporována`);*!*}*/!* ``` -2. 😠 Split to a separate line without braces. Never do that, easy to make an error when adding new lines: +2. 😠 Umístění na samostatný řádek bez složených závorek. Tohle nikdy nedělejte, jelikož snadno uděláte chybu, když budete přidávat další řádky: ```js if (n < 0) - alert(`Power ${n} is not supported`); + alert(`${n}-tá mocnina není podporována`); ``` -3. 😏 One line without braces - acceptable, if it's short: +3. 😏 Jeden řádek bez složených závorek - to je přijatelné, pokud je krátký: ```js - if (n < 0) alert(`Power ${n} is not supported`); + if (n < 0) alert(`${n}-tá mocnina není podporována`); ``` -4. 😃 The best variant: +4. 😃 Nejlepší varianta: ```js if (n < 0) { - alert(`Power ${n} is not supported`); + alert(`${n}-tá mocnina není podporována`); } ``` -For a very brief code, one line is allowed, e.g. `if (cond) return null`. But a code block (the last variant) is usually more readable. +Pro velmi krátký kód je dovolen jeden řádek, např. `if (podmínka) return null`. Ale kódový blok (poslední uvedená varianta) bývá obvykle čitelnější. -### Line Length +### Délka řádku -No one likes to read a long horizontal line of code. It's best practice to split them. +Nikdo není rád, když musí číst dlouhý vodorovný řádek kódu. Lepší přístup je rozdělit jej. -For example: +Například: ```js -// backtick quotes ` allow to split the string into multiple lines -let str = ` - 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. +// zpětné uvozovky ` umožňují rozdělit řádek na více řádků +let řetězec = ` + TC39 v ECMA International je skupina JavaScriptových vývojářů, + implementátorů, akademiků a dalších, kteří spolupracují s komunitou, + aby udržovali a rozvíjeli definici JavaScriptu. `; ``` -And, for `if` statements: +A pro příkazy `if`: ```js if ( id === 123 && - moonPhase === 'Waning Gibbous' && - zodiacSign === 'Libra' + měsíčníFáze === 'Ubývání' && + znameníZvěrokruhu === 'Váhy' ) { - letTheSorceryBegin(); + začniKouzlo(); } ``` -The maximum line length should be agreed upon at the team-level. It's usually 80 or 120 characters. +Na maximální délce řádku by se měl dohodnout celý tým. Obvykle bývá 80 nebo 120 znaků. -### Indents +### Odsazení -There are two types of indents: +Existují dva druhy odsazení: -- **Horizontal indents: 2 or 4 spaces.** +- **Vodorovné odsazení: 2 nebo 4 mezery.** - A horizontal indentation is made using either 2 or 4 spaces or the horizontal tab symbol (key `key:Tab`). Which one to choose is an old holy war. Spaces are more common nowadays. + Vodorovné odsazení se dělá pomocí 2 nebo 4 mezer nebo pomocí vodorovného symbolu tabulátoru (klávesa `key:Tab`). O tom, které zvolit, se už dlouho vedou svaté války. V dnešní době se více používají mezery. - One advantage of spaces over tabs is that spaces allow more flexible configurations of indents than the tab symbol. + Výhodou mezer oproti tabulátorům je, že mezery umožňují konfigurovat odsazení flexibilněji než symbol tabulátoru. - For instance, we can align the parameters with the opening bracket, like this: + Můžeme například zarovnat parametry k levé závorce takto: ```js no-beautify - show(parameters, - aligned, // 5 spaces padding at the left - one, - after, - another + zobraz(parametry, + zarovnané, // vlevo je odsazení 7 mezer + jeden, + za, + druhým ) { // ... } ``` -- **Vertical indents: empty lines for splitting code into logical blocks.** +- **Svislé odsazení: prázdné řádky pro rozdělení kódu do logických bloků.** - Even a single function can often be divided into logical blocks. In the example below, the initialization of variables, the main loop and returning the result are split vertically: + I jediná funkce může být často rozdělena do logických bloků. V níže uvedeném příkladu jsou svisle odděleny inicializace proměnných, hlavní smyčka a vrácení výsledku: ```js - function pow(x, n) { - let result = 1; + function mocnina(x, n) { + let výsledek = 1; // <-- for (let i = 0; i < n; i++) { - result *= x; + výsledek *= x; } // <-- - return result; + return výsledek; } ``` - Insert an extra newline where it helps to make the code more readable. There should not be more than nine lines of code without a vertical indentation. + Vkládejte prázdný nový řádek všude, kde pomáhá učinit kód čitelnějším. V kódu by nemělo být více než devět řádků za sebou bez svislého odsazení. -### Semicolons +### Středníky -A semicolon should be present after each statement, even if it could possibly be skipped. +Středník by měl být uveden za každým příkazem, i kdyby mohl být vynechán. -There are languages where a semicolon is truly optional and it is rarely used. In JavaScript, though, there are cases where a line break is not interpreted as a semicolon, leaving the code vulnerable to errors. See more about that in the chapter . +Existují jazyky, v nichž je středník zcela dobrovolný a používá se jen zřídka. V JavaScriptu však existují případy, kdy se zlom řádku neinterpretuje jako středník, čímž se kód stává náchylnějším k chybám. Více o tom se píše v kapitole . -If you're an experienced JavaScript programmer, you may choose a no-semicolon code style like [StandardJS](https://standardjs.com/). Otherwise, it's best to use semicolons to avoid possible pitfalls. The majority of developers put semicolons. +Jste-li zkušený programátor v JavaScriptu, můžete se rozhodnout pro bezstředníkový kódovací styl, např. [StandardJS](https://standardjs.com/). Jinak je však lepší středníky používat, abyste se vyhnuli možným úskalím. Většina vývojářů středníky uvádí. -### Nesting Levels +### Úrovně vnoření -Try to avoid nesting code too many levels deep. +Snažte se vyhnout příliš mnoha úrovním vnoření. -For example, in the loop, it's sometimes a good idea to use the [`continue`](info:while-for#continue) directive to avoid extra nesting. +Například v cyklu je někdy dobrý nápad použít direktivu [`continue`](info:while-for#continue), abychom se vyhnuli dalšímu vnoření. -For example, instead of adding a nested `if` conditional like this: +Například místo použití vnořené podmínky `if` tímto způsobem: ```js for (let i = 0; i < 10; i++) { - if (cond) { - ... // <- one more nesting level + if (podmínka) { + ... // <- o jednu úroveň vnoření víc } } ``` -We can write: +můžeme napsat: ```js for (let i = 0; i < 10; i++) { - if (!cond) *!*continue*/!*; - ... // <- no extra nesting level + if (!podmínka) *!*continue*/!*; + ... // <- žádná další úroveň vnoření } ``` -A similar thing can be done with `if/else` and `return`. +Podobně můžeme postupovat s `if/else` a `return`. -For example, two constructs below are identical. +Například dvě níže uvedené konstrukce jsou identické. -Option 1: +Možnost 1: ```js -function pow(x, n) { +function mocnina(x, n) { if (n < 0) { - alert("Negative 'n' not supported"); + alert("Záporné 'n' není podporováno"); } else { - let result = 1; + let výsledek = 1; for (let i = 0; i < n; i++) { - result *= x; + výsledek *= x; } - return result; + return výsledek; } } ``` -Option 2: +Možnost 2: ```js -function pow(x, n) { +function mocnina(x, n) { if (n < 0) { - alert("Negative 'n' not supported"); + alert("Záporné 'n' není podporováno"); return; } - let result = 1; + let výsledek = 1; for (let i = 0; i < n; i++) { - result *= x; + výsledek *= x; } - return result; + return výsledek; } ``` -The second one is more readable because the "special case" of `n < 0` is handled early on. Once the check is done we can move on to the "main" code flow without the need for additional nesting. +Druhá konstrukce je čitelnější, protože „zvláštní případ“ `n < 0` je ošetřen hned na začátku. Když je kontrola provedena, můžeme se přesunout k „hlavnímu“ toku kódu, aniž bychom potřebovali další vnoření. -## Function Placement +## Umístění funkcí -If you are writing several "helper" functions and the code that uses them, there are three ways to organize the functions. +Jestliže píšeme několik „pomocných“ funkcí a kód, který je používá, tak máme tři možnosti, jak funkce organizovat. -1. Declare the functions *above* the code that uses them: +1. Deklarovat funkce *před* kódem, který je používá: ```js - // *!*function declarations*/!* - function createElement() { + // *!*deklarace funkcí*/!* + function vytvořPrvek() { ... } - function setHandler(elem) { + function nastavHandler(prvek) { ... } - function walkAround() { + function choďKolem() { ... } - // *!*the code which uses them*/!* - let elem = createElement(); - setHandler(elem); - walkAround(); + // *!*kód, který je používá*/!* + let prvek = vytvořPrvek(); + nastavHandler(prvek); + choďKolem(); ``` -2. Code first, then functions +2. Nejprve kód, pak funkce: ```js - // *!*the code which uses the functions*/!* - let elem = createElement(); - setHandler(elem); - walkAround(); + // *!*kód, který používá funkce*/!* + let prvek = vytvořPrvek(); + nastavHandler(prvek); + choďKolem(); - // --- *!*helper functions*/!* --- - function createElement() { + // --- *!*pomocné funkce*/!* --- + function vytvořPrvek() { ... } - function setHandler(elem) { + function nastavHandler(prvek) { ... } - function walkAround() { + function choďKolem() { ... } ``` -3. Mixed: a function is declared where it's first used. +3. Směs: funkce je deklarována tam, kde je poprvé použita. -Most of time, the second variant is preferred. +Ve většině případů se dává přednost druhé variantě. -That's because when reading code, we first want to know *what it does*. If the code goes first, then it becomes clear from the start. Then, maybe we won't need to read the functions at all, especially if their names are descriptive of what they actually do. +Je to proto, že při čtení kódu chceme nejdříve vědět, *co kód dělá*. Pokud je kód uveden jako první, bude to zřejmé od začátku. Pak možná nebudeme muset funkce vůbec číst, zvláště pokud je z jejich názvů dostatečně jasné, co dělají. -## Style Guides +## Styloví průvodci -A style guide contains general rules about "how to write" code, e.g. which quotes to use, how many spaces to indent, the maximal line length, etc. A lot of minor things. +Stylový průvodce obsahuje obecná pravidla o tom, „jak psát“ kód, tj. které uvozovky používat, o kolik mezer odsazovat, maximální délku řádku atd. Mnoho vedlejších věcí. -When all members of a team use the same style guide, the code looks uniform, regardless of which team member wrote it. +Když všichni členové týmu používají stejného stylového průvodce, bude kód vypadat jednotně bez ohledu na to, který člen týmu ho napsal. -Of course, a team can always write their own style guide, but usually there's no need to. There are many existing guides to choose from. +Samozřejmě si tým vždy může napsat svého vlastního stylového průvodce, ale obvykle to není zapotřebí. Existuje mnoho průvodců, z nichž si lze vybrat. -Some popular choices: +Některé oblíbené možnosti: -- [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html) -- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) +- [JavaScriptový stylový průvodce Google](https://google.github.io/styleguide/jsguide.html) +- [JavaScriptový stylový průvodce Airbnb](https://github.com/airbnb/javascript) - [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) - [StandardJS](https://standardjs.com/) -- (plus many more) +- (a mnoho dalších) -If you're a novice developer, start with the cheat sheet at the beginning of this chapter. Then you can browse other style guides to pick up more ideas and decide which one you like best. +Pokud jste začínající vývojář, začněte podle příkladu na začátku této kapitoly. Pak si můžete projít jiné stylové průvodce, abyste získali další nápady a rozhodli se, který se vám líbí nejvíc. -## Automated Linters +## Automatické lintery -Linters are tools that can automatically check the style of your code and make improving suggestions. +Lintery jsou nástroje, které umějí automaticky zkontrolovat styl vašeho kódu a navrhnout zlepšení. -The great thing about them is that style-checking can also find some bugs, like typos in variable or function names. Because of this feature, using a linter is recommended even if you don't want to stick to one particular "code style". +Výborné na nich je, že tato kontrola stylu umí najít i některé chyby, například překlepy v názvech proměnných nebo funkcí. Pro tuto jejich vlastnost se používání linteru doporučuje, i když se nechcete vázat k jednomu konkrétnímu „stylu zápisu“. -Here are some well-known linting tools: +Zde jsou některé dobře známé lintery: -- [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. +- [JSLint](https://www.jslint.com/) -- jeden z prvních linterů. +- [JSHint](https://www.jshint.com/) -- více nastavení než JSLint. +- [ESLint](https://eslint.org/) -- pravděpodobně nejnovější. -All of them can do the job. The author uses [ESLint](https://eslint.org/). +Všechny tuto práci odvedou. Autor používá [ESLint](http://eslint.org/). -Most linters are integrated with many popular editors: just enable the plugin in the editor and configure the style. +Většina linterů je integrována s mnoha oblíbenými editory: jednoduše povolte plugin v editoru a nakonfigurujte styl. -For instance, for ESLint you should do the following: +Například pro ESLint byste měli udělat následující: -1. Install [Node.js](https://nodejs.org/). -2. Install ESLint with the command `npm install -g eslint` (npm is a JavaScript package installer). -3. Create a config file named `.eslintrc` in the root of your JavaScript project (in the folder that contains all your files). -4. Install/enable the plugin for your editor that integrates with ESLint. The majority of editors have one. +1. Nainstalujte [Node.js](https://nodejs.org/). +2. Nainstalujte ESLint příkazem `npm install -g eslint` (npm je instalátor JavaScriptových balíků). +3. Vytvořte konfigurační soubor s názvem `.eslintrc` v kořenové složce vašeho JavaScriptového projektu (ve složce, která obsahuje všechny vaše soubory). +4. Nainstalujte/povolte ve svém editoru plugin, který jej spojí s ESLintem. Většina editorů jej má. -Here's an example of an `.eslintrc` file: +Uvádíme příklad souboru `.eslintrc`: ```js { @@ -333,16 +333,16 @@ Here's an example of an `.eslintrc` file: } ``` -Here the directive `"extends"` denotes that the configuration is based on the "eslint:recommended" set of settings. After that, we specify our own. +Zde direktiva `"extends"` oznamuje, že konfigurace je založena na sadě nastavení „eslint:recommended“. Poté si specifikujeme vlastní. -It is also possible to download style rule sets from the web and extend them instead. See for more details about installation. +Je také možné si místo toho stáhnout sady stylových pravidel z webu a pak je rozšířit. Pro podrobnosti ohledně instalace viz . -Also certain IDEs have built-in linting, which is convenient but not as customizable as ESLint. +Také některá IDE obsahují zabudovaný linting, který je vyhovující, ale ne tak nastavitelný jako ESLint. -## Summary +## Shrnutí -All syntax rules described in this chapter (and in the style guides referenced) aim to increase the readability of your code. All of them are debatable. +Všechna syntaktická pravidla uvedená v této kapitole (a v odkazovaných stylových průvodcích) směřují ke zvýšení čitelnosti vašeho kódu. Všechna jsou diskutovatelná. -When we think about writing "better" code, the questions we should ask ourselves are: "What makes the code more readable and easier to understand?" and "What can help us avoid errors?" These are the main things to keep in mind when choosing and debating code styles. +Když uvažujeme o psaní „lepšího“ kódu, měli bychom si klást otázky „Co učiní kód čitelnějším a snadnějším k porozumění?“ a „Co nám pomůže vyvarovat se chyb?“. To je to hlavní, co bychom měli mít na paměti, když si vybíráme styly kódování a diskutujeme o nich. -Reading popular style guides will allow you to keep up to date with the latest ideas about code style trends and best practices. +Čtení oblíbených stylových průvodců nám umožní držet krok s nejnovějšími myšlenkami o trendech stylů zápisu programu a nejlepších praktikách. diff --git a/1-js/03-code-quality/02-coding-style/code-style.svg b/1-js/03-code-quality/02-coding-style/code-style.svg index 739d9f1ed..4a5acdb09 100644 --- a/1-js/03-code-quality/02-coding-style/code-style.svg +++ b/1-js/03-code-quality/02-coding-style/code-style.svg @@ -1 +1 @@ -2No space between the function name and parentheses between the parentheses and the parameterIndentation 2 spacesA space after for/if/while…} else { without a line breakSpaces around a nested callAn empty line between logical blocksLines are not very longA semicolon ; is mandatorySpaces around operatorsCurly brace { on the same line, after a spaceA space between argumentsA space between parameters \ No newline at end of file +2Bez mezer mezi názvem funkcí a závorkou mezi závorkou a parametremOdsazení 2 mezeryMezera za for/if/while…} else { bez konce řádkuMezery kolem vnořeného voláníPrázdný řádek mezi logickými blokyŘádky nejsou příliš dlouhéStředník ; je povinnýMezery kolem operátorůSložená závorka { na stejném řádku za mezerouMezera mezi argumentyMezera mezi parametry \ No newline at end of file diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index af3a06c80..2225c5243 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -1,40 +1,40 @@ -# Comments +# Komentáře -As we know from the chapter , comments can be single-line: starting with `//` and multiline: `/* ... */`. +Jak víme z kapitoly , komentáře mohou být jednořádkové, které začínají `//`, a víceřádkové `/* ... */`. -We normally use them to describe how and why the code works. +Obvykle je používáme k popisu, jak a proč kód funguje. -At first sight, commenting might be obvious, but novices in programming often use them wrongly. +Na první pohled může být komentování samozřejmé, ale začínající programátoři je často používají nesprávně. -## Bad comments +## Špatné komentáře -Novices tend to use comments to explain "what is going on in the code". Like this: +Začátečníci tíhnou k psaní komentářů, které vysvětlují, „co se v kódu děje“. Například: ```js -// This code will do this thing (...) and that thing (...) -// ...and who knows what else... -very; -complex; -code; +// Tento kód dělá toto (...) a toto (...) +// ...a kdo ví, co jiného... +velmi; +složitý; +kód; ``` -But in good code, the amount of such "explanatory" comments should be minimal. Seriously, the code should be easy to understand without them. +V dobrém kódu by však množství takových „vysvětlujících“ komentářů mělo být minimální. Kód by měl být srozumitelný i bez nich. -There's a great rule about that: "if the code is so unclear that it requires a comment, then maybe it should be rewritten instead". +Platí jedno zlaté pravidlo: „Je-li kód natolik nejasný, že vyžaduje komentář, možná by měl být přepsán.“ -### Recipe: factor out functions +### Recept: extrahujte funkce -Sometimes it's beneficial to replace a code piece with a function, like here: +Někdy se vyplatí nahradit kus kódu funkcí, například: ```js -function showPrimes(n) { - nextPrime: +function zobrazPrvočísla(n) { + dalšíPrvočíslo: for (let i = 2; i < n; i++) { *!* - // check if i is a prime number + // ověří, zda i je prvočíslo for (let j = 2; j < i; j++) { - if (i % j == 0) continue nextPrime; + if (i % j == 0) continue dalšíPrvočíslo; } */!* @@ -43,20 +43,20 @@ function showPrimes(n) { } ``` -The better variant, with a factored out function `isPrime`: +Lepší varianta s vyjmutou funkcí `jePrvočíslo`: ```js -function showPrimes(n) { +function zobrazPrvočísla(n) { for (let i = 2; i < n; i++) { - *!*if (!isPrime(i)) continue;*/!* + *!*if (!jePrvočíslo(i)) continue;*/!* alert(i); } } -function isPrime(n) { +function jePrvočíslo(n) { for (let i = 2; i < n; i++) { if (n % i == 0) return false; } @@ -65,116 +65,116 @@ function isPrime(n) { } ``` -Now we can understand the code easily. The function itself becomes the comment. Such code is called *self-descriptive*. +Nyní kódu snadno porozumíme. Funkce sama o sobě se stává komentářem. Takový kód se nazývá *sebepopisující*. -### Recipe: create functions +### Recept: vytvářejte funkce -And if we have a long "code sheet" like this: +A máme-li dlouhý „kus kódu“, jako třeba: ```js -// here we add whiskey +// zde přidáme whisky for(let i = 0; i < 10; i++) { - let drop = getWhiskey(); - smell(drop); - add(drop, glass); + let kapka = vezmiWhisky(); + přičichni(kapka); + přidej(kapka, sklenice); } -// here we add juice +// zde přidáme džus for(let t = 0; t < 3; t++) { - let tomato = getTomato(); - examine(tomato); - let juice = press(tomato); - add(juice, glass); + let rajče = vezmiRajče(); + prozkoumej(rajče); + let džus = rozmačkej(rajče); + přidej(džus, sklenice); } // ... ``` -Then it might be a better variant to refactor it into functions like: +pak může být lepší varianta jej přepsat do funkcí takto: ```js -addWhiskey(glass); -addJuice(glass); +přidejWhisky(sklenice); +přidejDžus(sklenice); -function addWhiskey(container) { +function přidejWhisky(nádoba) { for(let i = 0; i < 10; i++) { - let drop = getWhiskey(); + let kapka = vezmiWhisky(); //... } } -function addJuice(container) { +function přidejDžus(nádoba) { for(let t = 0; t < 3; t++) { - let tomato = getTomato(); + let rajče = vezmiRajče(); //... } } ``` -Once again, functions themselves tell what's going on. There's nothing to comment. And also the code structure is better when split. It's clear what every function does, what it takes and what it returns. +Opět funkce samy o sobě říkají, co se děje. Není tady co komentovat. I struktura kódu je lepší, když je kód rozdělený. Je jasné, co která funkce provádí, co přijímá a co vrací. -In reality, we can't totally avoid "explanatory" comments. There are complex algorithms. And there are smart "tweaks" for purposes of optimization. But generally we should try to keep the code simple and self-descriptive. +V realitě se nemůžeme „vysvětlujícím“ komentářům úplně vyhnout. Existují složité algoritmy a existují chytrá „vylepšení“ pro účely optimalizace. Obecně bychom se však měli snažit udržet kód jednoduchý a sebepopisující. -## Good comments +## Dobré komentáře -So, explanatory comments are usually bad. Which comments are good? +Vysvětlující komentáře jsou tedy obecně špatné. Jaké komentáře jsou dobré? -Describe the architecture -: Provide a high-level overview of components, how they interact, what's the control flow in various situations... In short -- the bird's eye view of the code. There's a special language [UML](http://wikipedia.org/wiki/Unified_Modeling_Language) to build high-level architecture diagrams explaining the code. Definitely worth studying. +Popis architektury +: Poskytují vysokoúrovňový pohled na komponenty, jak spolu komunikují, jaký je řídicí tok v různých situacích... Stručně řečeno -- pohled na kód z ptačí perspektivy. Existuje i speciální jazyk [UML](http://wikipedia.org/wiki/Unified_Modeling_Language) určený k tvorbě diagramů na vysoké úrovni architektury, které popisují kód. Rozhodně má smysl si jej prostudovat. -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. +Dokumentace parametrů a použití funkcí +: Existuje speciální syntaxe [JSDoc](http://en.wikipedia.org/wiki/JSDoc) pro dokumentaci funkcí: použití, parametry, návratová hodnota. -For instance: +Například: ```js /** - * Returns x raised to the n-th power. + * Vrátí x umocněné na n-tou. * - * @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. + * @param {number} x Číslo, které se má umocnit. + * @param {number} n Exponent, musí být přirozené číslo. + * @return {number} x umocněné na n-tou. */ -function pow(x, n) { +function mocnina(x, n) { ... } ``` -Such comments allow us to understand the purpose of the function and use it the right way without looking in its code. +Takové komentáře nám umožňují porozumět účelu funkce a používat ji správně, aniž bychom se dívali na její kód. -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. +Mimochodem i mnoho editorů, například [WebStorm](https://www.jetbrains.com/webstorm/), jim dokáže porozumět a používá je k našeptávání a automatické kontrole kódu. -Also, there are tools like [JSDoc 3](https://github.com/jsdoc/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at . +Existují i nástroje jako [JSDoc 3](https://github.com/jsdoc/jsdoc), které umějí z těchto komentářů vygenerovat dokumentaci v HTML. Více informací o JSDoc si můžete přečíst na . -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. +Proč se tato úloha řeší zrovna takhle? +: To, co je psáno, je důležité. Ale to, co *není* psáno, může být ještě důležitější k pochopení toho, o co jde. Proč je tato úloha řešena právě tímto způsobem? Kód nám odpověď nedává. - If there are many ways to solve the task, why this one? Especially when it's not the most obvious one. + Existuje-li mnoho způsobů, jak tuto úlohu řešit, proč zrovna tento? Zvláště pokud to není zrovna nejzřejmější způsob. - Without such comments the following situation is possible: - 1. You (or your colleague) open the code written some time ago, and see that it's "suboptimal". - 2. You think: "How stupid I was then, and how much smarter I'm now", and rewrite using the "more obvious and correct" variant. - 3. ...The urge to rewrite was good. But in the process you see that the "more obvious" solution is actually lacking. You even dimly remember why, because you already tried it long ago. You revert to the correct variant, but the time was wasted. + Bez takových komentářů může nastat následující situace: + 1. Vy (nebo váš kolega) otevřete kód, napsaný před nějakou dobou, a vidíte, že je „neoptimální“. + 2. Pomyslíte si: „To jsem byl tehdy ale hloupý, teď jsem o hodně chytřejší“, a přepíšete ho na „jasnější a korektnější“ variantu. + 3. ...Potřeba přepsat kód byla dobrá. Ale po spuštění uvidíte, že „jasnější“ řešení je ve skutečnosti horší. Matně si vzpomenete proč, jelikož jste to kdysi už zkoušeli. Vrátíte kód na korektní variantu, ale byla to ztráta času. - Comments that explain the solution are very important. They help to continue development the right way. + Komentáře, které vysvětlují řešení, jsou velmi důležité, protože nám pomáhají vyvíjet správnou cestou. -Any subtle features of the code? Where they are used? -: If the code has anything subtle and counter-intuitive, it's definitely worth commenting. +Jsou v kódu nějaké finty? Proč jsou použity? +: Obsahuje-li kód cokoli promyšleného a neintuitivního, má rozhodně smysl jej komentovat. -## Summary +## Shrnutí -An important sign of a good developer is comments: their presence and even their absence. +Komentáře jsou důležitým znakem dobrého vývojáře: jejich přítomnost, ale i jejich absence. -Good comments allow us to maintain the code well, come back to it after a delay and use it more effectively. +Dobré komentáře nám umožňují kód dobře udržovat, později se k němu vracet a efektivněji jej využívat. -**Comment this:** +**Komentujte toto:** -- Overall architecture, high-level view. -- Function usage. -- Important solutions, especially when not immediately obvious. +- Celkovou architekturu, pohled z vysoké úrovně. +- Používání funkcí. +- Důležitá řešení, zvláště pokud nejsou jasná na první pohled. -**Avoid comments:** +**Zdržte se komentářů:** -- That tell "how code works" and "what it does". -- Put them in only if it's impossible to make the code so simple and self-descriptive that it doesn't require them. +- Které vysvětlují „jak kód funguje“ a „co dělá“. +- Vkládejte je jen tehdy, když není možné udržet kód natolik jednoduchý a sebepopisující, že je nevyžaduje. -Comments are also used for auto-documenting tools like JSDoc3: they read them and generate HTML-docs (or docs in another format). +Komentáře se také používají pro nástroje automatické dokumentace jako JSDoc3, které je načtou a vygenerují z nich dokumentaci v HTML (nebo v jakémkoli jiném formátu). 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 96fdf4143..20901b22a 100644 --- a/1-js/03-code-quality/04-ninja-code/article.md +++ b/1-js/03-code-quality/04-ninja-code/article.md @@ -1,240 +1,239 @@ -# Ninja code +# Ninjův kód -```quote author="Confucius (Analects)" -Learning without thought is labor lost; thought without learning is perilous. +```quote author="Konfucius (Hovory)" +Učení bez myšlení je zbytečná práce; myšlení bez učení je nebezpečné. ``` -Programmer ninjas of the past used these tricks to sharpen the mind of code maintainers. +Tyto triky používali programátorští ninjové v minulosti, aby zostřili mysl údržbářů kódu. -Code review gurus look for them in test tasks. +Zasvěcení recenzenti kódu je hledají v testovacích úlohách. -Novice developers sometimes use them even better than programmer ninjas. +Začínající vývojáři je někdy používají ještě lépe než programátorští ninjové. -Read them carefully and find out who you are -- a ninja, a novice, or maybe a code reviewer? +Pozorně si je přečtěte a zjistěte, kdo jste -- ninja, začátečník, nebo snad recenzent kódu? -```warn header="Irony detected" -Many try to follow ninja paths. Few succeed. +```warn header="Detekována ironie" +Mnozí lidé se snaží následovat cesty ninjů. Jen málokteří uspějí. ``` -## Brevity is the soul of wit +## Stručnost je duší důvtipu -Make the code as short as possible. Show how smart you are. +Snažte se napsat kód co nejkratší. Ukažte všem, jak jste chytří. -Let subtle language features guide you. +Nechte se vést jemnými prvky jazyka. -For instance, take a look at this ternary operator `'?'`: +Podívejte se například na tento ternární operátor `'?'`: ```js -// taken from a well-known javascript library +// převzato z dobře známé JavaScriptové knihovny i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ``` -Cool, right? If you write like that, a developer who comes across this line and tries to understand what is the value of `i` is going to have a merry time. Then come to you, seeking for an answer. +Hezké, ne? Budete-li takhle psát, zažije vývojář, který narazí na tento řádek a bude se snažit porozumět, jaká je hodnota `i`, veselé chvíle. Pak přijde za vámi a bude žádat odpověď. -Tell them that shorter is always better. Initiate them into the paths of ninja. +Řekněte mu, že co je kratší, to je vždy lepší. Zasvěťte ho do cest ninjů. -## One-letter variables +## Jednopísmenné proměnné -```quote author="Laozi (Tao Te Ching)" -The Dao hides in wordlessness. Only the Dao is well begun and well -completed. +```quote author="Laozi (Tao te ťing)" +Dao spočívá v bezeslovnosti. Jedině Dao správně začíná a správně končí. ``` -Another way to code shorter is to use single-letter variable names everywhere. Like `a`, `b` or `c`. +Další způsob, jak zkrátit kód, je všude používat jednopísmenné názvy proměnných, např. `a`, `b` nebo `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. +Krátká proměnná se v kódu ztratí jako opravdový ninja v lese. Nikdo ji nebude moci najít pomocí funkce hledání v editoru. A i kdyby ano, nedokáže „rozšifrovat“, co názvy `a` nebo `b` znamenají. -...But there's an exception. A real ninja will never use `i` as the counter in a `"for"` loop. Anywhere, but not here. Look around, there are many more exotic letters. For instance, `x` or `y`. +...Je tady však jedna výjimka. Opravdový ninja nikdy nepoužije `i` jako čítač v cyklu `„for“`. Všude, jen ne tam. Jen se rozhlédněte kolem, existuje spousta exotičtějších písmen, např. `x` nebo `y`. -An exotic variable as a loop counter is especially cool if the loop body takes 1-2 pages (make it longer if you can). Then if someone looks deep inside the loop, they won't be able to quickly figure out that the variable named `x` is the loop counter. +Exotická proměnná jako čítač v cyklu je obzvláště půvabná, jestliže tělo cyklu zabírá 1-2 stránky (můžete-li, napište je co nejdelší). Když se potom někdo podívá hlouběji do cyklu, nebude schopen rychle přijít na to, že proměnná `x` je čítač cyklu. -## Use abbreviations +## Používejte zkratky -If the team rules forbid the use of one-letter and vague names -- shorten them, make abbreviations. +Jestliže týmová pravidla zakazují používat jednopísmenné a vágní názvy, zkracujte je. Vytvářejte zkratky. -Like this: +Třeba takto: -- `list` -> `lst`. -- `userAgent` -> `ua`. -- `browser` -> `brsr`. -- ...etc +- `seznam` -> `szn`. +- `uživatelskýAgent` -> `ua`. +- `prohlížeč` -> `prhl`. +- ...atd. -Only the one with truly good intuition will be able to understand such names. Try to shorten everything. Only a worthy person should be able to uphold the development of your code. +Jedině osoba s opravdu dobrou intuicí pak bude schopna takové názvy pochopit. Snažte se zkrátit všechno. Jen úctyhodný člověk pak bude moci podpořit vývoj vašeho kódu. -## Soar high. Be abstract. +## Stoupejte vzhůru. Buďte abstraktní. -```quote author="Laozi (Tao Te Ching)" -The great square is cornerless
-The great vessel is last complete,
-The great note is rarified sound,
-The great image has no form. +```quote author="Laozi (Tao te ťing) (překlad Václav Cílek)" +velký čtverec nemá rohy
+velkou nádobu neuděláš hned
+velký tón není slyšet
+velká podoba je bez tvaru
``` -While choosing a name try to use the most abstract word. Like `obj`, `data`, `value`, `item`, `elem` and so on. +Když volíte název, snažte se použít co nejabstraktnější slovo. Třeba `obj`, `data`, `hodnota`, `prvek`, `element` a podobně. -- **The ideal name for a variable is `data`.** Use it everywhere you can. Indeed, every variable holds *data*, right? +- **Ideální název proměnné je `data`.** Používejte jej všude, kde můžete. Každá proměnná přece obsahuje nějaká *data*, ne? - ...But what to do if `data` is already taken? Try `value`, it's also universal. After all, a variable eventually gets a *value*. + ...Ale co když je název `data` už zabrán? Zkuste `hodnota`, to je také univerzální. Koneckonců každá proměnná nakonec bude mít nějakou *hodnotu*. -- **Name a variable by its type: `str`, `num`...** +- **Pojmenujte proměnnou podle jejího typu: `řetězec`, `číslo`...** - Give them a try. A young initiate may wonder -- are such names really useful for a ninja? Indeed, they are! + Zkuste to. Mladý učedník se může divit -- jsou takové názvy pro ninju opravdu užitečné? Ale ano, jsou! - Sure, the variable name still means something. It says what's inside the variable: a string, a number or something else. But when an outsider tries to understand the code, they'll be surprised to see that there's actually no information at all! And will ultimately fail to alter your well-thought code. + Jistě, takový název proměnné stále něco znamená. Říká, co proměnná obsahuje: řetězec, číslo nebo něco jiného. Když se však outsider pokusí porozumět kódu, bude překvapen, že vidí, že název neobsahuje vlastně vůbec žádnou informaci! A nakonec se mu nepodaří váš skvěle promyšlený kód změnit. - The value type is easy to find out by debugging. But what's the meaning of the variable? Which string/number does it store? + Datový typ proměnné lze snadno odhalit při ladění. Ale jaký je význam proměnné? Jaký řetězec nebo číslo je v ní uloženo? - There's just no way to figure out without a good meditation! + Není způsob, jak to odhalit bez dobré meditace! -- **...But what if there are no more such names?** Just add a number: `data1, item2, elem5`... +- **...Ale co když žádné další takové názvy už nezbývají?** Stačí přidat číslo: `data1, prvek2, element5`... -## Attention test +## Zkouška pozornosti -Only a truly attentive programmer should be able to understand your code. But how to check that? +Vašemu kódu by měl porozumět jen skutečně pozorný programátor. Ale jak to zajistit? -**One of the ways -- use similar variable names, like `date` and `data`.** +**Jeden způsob -- používejte podobné názvy proměnných, např. `datum` a `data`.** -Mix them where you can. +Směšujte je všude, kde je to možné. -A quick read of such code becomes impossible. And when there's a typo... Ummm... We're stuck for long, time to drink tea. +Takový kód je pak nemožné rychle přečíst. A když je někde překlep... Hmmm... Nadlouho nás to zdrží, uděláme si přestávku na čaj. -## Smart synonyms +## Chytrá synonyma -```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. +```quote author="Laozi (Tao te ťing) (překlad Václav Cílek)" +Tao, které se dá popsat slovy, není stálé Tao
+jméno, které se dá jmenovat, není věčné jméno ``` -Using *similar* names for *same* things makes life more interesting and shows your creativity to the public. +Používání *podobných* názvů pro *stejné* věci činí život zajímavějším a ukáže publiku vaši kreativitu. -For instance, consider function prefixes. If a function shows a message on the screen -- start it with `display…`, like `displayMessage`. And then if another function shows on the screen something else, like a user name, start it with `show…` (like `showName`). +Zvažte například prefixy funkcí. Jestliže funkce zobrazuje zprávu na obrazovce, začněte její název `zobraz…`, např. `zobrazZprávu`. Pokud pak jiná funkce zobrazí na obrazovce něco jiného, třeba uživatelské jméno, začněte její název `vypiš…` (např. `vypišJméno`). -Insinuate that there's a subtle difference between such functions, while there is none. +To naznačuje, že mezi těmito funkcemi je nějaký drobný rozdíl, přestože ve skutečnosti žádný není. -Make a pact with fellow ninjas of the team: if John starts "showing" functions with `display...` in his code, then Peter could use `render..`, and Ann -- `paint...`. Note how much more interesting and diverse the code became. +Dohodněte se s kolegy ninji z týmu: bude-li Honza pojmenovávat „zobrazovací“ funkce ve svém kódu `zobraz...`, pak Petr by mohl používat `vypiš...` a Anna `vykresli...`. Všimněte si, nakolik se kód stane zajímavějším a rozmanitějším. -...And now the hat trick! +...A nyní přijde ten pravý trik! -For two functions with important differences -- use the same prefix! +Pro dvě funkce, mezi nimiž je důležitý rozdíl, použijte stejný prefix! -For instance, the function `printPage(page)` will use a printer. And the function `printText(text)` will put the text on-screen. Let an unfamiliar reader think well over similarly named function `printMessage`: "Where does it put the message? To a printer or on the screen?". To make it really shine, `printMessage(message)` should output it in the new window! +Například funkce `tiskniStránku(stránka)` bude tisknout na tiskárnu. Ale funkce `tiskniText(text)` vypíše text na obrazovku. Nechte neznalého čtenáře, aby se zamyslel nad podobně pojmenovanou funkcí `tiskniZprávu`: „Kam tu zprávu vlastně vytiskne? Na tiskárnu, nebo na obrazovku?“ Aby to bylo opravdu úchvatné, `tiskniZprávu(zpráva)` by měla zobrazit zprávu v novém okně! -## Reuse names +## Používejte opakovaně stejné názvy -```quote author="Laozi (Tao Te Ching)" -Once the whole is divided, the parts
-need names.
-There are already enough names.
-One must know when to stop. +```quote author="Laozi (Tao te ťing) (překlad Václav Cílek)" +když rozdělujeme jednotu,
+objevují se jména
+pohromě se vyhneme,
+jen když víme, kdy přestat
``` -Add a new variable only when absolutely necessary. +Přidejte novou proměnnou jen tehdy, když je to absolutně nezbytné. -Instead, reuse existing names. Just write new values into them. +Jinak místo toho opakovaně používejte již existující názvy. Jen do nich zapisujte nové hodnoty. -In a function try to use only variables passed as parameters. +Ve funkci se snažte používat jedině proměnné, které byly předány jako argumenty. -That would make it really hard to identify what's exactly in the variable *now*. And also where it comes from. The purpose is to develop the intuition and memory of a person reading the code. A person with weak intuition would have to analyze the code line-by-line and track the changes through every code branch. +Díky tomu je opravdu těžké poznat, co vlastně proměnná obsahuje *právě teď*. A také, odkud to přišlo. Cílem je procvičit intuici a paměť člověka, který čte kód. Osoba se slabou intuicí bude muset analyzovat kód řádek po řádku a stopovat změny v každé větvi. -**An advanced variant of the approach is to covertly (!) replace the value with something alike in the middle of a loop or a function.** +**Pokročilá varianta tohoto přístupu je utajeně (!) nahradit hodnotu něčím podobným uvnitř cyklu nebo funkce.** -For instance: +Například: ```js -function ninjaFunction(elem) { - // 20 lines of code working with elem +function ninjovaFunkce(elem) { + // 20 řádků kódu pracujícího s elem elem = clone(elem); - // 20 more lines, now working with the clone of the elem! + // 20 dalších řádků kódu, které nyní pracují s klonem proměnné elem! } ``` -A fellow programmer who wants to work with `elem` in the second half of the function will be surprised... Only during the debugging, after examining the code they will find out that they're working with a clone! +Kolega, který bude chtít pracovat s proměnnou `elem` ve druhé polovině funkce, bude překvapen... Teprve při ladění po prozkoumání kódu přijde na to, že pracuje s klonem! -Seen in code regularly. Deadly effective even against an experienced ninja. +Toto je v kódu často vidět. Je to hrozivě efektivní i proti zkušenému ninjovi. -## Underscores for fun +## Legrace s podtržítky -Put underscores `_` and `__` before variable names. Like `_name` or `__value`. It would be great if only you knew their meaning. Or, better, add them just for fun, without particular meaning at all. Or different meanings in different places. +Umisťujte před názvy proměnných podtržítka `_` a `__`. Například `_jméno` nebo `__hodnota`. Bude to legrace, když budete jejich význam znát jenom vy. Nebo ještě lépe: přidávejte je jen pro legraci, aniž by měla nějaký konkrétní význam. Nebo ať mají na různých místech různé významy. -You kill two rabbits with one shot. First, the code becomes longer and less readable, and the second, a fellow developer may spend a long time trying to figure out what the underscores mean. +Zabijete dvě mouchy jednou ranou. Za prvé, kód se prodlouží a stane se méně čitelným, a za druhé, kolega vývojář může strávit dlouhou dobu zjišťováním, co vlastně ta podtržítka znamenají. -A smart ninja puts underscores at one spot of code and evades them at other places. That makes the code even more fragile and increases the probability of future errors. +Elegantní ninja na jednom místě kódu podtržítka vkládá a na jiných se jim vyhýbá. Díky tomu je kód ještě zranitelnější a zvyšuje se pravděpodobnost budoucích chyb. -## Show your love +## Ukažte svou lásku -Let everyone see how magnificent your entities are! Names like `superElement`, `megaFrame` and `niceItem` will definitely enlighten a reader. +Dejte každému vědět, jak úžasné jsou vaše entity! Názvy jako `superElement`, `megaRámec` nebo `pěknýPrvek` čtenáře zaručeně osvítí. -Indeed, from one hand, something is written: `super..`, `mega..`, `nice..` But from the other hand -- that brings no details. A reader may decide to look for a hidden meaning and meditate for an hour or two of their paid working time. +Jistě, na jednu stranu musíte něco napsat: `super..`, `mega..`, `pěkný..`. Ale na druhou stranu to neposkytuje žádné detaily. Čtenář se může rozhodnout hledat jejich skrytý význam a strávit meditací hodinu nebo dvě své placené pracovní doby. -## Overlap outer variables +## Překrývejte vnější proměnné ```quote author="Guan Yin Zi" -When in the light, can't see anything in the darkness.
-When in the darkness, can see everything in the light. +Kdo je na světle, nevidí nic, co je ve tmě.
+Kdo je ve tmě, vidí všechno, co je na světle. ``` -Use same names for variables inside and outside a function. As simple. No efforts to invent new names. +Používejte stejné názvy pro proměnné vně a uvnitř funkce. Je to jednoduché. Není třeba úsilí k vymýšlení nových názvů. ```js -let *!*user*/!* = authenticateUser(); +let *!*uživatel*/!* = autentikujUživatele(); -function render() { - let *!*user*/!* = anotherValue(); +function renderuj() { + let *!*uživatel*/!* = jináHodnota(); ... - ...many lines... + ...mnoho řádků... ... - ... // <-- a programmer wants to work with user here and... + ... // <-- programátor zde chce pracovat s uživatelem a... ... } ``` -A programmer who jumps inside the `render` will probably fail to notice that there's a local `user` shadowing the outer one. +Programátor, který skočí dovnitř funkce `renderuj`, si pravděpodobně nevšimne, že v ní je lokální proměnná `uživatel`, která zastiňuje tu vnější. -Then they'll try to work with `user` assuming that it's the external variable, the result of `authenticateUser()`... The trap is sprung! Hello, debugger... +Pak se pokusí pracovat s proměnnou `uživatel` předpokládaje, že je to externí proměnná, která je výsledkem volání `autentikujUživatele()`... A past sklapne! Nazdar, debuggere... -## Side-effects everywhere! +## Všude vedlejší efekty! -There are functions that look like they don't change anything. Like `isReady()`, `checkPermission()`, `findTags()`... They are assumed to carry out calculations, find and return the data, without changing anything outside of them. In other words, without "side-effects". +Existují funkce, které vypadají, jako by nic neměnily. Například `jePřipraven()`, `ověřOprávnění()`, `najdiZnačky()`... Předpokládá se, že provedou výpočty a najdou a vrátí data, aniž by měnily cokoli mimo ně. Jinými slovy, bez „vedlejších efektů“. -**A really beautiful trick is to add a "useful" action to them, besides the main task.** +**Opravdu krásný trik je přidat do nich kromě hlavního úkolu ještě nějakou „užitečnou“ akci navíc.** -An expression of dazed surprise on the face of your colleague when they see a function named `is..`, `check..` or `find...` changing something -- will definitely broaden your boundaries of reason. +Pohled na překvapenou tvář vašeho kolegy, který zjistí, že funkce pojmenovaná `je..`, `ověř..` nebo `najdi..` něco mění, zaručeně posune hranice vašeho rozumu. -**Another way to surprise is to return a non-standard result.** +**Další cestou, jak překvapit, je vrátit nestandardní výsledek.** -Show your original thinking! Let the call of `checkPermission` return not `true/false`, but a complex object with the results of the check. +Předveďte své originální myšlení! Nechte funkci `ověřOprávnění` vrátit nikoli `true/false`, ale složitý objekt s výsledky ověření. -Those developers who try to write `if (checkPermission(..))`, will wonder why it doesn't work. Tell them: "Read the docs!". And give this article. +Vývojáři, kteří se pokusí napsat `if (ověřOprávnění(..))`, se budou divit, proč to nefunguje. Řekněte jim: „Přečtěte si dokumentaci!“ A dejte jim odkaz na tento článek. -## Powerful functions! +## Silné funkce! -```quote author="Laozi (Tao Te Ching)" -The great Tao flows everywhere,
-both to the left and to the right. +```quote author="Laozi (Tao te ťing) (překlad Václav Cílek)" +Velké Tao se rozlévá, kam chce ``` -Don't limit the function by what's written in its name. Be broader. +Neomezujte funkci na to, co je uvedeno v jejím názvu. Rozšiřte se. -For instance, a function `validateEmail(email)` could (besides checking the email for correctness) show an error message and ask to re-enter the email. +Například funkce `zkontrolujEmail(email)` by mohla (kromě kontroly, zda email je správně) zobrazit chybovou zprávu a požádat uživatele, aby email zadal znovu. -Additional actions should not be obvious from the function name. A true ninja coder will make them not obvious from the code as well. +Přidané akce by neměly být zřejmé z názvu funkce. Opravdový ninja je učiní nezřejmými dokonce i z kódu. -**Joining several actions into one protects your code from reuse.** +**Spojení několika akcí do jedné ochrání váš kód před opakovaným použitím.** -Imagine, another developer wants only to check the email, and not output any message. Your function `validateEmail(email)` that does both will not suit them. So they won't break your meditation by asking anything about it. +Představte si, že jiný vývojář bude chtít jen zkontrolovat email a nevypisovat žádnou zprávu. Vaše funkce `zkontrolujEmail(email)`, která dělá obojí, mu pak nebude vyhovovat. Nepřekazí tedy vaši meditaci tím, že se na ni bude ptát. -## Summary +## Shrnutí -All "pieces of advice" above are from the real code... Sometimes, written by experienced developers. Maybe even more experienced than you are ;) +Všechny výše uvedené „rady“ pocházejí ze skutečného kódu... Někdy byl dokonce napsán zkušenými vývojáři. Možná ještě zkušenějšími, než vy ;) -- Follow some of them, and your code will become full of surprises. -- Follow many of them, and your code will become truly yours, no one would want to change it. -- Follow all, and your code will become a valuable lesson for young developers looking for enlightenment. +- Když budete dodržovat některé z nich, bude váš kód plný překvapení. +- Když budete dodržovat mnohé z nich, bude váš kód opravdu jen váš a nikdo ho nebude chtít měnit. +- Když budete dodržovat všechny, bude váš kód cennou lekcí pro mladé vývojáře toužící po osvícení. 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 79f190370..4c2b1aa5e 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -69,7 +69,7 @@ The flow of development usually looks like this: 1. An initial spec is written, with tests for the most basic functionality. 2. An initial implementation is created. -3. To check whether it works, we run the testing framework [Mocha](http://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works. +3. To check whether it works, we run the testing framework [Mocha](https://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works. 4. Now we have a working initial implementation with tests. 5. We add more use cases to the spec, probably not yet supported by the implementations. Tests start to fail. 6. Go to 3, update the implementation till tests give no errors. @@ -79,15 +79,15 @@ So, the development is *iterative*. We write the spec, implement it, make sure t Let's see this development flow in our practical case. -The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use few JavaScript libraries to run the tests, just to see that they are working (they will all fail). +The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use a few JavaScript libraries to run the tests, just to see that they are working (they will all fail). ## The spec in action Here in the tutorial we'll be using the following JavaScript libraries for tests: -- [Mocha](http://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. -- [Chai](http://chaijs.com) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. -- [Sinon](http://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. +- [Mocha](https://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. +- [Chai](https://www.chaijs.com/) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. +- [Sinon](https://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. These libraries are suitable for both in-browser and server-side testing. Here we'll consider the browser variant. @@ -338,14 +338,14 @@ The newly added tests fail, because our implementation does not support them. Th ```smart header="Other assertions" Please note the assertion `assert.isNaN`: it checks for `NaN`. -There are other assertions in [Chai](http://chaijs.com) as well, for instance: +There are other assertions in [Chai](https://www.chaijs.com/) as well, for instance: - `assert.equal(value1, value2)` -- checks the equality `value1 == value2`. - `assert.strictEqual(value1, value2)` -- checks the strict equality `value1 === value2`. - `assert.notEqual`, `assert.notStrictEqual` -- inverse checks to the ones above. - `assert.isTrue(value)` -- checks that `value === true` - `assert.isFalse(value)` -- checks that `value === false` -- ...the full list is in the [docs](http://chaijs.com/api/assert/) +- ...the full list is in the [docs](https://www.chaijs.com/api/assert/) ``` So we should add a couple of lines to `pow`: diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 161c6a6e9..491b13519 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -7,7 +7,7 @@ Týmy vyvíjející JavaScriptové motory mají své vlastní nápady ohledně t Je tedy poměrně běžné, že motor implementuje pouze část standardu. -Dobrá stránka, na které uvidíte aktuální stav podpory vlastností jazyka, je (je to velká tabulka, máme ještě hodně co studovat). +Dobrá stránka, na které uvidíte aktuální stav podpory vlastností jazyka, je (je to velká tabulka, máme ještě hodně co studovat). Jako programátoři rádi používáme nejnovější vlastnosti. Čím více dobrých věcí, tím lépe! @@ -73,7 +73,6 @@ JavaScript je vysoce dynamický jazyk. Skripty mohou přidávat nebo modifikovat Dvě zajímavé knihovny polyfillů jsou: - [core js](https://github.com/zloirock/core-js), která toho podporuje mnoho a umožňuje přidávat jen potřebné vlastnosti. -- [polyfill.io](https://polyfill.io/) je služba, která poskytuje skript s polyfilly podle vlastností a uživatelova prohlížeče. ## Shrnutí @@ -87,5 +86,5 @@ Dobré zdroje, které ukazují aktuální stav podpory různých vlastností: - - pro čistý JavaScript. - - pro funkce vztahující se k prohlížeči. -P.S. Pokud se týká vlastností jazyka, obvykle je nejaktuálnější Google Chrome. Pokud vám některé demo v tomto tutoriálu selže, zkuste ho. Většina dem v tutoriálu však funguje na kterémkoli moderním prohlížeči. +P.S. Pokud se týká vlastností jazyka, obvykle je nejaktuálnější Google Chrome. Pokud vám některé demo v tomto tutoriálu selže, zkuste ho. Většina dem v tutoriálu však funguje na kterémkoli moderním prohlížeči. diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md index 7fe8a26a6..e80f748ab 100644 --- a/1-js/04-object-basics/02-object-copy/article.md +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -100,6 +100,30 @@ alert( a == b ); // false For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake. +````smart header="Const objects can be modified" +An important side effect of storing objects as references is that an object declared as `const` *can* be modified. + +For instance: + +```js run +const user = { + name: "John" +}; + +*!* +user.name = "Pete"; // (*) +*/!* + +alert(user.name); // Pete +``` + +It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change. + +In other words, the `const user` gives an error only if we try to set `user=...` as a whole. + +That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter . +```` + ## Cloning and merging, Object.assign [#cloning-and-merging-object-assign] So, copying an object variable creates one more reference to the same object. @@ -136,16 +160,17 @@ We can also use the method [Object.assign](https://developer.mozilla.org/en-US/d The syntax is: ```js -Object.assign(dest, [src1, src2, src3...]) +Object.assign(dest, ...sources) ``` - 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`. +- Further arguments is a list of source objects. -For instance, we can use it to merge several objects into one: -```js +It copies the properties of all source objects into the target `dest`, and then returns it as the result. + +For example, we have `user` object, let's add a couple of permissions to it: + +```js run let user = { name: "John" }; let permissions1 = { canView: true }; @@ -157,6 +182,9 @@ Object.assign(user, permissions1, permissions2); */!* // now user = { name: "John", canView: true, canEdit: true } +alert(user.name); // John +alert(user.canView); // true +alert(user.canEdit); // true ``` If the copied property name already exists, it gets overwritten: @@ -169,9 +197,9 @@ 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: +We also can use `Object.assign` to perform a simple object cloning: -```js +```js run let user = { name: "John", age: 30 @@ -180,9 +208,12 @@ let user = { *!* let clone = Object.assign({}, user); */!* + +alert(clone.name); // John +alert(clone.age); // 30 ``` -It copies all properties of `user` into the empty object and returns it. +Here it copies all properties of `user` into the empty object and returns it. There are also other methods of cloning an object, e.g. using the [spread syntax](info:rest-parameters-spread) `clone = {...user}`, covered later in the tutorial. @@ -219,37 +250,71 @@ 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, get the result from the other one +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 60, get the result from the other one ``` -To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning". +To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning. -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. +### structuredClone -For instance: +The call `structuredClone(object)` clones the `object` with all nested properties. + +Here's how we can use it in our example: ```js run -const user = { - name: "John" +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } }; *!* -user.name = "Pete"; // (*) +let clone = structuredClone(user); */!* -alert(user.name); // Pete +alert( user.sizes === clone.sizes ); // false, different objects + +// user and clone are totally unrelated now +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 50, not related ``` -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. +The `structuredClone` method can clone most data types, such as objects, arrays, primitive values. -In other words, the `const user` gives an error only if we try to set `user=...` as a whole. +It also supports circular references, when an object property references the object itself (directly or via a chain or references). -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 . -```` +For instance: + +```js run +let user = {}; +// let's create a circular reference: +// user.me references the user itself +user.me = user; + +let clone = structuredClone(user); +alert(clone.me === clone); // true +``` + +As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well. + +Although, there are cases when `structuredClone` fails. + +For instance, when an object has a function property: + +```js run +// error +structuredClone({ + f: function() {} +}); +``` + +Function properties aren't supported. + +To handle such complex cases we may need to use a combination of cloning methods, write custom code or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). ## Summary @@ -257,4 +322,4 @@ Objects are assigned and copied by reference. In other words, a variable stores 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). +To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function `structuredClone` or use a custom cloning implementation, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md index 50ada6713..1b576d629 100644 --- a/1-js/04-object-basics/03-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -205,8 +205,8 @@ Modern engines implement advanced algorithms of garbage collection. A general book "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al) covers some of them. -If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). +If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). -The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](http://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. +The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](https://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. 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 a2a19c620..7d2ef8c15 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md @@ -4,7 +4,7 @@ importance: 2 # Chaining -There's a `ladder` object that allows to go up and down: +There's a `ladder` object that allows you to go up and down: ```js let ladder = { @@ -21,7 +21,7 @@ let ladder = { }; ``` -Now, if we need to make several calls in sequence, can do it like this: +Now, if we need to make several calls in sequence, we can do it like this: ```js ladder.up(); @@ -32,10 +32,10 @@ ladder.down(); ladder.showStep(); // 0 ``` -Modify the code of `up`, `down` and `showStep` to make the calls chainable, like this: +Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this: ```js ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` -Such approach is widely used across JavaScript libraries. +Such an approach is widely used across JavaScript libraries. 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 d80113acc..e932a201a 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 @@ -10,8 +10,8 @@ Is it possible to create functions `A` and `B` so that `new A() == new B()`? function A() { ... } function B() { ... } -let a = new A; -let b = new B; +let a = new A(); +let b = new B(); alert( a == b ); // true ``` diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md index 60e7c373e..c862bec40 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md @@ -6,7 +6,7 @@ importance: 5 Create a constructor function `Calculator` that creates objects with 3 methods: -- `read()` asks for two values using `prompt` and remembers them in object properties. +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. - `sum()` returns the sum of these properties. - `mul()` returns the multiplication product of these properties. 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 f3e9c3ec0..a335464f1 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -171,7 +171,7 @@ alert( new SmallUser().name ); // John Usually constructors don't have a `return` statement. Here we mention the special behavior with returning objects mainly for the sake of completeness. ````smart header="Omitting parentheses" -By the way, we can omit parentheses after `new`, if it has no arguments: +By the way, we can omit parentheses after `new`: ```js let user = new User; // <-- no parentheses diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index acc4e78b4..fa68da583 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -136,8 +136,8 @@ As we can see from the code, `user` becomes a self-descriptive string or a money If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: -- For the `"string"` hint: call `toString` method, and if it doesn't exist, 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). +- For the `"string"` hint: call `toString` method, and if it doesn't exist or if it returns an object instead of a primitive value, then call `valueOf` (so `toString` has the priority for string conversions). +- For other hints: call `valueOf`, and if it doesn't exist or if it returns an object instead of a primitive value, then call `toString` (so `valueOf` has the priority for maths). Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. @@ -226,7 +226,7 @@ As we know already, many operators and functions perform type conversions, e.g. If we pass an object as an argument, then there are two stages of calculations: 1. The object is converted to a primitive (using the rules described above). -2. If the necessary for further calculations, the resulting primitive is also converted. +2. If necessary for further calculations, the resulting primitive is also converted. For instance: @@ -253,7 +253,7 @@ let obj = { } }; -alert(obj + 2); // 22 ("2" + 2), conversion to primitive returned a string => concatenation +alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation ``` ## Summary diff --git a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md index a17a4671a..4bcd74512 100644 --- a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md +++ b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md @@ -28,6 +28,6 @@ Note that `63.5` has no precision loss at all. That's because the decimal part ` ```js run -alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 +alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 ``` diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 3fec8848f..96a7b622a 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -4,7 +4,7 @@ In modern JavaScript, there are two types of numbers: 1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter. -2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in few special areas, we devote them a special chapter . +2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter . So here we'll talk about regular numbers. Let's expand our knowledge of them. @@ -41,7 +41,7 @@ In other words, `e` multiplies the number by `1` with the given zeroes count. 1.23e6 === 1.23 * 1000000; // e6 means *1000000 ``` -Now let's write something very small. Say, 1 microsecond (one millionth of a second): +Now let's write something very small. Say, 1 microsecond (one-millionth of a second): ```js let mсs = 0.000001; @@ -103,13 +103,13 @@ alert( num.toString(16) ); // ff alert( num.toString(2) ); // 11111111 ``` -The `base` can vary from `2` to `36`. By default it's `10`. +The `base` can vary from `2` to `36`. By default, it's `10`. Common use cases for this are: - **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`. - **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`. -- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example to make a short url. Can simply represent it in the numeral system with base `36`: +- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole Latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example, to make a short url. Can simply represent it in the numeral system with base `36`: ```js run alert( 123456..toString(36) ); // 2n9c @@ -137,7 +137,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`, the middle case: `3.5` rounds up to `4` too. +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`. `Math.trunc` (not supported by Internet Explorer) : Removes anything after the decimal point without rounding: `3.1` becomes `3`, `-1.1` becomes `-1`. @@ -147,8 +147,10 @@ Here's the table to summarize the differences between them: | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |---|---------|--------|---------|---------| |`3.1`| `3` | `4` | `3` | `3` | +|`3.5`| `3` | `4` | `4` | `3` | |`3.6`| `3` | `4` | `4` | `3` | |`-1.1`| `-2` | `-1` | `-1` | `-1` | +|`-1.5`| `-2` | `-1` | `-1` | `-1` | |`-1.6`| `-2` | `-1` | `-2` | `-1` | @@ -188,7 +190,7 @@ There are two ways to do so: alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits ``` - We can convert it to a number using the unary plus or a `Number()` call, e.g write `+num.toFixed(5)`. + We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`. ## Imprecise calculations @@ -222,7 +224,13 @@ But why does this happen? A number is stored in memory in its binary form, a sequence of bits - ones and zeroes. But fractions like `0.1`, `0.2` that look simple in the decimal numeric system are actually unending fractions in their binary form. -What is `0.1`? It is one divided by ten `1/10`, one-tenth. In decimal numeral system such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. +```js run +alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101 +alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101 +alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101 +``` + +What is `0.1`? It is one divided by ten `1/10`, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`. So, division by powers `10` is guaranteed to work well in the decimal system, but division by `3` is not. For the same reason, in the binary numeral system, the division by powers of `2` is guaranteed to work, but `1/10` becomes an endless binary fraction. @@ -242,7 +250,7 @@ That's why `0.1 + 0.2` is not exactly `0.3`. ```smart header="Not only JavaScript" The same issue exists in many other programming languages. -PHP, Java, C, Perl, Ruby give exactly the same result, because they are based on the same numeric format. +PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format. ``` Can we work around the problem? Sure, the most reliable method is to round the result with the help of a method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed): @@ -266,7 +274,7 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` -So, multiply/divide approach reduces the error, but doesn't remove it totally. +So, the multiply/divide approach reduces the error, but doesn't remove it totally. Sometimes we could try to evade fractions at all. Like if we're dealing with a shop, then we can store prices in cents instead of dollars. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely possible. Just round them to cut "tails" when needed. @@ -288,7 +296,7 @@ Another funny consequence of the internal representation of numbers is the exist 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. +In most cases, the distinction is unnoticeable, because operators are suited to treat them as the same. ``` ## Tests: isFinite and isNaN @@ -334,15 +342,44 @@ alert( isFinite(num) ); 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`" +````smart header="`Number.isNaN` and `Number.isFinite`" +[Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) and [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) methods are the more "strict" versions of `isNaN` and `isFinite` functions. They do not autoconvert their argument into a number, but check if it belongs to the `number` type instead. + +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case, it returns `false`. + + ```js run + alert( Number.isNaN(NaN) ); // true + alert( Number.isNaN("str" / 2) ); // true + + // Note the difference: + alert( Number.isNaN("str") ); // false, because "str" belongs to the string type, not the number type + alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion + ``` + +- `Number.isFinite(value)` returns `true` if the argument belongs to the `number` type and it is not `NaN/Infinity/-Infinity`. In any other case, it returns `false`. + + ```js run + alert( Number.isFinite(123) ); // true + alert( Number.isFinite(Infinity) ); // false + alert( Number.isFinite(2 / 0) ); // false + + // Note the difference: + alert( Number.isFinite("123") ); // false, because "123" belongs to the string type, not the number type + alert( isFinite("123") ); // true, because isFinite converts string "123" into a number 123 + ``` + +In a way, `Number.isNaN` and `Number.isFinite` are simpler and more straightforward than `isNaN` and `isFinite` functions. In practice though, `isNaN` and `isFinite` are mostly used, as they're shorter to write. +```` + +```smart header="Comparison with `Object.is`" There is a special built-in method `Object.is` that compares values like `===`, but is more reliable for two edge cases: 1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing. -2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's true, because internally the number has a sign bit that may be different even if all other bits are zeroes. +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct because internally the number has a sign bit that may be different even if all other bits are zeroes. In all other cases, `Object.is(a, b)` is the same as `a === b`. -This way of comparison is often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). +We mention `Object.is` here, because it's often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). ``` @@ -356,7 +393,7 @@ alert( +"100px" ); // NaN The sole exception is spaces at the beginning or at the end of the string, as they are ignored. -But in real life we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. +But in real life, we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries, the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that. That's what `parseInt` and `parseFloat` are for. @@ -435,7 +472,9 @@ For different numeral systems: For regular number tests: - `isNaN(value)` converts its argument to a number and then tests it for being `NaN` -- `isFinite(value)` converts its argument to a number and returns `true` if it's a regular number, not `NaN/Infinity/-Infinity` +- `Number.isNaN(value)` checks whether its argument belongs to the `number` type, and if so, tests it for being `NaN` +- `isFinite(value)` converts its argument to a number and then tests it for not being `NaN/Infinity/-Infinity` +- `Number.isFinite(value)` checks whether its argument belongs to the `number` type, and if so, tests it for not being `NaN/Infinity/-Infinity` For converting values like `12pt` and `100px` to a number: @@ -448,4 +487,4 @@ For fractions: More mathematical functions: -- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small, but can cover basic needs. +- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small but can cover basic needs. diff --git a/1-js/05-data-types/03-string/1-ucfirst/solution.md b/1-js/05-data-types/03-string/1-ucfirst/solution.md index f7a332d0d..be5dd2aaf 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/solution.md +++ b/1-js/05-data-types/03-string/1-ucfirst/solution.md @@ -8,12 +8,7 @@ let newStr = str[0].toUpperCase() + str.slice(1); There's a small problem though. If `str` is empty, then `str[0]` is `undefined`, and as `undefined` doesn't have the `toUpperCase()` method, we'll get an error. -There are two variants here: - -1. Use `str.charAt(0)`, as it always returns a string (maybe empty). -2. Add a test for an empty string. - -Here's the 2nd variant: +The easiest way out is to add a test for an empty string, like this: ```js run demo function ucFirst(str) { @@ -24,4 +19,3 @@ function ucFirst(str) { alert( ucFirst("john") ); // John ``` - diff --git a/1-js/05-data-types/03-string/3-truncate/task.md b/1-js/05-data-types/03-string/3-truncate/task.md index 6382029f4..c99a5f15a 100644 --- a/1-js/05-data-types/03-string/3-truncate/task.md +++ b/1-js/05-data-types/03-string/3-truncate/task.md @@ -11,7 +11,7 @@ The result of the function should be the truncated (if needed) string. For instance: ```js -truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…" +truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…" -truncate("Hi everyone!", 20) = "Hi everyone!" +truncate("Hi everyone!", 20) == "Hi everyone!" ``` diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index b3535866d..60ce2b6f0 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 feature is called "tagged templates", it's rarely seen, but you can read about it in the MDN: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). ## Special characters @@ -59,10 +59,10 @@ It is still possible to create multiline strings with single and double quotes b ```js run let guestList = "Guests:\n * John\n * Pete\n * Mary"; -alert(guestList); // a multiline list of guests +alert(guestList); // a multiline list of guests, same as above ``` -For example, these two lines are equal, just written differently: +As a simpler example, these two lines are equal, just written differently: ```js run let str1 = "Hello\nWorld"; // two lines using a "newline symbol" @@ -74,33 +74,26 @@ World`; alert(str1 == str2); // true ``` -There are other, less common "special" characters. - -Here's the full list: +There are other, less common special characters: | Character | Description | |-----------|-------------| |`\n`|New line| |`\r`|In Windows text files a combination of two characters `\r\n` represents a new break, while on non-Windows OS it's just `\n`. That's for historical reasons, most Windows software also understands `\n`. | -|`\'`, `\"`|Quotes| +|`\'`, `\"`, \\`|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. | +|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- mentioned for completeness, coming from old times, not used nowadays (you can forget them right now). | + +As you can see, all special characters start with a backslash character `\`. It is also called an "escape character". -Examples with Unicode: +Because it's so special, if we need to show an actual backslash `\` within the string, we need to double it: ```js run -alert( "\u00A9" ); // © -alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long Unicode) -alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) +alert( `The backslash: \\` ); // The backslash: \ ``` -All special characters start with a backslash character `\`. It is also called an "escape character". - -We might also use it if we wanted to insert a quote into the string. +So-called "escaped" quotes `\'`, `\"`, \\` are used to insert a quote into the same-quoted string. For instance: @@ -113,18 +106,10 @@ As you can see, we have to prepend the inner quote by the backslash `\'`, becaus 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! +alert( "I'm the Walrus!" ); // I'm the Walrus! ``` -Note that the backslash `\` serves for the correct reading of the string by JavaScript, then disappears. The in-memory string has no `\`. You can clearly see that in `alert` from the examples above. - -But what if we need to show an actual backslash `\` within the string? - -That's possible, but we need to double it like `\\`: - -```js run -alert( `The backslash: \\` ); // The backslash: \ -``` +Besides these special characters, there's also a special notation for Unicode codes `\u…`, it's rarely used and is covered in the optional chapter about [Unicode](info:unicode). ## String length @@ -139,33 +124,36 @@ Note that `\n` is a single "special" character, so the length is indeed `3`. ```warn header="`length` is a property" People with a background in some other languages sometimes mistype by calling `str.length()` instead of just `str.length`. That doesn't work. -Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. +Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. Not `.length()`, but `.length`. ``` ## Accessing characters -To get a character at position `pos`, use square brackets `[pos]` or call the method [str.charAt(pos)](mdn:js/String/charAt). The first character starts from the zero position: +To get a character at position `pos`, use square brackets `[pos]` or call the method [str.at(pos)](mdn:js/String/at). The first character starts from the zero position: ```js run let str = `Hello`; // the first character alert( str[0] ); // H -alert( str.charAt(0) ); // H +alert( str.at(0) ); // H // the last character alert( str[str.length - 1] ); // o +alert( str.at(-1) ); ``` -The square brackets are a modern way of getting a character, while `charAt` exists mostly for historical reasons. +As you can see, the `.at(pos)` method has a benefit of allowing negative position. If `pos` is negative, then it's counted from the end of the string. -The only difference between them is that if no character is found, `[]` returns `undefined`, and `charAt` returns an empty string: +So `.at(-1)` means the last character, and `.at(-2)` is the one before it, etc. + +The square brackets always return `undefined` for negative indexes, for instance: ```js run let str = `Hello`; -alert( str[1000] ); // undefined -alert( str.charAt(1000) ); // '' (an empty string) +alert( str[-2] ); // undefined +alert( str.at(-2) ); // l ``` We can also iterate over characters using `for..of`: @@ -310,45 +298,6 @@ 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_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)`. - -For instance: - -```js run -alert( ~2 ); // -3, the same as -(2+1) -alert( ~1 ); // -2, the same as -(1+1) -alert( ~0 ); // -1, the same as -(0+1) -*!* -alert( ~-1 ); // 0, the same as -(-1+1) -*/!* -``` - -As we can see, `~n` is zero only if `n == -1` (that's for any 32-bit signed integer `n`). - -So, the test `if ( ~str.indexOf("...") )` is truthy only if the result of `indexOf` is not `-1`. In other words, when there is a match. - -People use it to shorten `indexOf` checks: - -```js run -let str = "Widget"; - -if (~str.indexOf("Widget")) { - alert( 'Found it!' ); // works -} -``` - -It is usually not recommended to use language features in a non-obvious way, but this particular trick is widely used in old code, so we should understand it. - -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 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). - ### includes, startsWith, endsWith The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` contains `substr` within. @@ -407,9 +356,9 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and ``` `str.substring(start [, end])` -: Returns the part of the string *between* `start` and `end`. +: Returns the part of the string *between* `start` and `end` (not including `end`). - This is almost the same as `slice`, but it allows `start` to be greater than `end`. + This is almost the same as `slice`, but it allows `start` to be greater than `end` (in this case it simply swaps `start` and `end` values). For instance: @@ -445,18 +394,22 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters ``` + This method resides in the [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) of the language specification. It means that only browser-hosted Javascript engines should support it, and it's not recommended to use it. In practice, it's supported everywhere. + Let's recap these methods to avoid any confusion: | method | selects... | negatives | |--------|-----------|-----------| | `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives | -| `substring(start, end)` | between `start` and `end` | negative values mean `0` | +| `substring(start, end)` | between `start` and `end` (not including `end`)| negative values mean `0` | | `substr(start, length)` | from `start` get `length` characters | allows negative `start` | ```smart header="Which one to choose?" All of them can do the job. Formally, `substr` has a minor drawback: it is described not in the core JavaScript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere. -Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. So, it's enough to remember solely `slice` of these three methods. +Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. + +So, for practical use it's enough to remember only `slice`. ``` ## Comparing strings @@ -479,17 +432,18 @@ Although, there are some oddities. This may lead to strange results if we sort these country names. Usually people would expect `Zealand` to come after `Österreich` in the list. -To understand what happens, let's review the internal representation of strings in JavaScript. +To understand what happens, we should be aware that strings in Javascript are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. -All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. There are special methods that allow to get the character for the code and back. +There are special methods that allow to get the character for the code and back: `str.codePointAt(pos)` -: Returns the code for the character at position `pos`: +: Returns a decimal number representing the code for the character at position `pos`: ```js run // different case letters have different codes - alert( "z".codePointAt(0) ); // 122 alert( "Z".codePointAt(0) ); // 90 + alert( "z".codePointAt(0) ); // 122 + alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value) ``` `String.fromCodePoint(code)` @@ -497,13 +451,7 @@ All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). Th ```js run alert( String.fromCodePoint(90) ); // Z - ``` - - We can also add Unicode characters by their codes using `\u` followed by the hex code: - - ```js run - // 90 is 5a in hexadecimal system - alert( '\u005a' ); // Z + alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument) ``` Now let's see the characters with codes `65..220` (the latin alphabet and a little bit extra) by making a string of them: @@ -515,6 +463,7 @@ for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); +// Output: // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` @@ -534,7 +483,7 @@ The "right" algorithm to do string comparisons is more complex than it may seem, 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, modern browsers support the internationalization standard [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/). It provides a special method to compare strings in different languages, following their rules. @@ -552,119 +501,11 @@ alert( 'Österreich'.localeCompare('Zealand') ); // -1 This method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), which allows it to specify the language (by default taken from the environment, letter order depends on the language) and setup additional rules like case sensitivity or should `"a"` and `"á"` be treated as the same etc. -## Internals, Unicode - -```warn header="Advanced knowledge" -The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters or other rare symbols. - -You can skip the section if you don't plan to support them. -``` - -### Surrogate pairs - -All frequently used characters have 2-byte codes. Letters in most european languages, numbers, and even most hieroglyphs, have a 2-byte representation. - -But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair". - -The length of such symbols is `2`: - -```js run -alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X -alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY -alert( '𩷶'.length ); // 2, a rare Chinese hieroglyph -``` - -Note that surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! - -We actually have a single symbol in each of the strings above, but the `length` shows a length of `2`. - -`String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs. - -Getting a symbol can be tricky, because surrogate pairs are treated as two characters: - -```js run -alert( '𝒳'[0] ); // strange symbols... -alert( '𝒳'[1] ); // ...pieces of the surrogate pair -``` - -Note that pieces of the surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. - -Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. - -In the case above: - -```js run -// charCodeAt is not surrogate-pair aware, so it gives codes for parts - -alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, between 0xd800 and 0xdbff -alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, between 0xdc00 and 0xdfff -``` - -You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. - -### Diacritical marks and normalization - -In many languages, there are symbols that are composed of the base character with a mark above/under it. - -For instance, the letter `a` can be the base character for: `àáâäãåā`. 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. - -For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. - -```js run -alert( 'S\u0307' ); // Ṡ -``` - -If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. - -For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. - -For example: - -```js run -alert( 'S\u0307\u0323' ); // Ṩ -``` - -This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. - -For instance: - -```js run -let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below -let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above - -alert( `s1: ${s1}, s2: ${s2}` ); - -alert( s1 == s2 ); // false though the characters look identical (?!) -``` - -To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. - -It is implemented by [str.normalize()](mdn:js/String/normalize). - -```js run -alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true -``` - -It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). - -```js run -alert( "S\u0307\u0323".normalize().length ); // 1 - -alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true -``` - -In reality, this is not always the case. The reason being that the symbol `Ṩ` is "common enough", so UTF-16 creators included it in the main table and gave it the code. - -If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. - ## Summary - 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...`. -- To get a character, use: `[]`. +- We can use special characters, such as a line break `\n`. +- To get a character, use: `[]` or `at` method. - To get a substring, use: `slice` or `substring`. - To lowercase/uppercase a string, use: `toLowerCase/toUpperCase`. - To look for a substring, use: `indexOf`, or `includes/startsWith/endsWith` for simple checks. @@ -677,3 +518,5 @@ There are several other helpful methods in strings: - ...and more to be found in the [manual](mdn:js/String). Strings also have methods for doing search/replace with regular expressions. But that's big topic, so it's explained in a separate tutorial section . + +Also, as of now it's important to know that strings are based on Unicode encoding, and hence there're issues with comparisons. There's more about Unicode in the chapter . 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 befd80296..7e1ca3bde 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 @@ -59,7 +59,7 @@ alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 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. +For big arrays (1000, 10000 or more items) such algorithms can lead to serious sluggishness. # Fast solution @@ -91,4 +91,4 @@ alert( getMaxSubSum([-1, -2, -3]) ); // 0 The algorithm requires exactly 1 array pass, so the time complexity is O(n). -You can find more detail information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. +You can find more detailed information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. diff --git a/1-js/05-data-types/04-array/2-create-array/task.md b/1-js/05-data-types/04-array/2-create-array/task.md index 16d14071f..d4551c79c 100644 --- a/1-js/05-data-types/04-array/2-create-array/task.md +++ b/1-js/05-data-types/04-array/2-create-array/task.md @@ -8,7 +8,7 @@ Let's try 5 array operations. 1. Create an array `styles` with items "Jazz" and "Blues". 2. Append "Rock-n-Roll" to the end. -3. Replace the value in the middle by "Classics". Your code for finding the middle value should work for any arrays with odd length. +3. Replace the value in the middle with "Classics". Your code for finding the middle value should work for any arrays with odd length. 4. Strip off the first value of the array and show it. 5. Prepend `Rap` and `Reggae` to the array. diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 4bcab0bc9..e71e86a5b 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -98,7 +98,7 @@ The "trailing comma" style makes it easier to insert/remove items, because all l Let's say we want the last element of the array. -Some programming languages allow to use negative indexes for the same purpose, like `fruits[-1]`. +Some programming languages allow the use of negative indexes for the same purpose, like `fruits[-1]`. Although, in JavaScript it won't work. The result will be `undefined`, because the index in square brackets is treated literally. @@ -426,7 +426,7 @@ let matrix = [ [7, 8, 9] ]; -alert( matrix[1][1] ); // 5, the central element +alert( matrix[0][1] ); // 2, the second value of the first inner array ``` ## toString 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 104093785..853645958 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,6 +1,6 @@ # Array methods -Arrays provide a lot of methods. To make things easier, in this chapter they are split into groups. +Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. ## Add/remove items @@ -32,11 +32,11 @@ alert( arr.length ); // 3 The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`. -That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now. +That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now. So, special methods should be used. -The [arr.splice](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: @@ -62,7 +62,7 @@ alert( arr ); // ["I", "JavaScript"] Easy, right? Starting from the index `1` it removed `1` element. -In the next example we remove 3 elements and replace them with the other two: +In the next example, we remove 3 elements and replace them with the other two: ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; @@ -84,7 +84,7 @@ let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- array of removed elements ``` -The `splice` method is also able to insert the elements without any removals. For that we need to set `deleteCount` to `0`: +The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: ```js run let arr = ["I", "study", "JavaScript"]; @@ -114,7 +114,7 @@ alert( arr ); // 1,2,3,4,5 ### slice -The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking `arr.splice`. +The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. The syntax is: @@ -124,7 +124,7 @@ arr.slice([start], [end]) It returns a new array copying to it all items from index `start` to `end` (not including `end`). Both `start` and `end` can be negative, in that case position from array end is assumed. -It's similar to a string method `str.slice`, but instead of substrings it makes subarrays. +It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. For instance: @@ -206,7 +206,7 @@ The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for ever The syntax: ```js arr.forEach(function(item, index, array) { - // ... do something with item + // ... do something with an item }); ``` @@ -239,7 +239,7 @@ The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/ - `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`. - `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found. -Usually these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. +Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. For instance: @@ -255,15 +255,15 @@ alert( arr.includes(1) ); // true Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero. -If we want to check if `item` exists in the array, and don't need the exact index, then `arr.includes` is preferred. +If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred. The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left. ```js run let fruits = ['Apple', 'Orange', 'Apple'] -alert( arr.indexOf('Apple') ); // 0 (first Apple) -alert( arr.lastIndexOf('Apple') ); // 2 (last Apple) +alert( fruits.indexOf('Apple') ); // 0 (first Apple) +alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) ``` ````smart header="The `includes` method handles `NaN` correctly" @@ -274,12 +274,12 @@ const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0) alert( arr.includes(NaN) );// true (correct) ``` -That's because `includes` was added to JavaScript much later and uses the more up to date comparison algorithm internally. +That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. ```` ### find and findIndex/findLastIndex -Imagine we have an array of objects. How do we find an object with the specific condition? +Imagine we have an array of objects. How do we find an object with a specific condition? Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy. @@ -297,7 +297,7 @@ The function is called for elements of the array, one after another: - `index` is its index. - `array` is the array itself. -If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned. +If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`: @@ -313,11 +313,11 @@ let user = users.find(item => item.id == 1); alert(user.name); // John ``` -In real life arrays of objects is a common thing, so the `find` method is very useful. +In real life, arrays of objects are a common thing, so the `find` method is very useful. Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. That's typical, other arguments of this function are rarely used. -The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax, but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. +The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`. @@ -338,8 +338,6 @@ alert(users.findIndex(user => user.name == 'John')); // 0 alert(users.findLastIndex(user => user.name == 'John')); // 3 ``` - - ### filter The `find` method looks for a single (first) element that makes the function return `true`. @@ -452,11 +450,11 @@ alert(arr); // *!*1, 2, 15*/!* Now it works as intended. -Let's step aside and think what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. +Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. -By the way, if we ever want to know which elements are compared -- nothing prevents from alerting them: +By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -528,7 +526,7 @@ Here's the situation from real life. We are writing a messaging app, and the per The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`. -In the example below, we split by a comma followed by space: +In the example below, we split by a comma followed by a space: ```js run let names = 'Bilbo, Gandalf, Nazgul'; @@ -595,9 +593,9 @@ Arguments: - `index` -- is its position. - `array` -- is the array. -As function is applied, the result of the previous function call is passed to the next one as the first argument. +As the function is applied, the result of the previous function call is passed to the next one as the first argument. -So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end it becomes the result of `reduce`. +So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`. Sounds complicated? @@ -666,7 +664,7 @@ arr.reduce((sum, current) => sum + current); So it's advised to always specify the initial value. -The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left. +The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same but goes from right to left. ## Array.isArray @@ -691,7 +689,7 @@ alert(Array.isArray([])); // true Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`. -That parameter is not explained in the sections above, because it's rarely used. But for completeness we have to cover it. +That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it. Here's the full syntax of these methods: @@ -751,7 +749,7 @@ A cheat sheet of array methods: - `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken. - To search among elements: - - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, return the index or `-1` if not found. + - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found. - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. - `findIndex` is like `find`, but returns the index instead of a value. @@ -797,7 +795,7 @@ These methods are the most used ones, they cover 99% of use cases. But there are For the full list, see the [manual](mdn:js/Array). -From the first sight it may seem that there are so many methods, quite difficult to remember. But actually that's much easier. +At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. Look through the cheat sheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index ce9074cc7..e2c0d4f97 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -174,7 +174,7 @@ When we use JavaScript for practical tasks in a browser or any other environment For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`). -But an iterable may be not array-like. And vice versa an array-like may be not iterable. +But an iterable may not be array-like. And vice versa an array-like may not be iterable. For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`. @@ -218,7 +218,7 @@ alert(arr.pop()); // World (method works) The same happens for an iterable: -```js +```js run // assuming that range is taken from the example above let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (array toString conversion works) @@ -233,7 +233,7 @@ The optional second argument `mapFn` can be a function that will be applied to e For instance: -```js +```js run // assuming that range is taken from the example above // square each number 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 bd6cad562..37f5e48c2 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -10,17 +10,17 @@ But that's not enough for real life. That's why `Map` and `Set` also exist. ## Map -[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. Methods and properties are: -- `new Map()` -- creates the map. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element (the key/value pair) by the key. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. For instance: @@ -100,14 +100,13 @@ map.set('1', 'str1') ``` ```` - ## Iteration over Map For looping over a `map`, there are 3 methods: -- `map.keys()` -- returns an iterable for keys, -- `map.values()` -- returns an iterable for values, -- `map.entries()` -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- returns an iterable for keys, +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- returns an iterable for values, +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. For instance: @@ -162,7 +161,7 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format. +If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) that returns an array of key/value pairs for an object exactly in that format. So we can create a map from an object like this: @@ -233,16 +232,16 @@ That's the same, because `Object.fromEntries` expects an iterable object as the ## Set -A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once. +A [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) is a special type collection - "set of values" (without keys), where each value may occur only once. Its main methods are: -- `new Set(iterable)` -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. -- `set.add(value)` -- adds a value, returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value, returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. The main feature is that repeated calls of `set.add(value)` with the same value don't do anything. That's the reason why each value appears in a `Set` only once. @@ -272,7 +271,7 @@ for (let user of set) { } ``` -The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. +The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. ## Iteration over Set @@ -291,42 +290,42 @@ set.forEach((value, valueAgain, set) => { Note the funny thing. The callback function passed in `forEach` has 3 arguments: a `value`, then *the same value* `valueAgain`, and then the target object. Indeed, the same value appears in the arguments twice. -That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But this may help to replace `Map` with `Set` in certain cases with ease, and vice versa. The same methods `Map` has for iterators are also supported: -- `set.keys()` -- returns an iterable object for values, -- `set.values()` -- same as `set.keys()`, for compatibility with `Map`, -- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- returns an iterable object for values, +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- same as `set.keys()`, for compatibility with `Map`, +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. ## Summary -`Map` -- is a collection of keyed values. +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- is a collection of keyed values. Methods and properties: -- `new Map([iterable])` -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. -- `map.set(key, value)` -- stores the value by the key, 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, 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. +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key, returns the map itself. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. The differences from a regular `Object`: - Any keys, objects can be keys. - Additional convenient methods, the `size` property. -`Set` -- is a collection of unique values. +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- is a collection of unique values. Methods and properties: -- `new Set([iterable])` -- creates the set, with optional `iterable` (e.g. array) of values for initialization. -- `set.add(value)` -- adds a value (does nothing if `value` exists), returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, with optional `iterable` (e.g. array) of values for initialization. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value (does nothing if `value` exists), returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. Iteration over `Map` and `Set` is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 8d5a86981..9795017d4 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,8 +1,10 @@ + # WeakMap and WeakSet As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. For instance: + ```js let john = { name: "John" }; @@ -54,13 +56,13 @@ john = null; // overwrite the reference */!* ``` -`WeakMap` is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. Let's see what it means on examples. ## WeakMap -The first difference between `Map` and `WeakMap` is that keys must be objects, not primitive values: +The first difference between [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is that keys must be objects, not primitive values: ```js run let weakMap = new WeakMap(); @@ -94,10 +96,10 @@ Compare it with the regular `Map` example above. Now if `john` only exists as th `WeakMap` has only the following methods: -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` +- [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) +- [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) +- [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) +- [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) 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*. @@ -182,6 +184,7 @@ function process(obj) { let result = /* calculations of the result for */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -221,6 +224,7 @@ function process(obj) { let result = /* calculate the result for */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -242,11 +246,11 @@ obj = null; ## WeakSet -`WeakSet` behaves similarly: +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) behaves similarly: - It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). - An object exists in the set while it is reachable from somewhere else. -- Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations. +- Like `Set`, it supports [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) and [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), but not `size`, `keys()` and no iterations. Being "weak", it also serves as additional storage. But not for arbitrary data, rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. @@ -280,9 +284,9 @@ The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterati ## Summary -`WeakMap` is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. -`WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector. 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 41e36db2c..0c52741d1 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -5,18 +5,18 @@ 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. - Arrays allow us to gather data items into an ordered list. -Although, when we pass those to a function, it may need not be an object/array as a whole. It may need individual pieces. +However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties. *Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. -Destructuring also works great with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. +Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. ## Array destructuring Here's an example of how an array is destructured into variables: ```js -// we have an array with the name and surname +// we have an array with a name and surname let arr = ["John", "Smith"] *!* @@ -40,10 +40,10 @@ 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. +As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better. ````smart header="\"Destructuring\" does not mean \"destructive\"." -It's called "destructuring assignment," because it "destructurizes" by copying items into variables. But the array itself is not modified. +It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified. It's just a shorter way to write: ```js @@ -65,7 +65,7 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic alert( title ); // Consul ``` -In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items is also skipped (as there are no variables for them). +In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them). ```` ````smart header="Works with any iterable on the right-side" @@ -95,9 +95,9 @@ alert(user.surname); // Smith ```` ````smart header="Looping with .entries()" -In the previous chapter we saw the [Object.entries(obj)](mdn:js/Object/entries) method. +In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method. -We can use it with destructuring to loop over keys-and-values of an object: +We can use it with destructuring to loop over the keys-and-values of an object: ```js run let user = { @@ -105,7 +105,7 @@ let user = { age: 30 }; -// loop over keys-and-values +// loop over the keys-and-values *!* for (let [key, value] of Object.entries(user)) { */!* @@ -169,7 +169,7 @@ If we'd like also to gather all that follows -- we can add one more parameter th let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* -// rest is array of items, starting from the 3rd one +// rest is an array of items, starting from the 3rd one alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 @@ -187,7 +187,7 @@ let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Ro ### Default values -If the array is shorter than the list of variables at the left, there'll be no errors. Absent values are considered undefined: +If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined: ```js run *!* @@ -418,7 +418,7 @@ alert( title ); // Menu ## Nested destructuring -If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. +If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern on the left side of the assignment has the same structure to extract values from them: @@ -449,7 +449,7 @@ alert(item1); // Cake alert(item2); // Donut ``` -All properties of `options` object except `extra` that is absent in the left part, are assigned to corresponding variables: +All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables: ![](destructuring-complex.svg) @@ -459,9 +459,9 @@ Note that there are no variables for `size` and `items`, as we take their conten ## Smart function parameters -There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on. +There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, an item list and so on. -Here's a bad way to write such function: +Here's a bad way to write such a function: ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -469,7 +469,7 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` -In real-life, the problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. +In real-life, the problem is how to remember the order of arguments. Usually, IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. Like this? @@ -534,7 +534,7 @@ function({ }) ``` -Then, for an object of parameters, there will be a variable `varName` for property `incomingProperty`, with `defaultValue` by default. +Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object: @@ -561,7 +561,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw - Destructuring assignment allows for instantly mapping an object or array onto many variables. - The full object syntax: ```js - let {prop : varName = default, ...rest} = object + let {prop : varName = defaultValue, ...rest} = object ``` This means that property `prop` should go into the variable `varName` and, if no such property exists, then the `default` value should be used. @@ -571,9 +571,9 @@ In the code above, the whole arguments object is `{}` by default, so there's alw - The full array syntax: ```js - let [item1 = default, item2, ...rest] = array + let [item1 = defaultValue, item2, ...rest] = array ``` - The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`. + The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`. - It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one. diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index 2266c0779..6958a3a97 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -376,7 +376,7 @@ for (let i = 0; i < 10; i++) { ```warn header="Be careful doing microbenchmarking" Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. -The great pack of articles about V8 can be found at . +The great pack of articles about V8 can be found at . ``` ## Date.parse from a string diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index 503745356..133ffb353 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -405,7 +405,7 @@ To decode a JSON-string, we need another method named [JSON.parse](mdn:js/JSON/p The syntax: ```js -let value = JSON.parse(str, [reviver]); +let value = JSON.parse(str[, reviver]); ``` str @@ -451,7 +451,7 @@ let json = `{ Besides, JSON does not support comments. Adding a comment to JSON makes it invalid. -There's another format named [JSON5](http://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. +There's another format named [JSON5](https://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. The regular JSON is that strict not because its developers are lazy, but to allow easy, reliable and very fast implementations of the parsing algorithm. diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index 3a281ef3f..11667f940 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -37,4 +37,4 @@ P.S. Naturally, the formula is the fastest solution. It uses only 3 operations f The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower. -P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory, so counting `sumTo(100000)` becomes possible. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function, with no other calculations performed, then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index b7992f162..5ae894474 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -61,7 +61,7 @@ When `pow(x, n)` is called, the execution splits into two branches: if n==1 = x / pow(x, n) = - \ + \ else = x * pow(x, n - 1) ``` @@ -285,7 +285,7 @@ The iterative `pow` uses a single context changing `i` and `result` in the proce **Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.** -...But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. +...But sometimes the rewrite is non-trivial, especially when a function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used. diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md index c63fe70cd..dbdfbd6c0 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -23,7 +23,7 @@ function sum(a, b) { alert( sum(1, 2, 3, 4, 5) ); ``` -There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted. +There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted, so the result in the code above is `3`. The rest of the parameters can be included in the function definition by using three dots `...` followed by the name of the array that will contain them. The dots literally mean "gather the remaining parameters into an array". diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 1579afb62..28d7a76ec 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -58,7 +58,7 @@ alert(test); // ReferenceError: test is not defined The same thing for loops: `var` cannot be block- or loop-local: -```js +```js run for (var i = 0; i < 10; i++) { var one = 1; // ... @@ -170,7 +170,7 @@ That's best demonstrated with an example: ```js run function sayHi() { - alert(phrase); + alert(phrase); *!* var phrase = "Hello"; 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 fa1d98cb9..f96959988 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -27,7 +27,7 @@ Usually, that's a function. For historical reasons, a string of code can be pass : The delay before run, in milliseconds (1000 ms = 1 second), by default 0. `arg1`, `arg2`... -: Arguments for the function (not supported in IE9-) +: Arguments for the function For instance, this code calls `sayHi()` after one second: @@ -102,7 +102,7 @@ As we can see from `alert` output, in a browser the timer identifier is a number Again, there is no universal specification for these methods, so that's fine. -For browsers, timers are described in the [timers section](https://www.w3.org/TR/html5/webappapis.html#timers) of HTML5 standard. +For browsers, timers are described in the [timers section](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) of HTML Living Standard. ## setInterval @@ -256,7 +256,7 @@ The first line "puts the call into calendar after 0ms". But the scheduler will o There are also advanced browser-related use cases of zero-delay timeout, that we'll discuss in the chapter . ````smart header="Zero delay is in fact not zero (in a browser)" -In the browser, there's a limitation of how often nested timers can run. The [HTML5 standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". +In the browser, there's a limitation of how often nested timers can run. The [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". Let's demonstrate what it means with the example below. The `setTimeout` call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the `times` array. What do the real delays look like? Let's see: diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md index 403107ca6..4a381c0b4 100644 --- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md +++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md @@ -1,5 +1,5 @@ -The error occurs because `ask` gets functions `loginOk/loginFail` without the object. +The error occurs because `askPassword` gets functions `loginOk/loginFail` without the object. When it calls them, they naturally assume `this=undefined`. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 9d705cdcd..6d65e7dd1 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -202,7 +202,7 @@ for (let key in user) { } ``` -JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll) in lodash. +JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) in lodash. ```` ## Partial functions 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 bdc693418..0a945b377 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name' Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours. ```smart header="Errors appear only in strict mode" -In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. ``` Here's the same example, but the property is created from scratch: diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index 71f118e1b..34b977e9f 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -14,7 +14,7 @@ The only usage of `__proto__`, that's not frowned upon, is as a property when cr Although, there's a special method for this too: -- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. For instance: @@ -201,7 +201,7 @@ alert(Object.keys(chineseDictionary)); // hello,bye - To create an object with the given prototype, use: - literal syntax: `{ __proto__: ... }`, allows to specify multiple properties - - or [Object.create(proto, [descriptors])](mdn:js/Object/create), allows to specify property descriptors. + - or [Object.create(proto[, descriptors])](mdn:js/Object/create), allows to specify property descriptors. The `Object.create` provides an easy way to shallow-copy an object with all descriptors: diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 06001d900..526b832ef 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -103,7 +103,7 @@ Here's the diagram (see the right part): That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above. -As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. +As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`. ## EventMixin 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 a928da289..bf548373a 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -632,7 +632,7 @@ For instance: The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers. -There are also web-services that provide error-logging for such cases, like or . +There are also web-services that provide error-logging for such cases, like or . They work like this: 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 918289319..d28b07439 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -38,7 +38,7 @@ class Error { Now let's inherit `ValidationError` from it and try it in action: -```js run untrusted +```js run *!* class ValidationError extends Error { */!* diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index fbb425d47..b15643f0a 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -36,7 +36,7 @@ So to summarize: the executor runs automatically and attempts to perform a job. The `promise` object returned by the `new Promise` constructor has these internal properties: - `state` — initially `"pending"`, then changes to either `"fulfilled"` when `resolve` is called or `"rejected"` when `reject` is called. -- `result` — initially `undefined`, then changes to `value` when `resolve(value)` called or `error` when `reject(error)` is called. +- `result` — initially `undefined`, then changes to `value` when `resolve(value)` is called or `error` when `reject(error)` is called. So the executor eventually moves `promise` to one of these states: @@ -46,7 +46,7 @@ Later we'll see how "fans" can subscribe to these changes. Here's an example of a promise constructor and a simple executor function with "producing code" that takes time (via `setTimeout`): -```js run +```js let promise = new Promise(function(resolve, reject) { // the function is executed automatically when the promise is constructed @@ -281,7 +281,7 @@ To summarize: - If a `finally` handler returns something, it's ignored. - When `finally` throws an error, then the execution goes to the nearest error handler. -These features are helpful and make things work just the right way if we `finally` how it's supposed to be used: for generic cleanup procedures. +These features are helpful and make things work just the right way if we use `finally` how it's supposed to be used: for generic cleanup procedures. ````smart header="We can attach handlers to settled promises" If a promise is pending, `.then/catch/finally` handlers wait for its outcome. diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md index ebf869156..a33ca258c 100644 --- a/1-js/11-async/03-promise-chaining/article.md +++ b/1-js/11-async/03-promise-chaining/article.md @@ -72,7 +72,7 @@ promise.then(function(result) { }); ``` -What we did here is just several handlers to one promise. They don't pass the result to each other; instead they process it independently. +What we did here is just adding several handlers to one promise. They don't pass the result to each other; instead they process it independently. Here's the picture (compare it with the chaining above): @@ -224,7 +224,7 @@ This feature allows us to integrate custom objects with promise chains without h ## Bigger example: fetch -In frontend programming promises are often used for network requests. So let's see an extended example of that. +In frontend programming, promises are often used for network requests. So let's see an extended example of that. We'll use the [fetch](info:fetch) method to load the information about the user from the remote server. It has a lot of optional parameters covered in [separate chapters](info:fetch), but the basic syntax is quite simple: diff --git a/1-js/11-async/06-promisify/article.md b/1-js/11-async/06-promisify/article.md index 1d81b31a6..855678e5b 100644 --- a/1-js/11-async/06-promisify/article.md +++ b/1-js/11-async/06-promisify/article.md @@ -25,7 +25,7 @@ function loadScript(src, callback) { The function loads a script with the given `src`, and then calls `callback(err)` in case of an error, or `callback(null, script)` in case of successful loading. That's a widespread agreement for using callbacks, we saw it before. -Let's promisify it. +Let's promisify it. We'll make a new function `loadScriptPromise(src)`, that does the same (loads the script), but returns a promise instead of using callbacks. @@ -124,7 +124,7 @@ For more exotic callback formats, like those without `err` at all: `callback(res There are also modules with a bit more flexible promisification functions, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). In Node.js, there's a built-in `util.promisify` function for that. ```smart -Promisification is a great approach, especially when you use `async/await` (see the next chapter), but not a total replacement for callbacks. +Promisification is a great approach, especially when you use `async/await` (covered later in the chapter ), but not a total replacement for callbacks. Remember, a promise may have only one result, but a callback may technically be called many times. diff --git a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md index af2ad0eed..4355d0cfc 100644 --- a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md +++ b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md @@ -3,7 +3,7 @@ function* pseudoRandom(seed) { let value = seed; while(true) { - value = value * 16807 % 2147483647 + value = value * 16807 % 2147483647; yield value; } diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index e9b7272b9..5ad70d151 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -9,11 +9,11 @@ But eventually scripts became more and more complex, so the community invented a To name some (for historical reasons): -- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](http://requirejs.org/). -- [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.js server. +- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](https://requirejs.org/). +- [CommonJS](https://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.js server. - [UMD](https://github.com/umdjs/umd) -- one more module system, suggested as a universal one, compatible with AMD and CommonJS. -Now all these slowly become a part of history, but we still can find them in old scripts. +Now these all slowly became a part of history, but we still can find them in old scripts. The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js. So we'll study the modern JavaScript modules from now on. diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 8af1b3b10..1b5649c69 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -46,7 +46,7 @@ Also, we can put `export` separately. Here we first declare, and then export: -```js +```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); @@ -93,25 +93,14 @@ At first sight, "import everything" seems such a cool thing, short to write, why Well, there are few reasons. -1. Modern build tools ([webpack](https://webpack.js.org/) and others) bundle modules together and optimize them to speedup loading and remove unused stuff. - - Let's say, we added a 3rd-party library `say.js` to our project with many functions: - ```js - // 📁 say.js - export function sayHi() { ... } - export function sayBye() { ... } - export function becomeSilent() { ... } - ``` +1. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. +2. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. - Now if we only use one of `say.js` functions in our project: - ```js - // 📁 main.js - import {sayHi} from './say.js'; - ``` - ...Then the optimizer will see that and remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking". +```smart header="Don't be afraid to import too much" +Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also remove unused imports. -2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. -3. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. +For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimized bundle. +``` ## Import "as" @@ -224,7 +213,7 @@ Without `default`, such an export would give an error: export class { // Error! (non-default export needs a name) constructor() {} } -``` +``` ### The "default" name @@ -326,7 +315,7 @@ Imagine, we're writing a "package": a folder with a lot of modules, with some of The file structure could be like this: ``` auth/ - index.js + index.js user.js helpers.js tests/ @@ -372,7 +361,7 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo ```js // 📁 auth/index.js -// re-export login/logout +// re-export login/logout export {login, logout} from './helpers.js'; // re-export the default export as User @@ -380,7 +369,7 @@ 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. +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 @@ -399,7 +388,7 @@ We can come across two problems with it: 1. `export User from './user.js'` won't work. That would lead to a syntax error. - To re-export the default export, we have to write `export {default as User}`, as in the example above. + To re-export the default export, we have to write `export {default as User}`, as in the example above. 2. `export * from './user.js'` re-exports only named exports, but ignores the default one. @@ -430,7 +419,7 @@ Import: - Importing named exports: - `import {x [as y], ...} from "module"` -- Importing the default export: +- Importing the default export: - `import x from "module"` - `import {default as x} from "module"` - Import all: diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md index 6861837a9..894db8fc6 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -87,7 +87,7 @@ The result of a property access `user.hi` is not a function, but a value of Refe (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). +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 `()`. diff --git a/1-js/99-js-misc/06-unicode/article.md b/1-js/99-js-misc/06-unicode/article.md new file mode 100644 index 000000000..4f144f824 --- /dev/null +++ b/1-js/99-js-misc/06-unicode/article.md @@ -0,0 +1,172 @@ + +# Unicode, String internals + +```warn header="Advanced knowledge" +The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters, or other rare symbols. +``` + +As we already know, JavaScript strings are based on [Unicode](https://en.wikipedia.org/wiki/Unicode): each character is represented by a byte sequence of 1-4 bytes. + +JavaScript allows us to insert a character into a string by specifying its hexadecimal Unicode code with one of these three notations: + +- `\xXX` + + `XX` must be two hexadecimal digits with a value between `00` and `FF`, then `\xXX` is the character whose Unicode code is `XX`. + + Because the `\xXX` notation supports only two hexadecimal digits, it can be used only for the first 256 Unicode characters. + + These first 256 characters include the Latin alphabet, most basic syntax characters, and some others. For example, `"\x7A"` is the same as `"z"` (Unicode `U+007A`). + + ```js run + alert( "\x7A" ); // z + alert( "\xA9" ); // ©, the copyright symbol + ``` + +- `\uXXXX` + `XXXX` must be exactly 4 hex digits with the value between `0000` and `FFFF`, then `\uXXXX` is the character whose Unicode code is `XXXX`. + + Characters with Unicode values greater than `U+FFFF` can also be represented with this notation, but in this case, we will need to use a so called surrogate pair (we will talk about surrogate pairs later in this chapter). + + ```js run + alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation + alert( "\u044F" ); // я, the Cyrillic alphabet letter + alert( "\u2191" ); // ↑, the arrow up symbol + ``` + +- `\u{X…XXXXXX}` + + `X…XXXXXX` must be a hexadecimal value of 1 to 6 bytes between `0` and `10FFFF` (the highest code point defined by Unicode). This notation allows us to easily represent all existing Unicode characters. + + ```js run + alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode) + alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) + ``` + +## Surrogate pairs + +All frequently used characters have 2-byte codes (4 hex digits). Letters in most European languages, numbers, and the basic unified CJK ideographic sets (CJK -- from Chinese, Japanese, and Korean writing systems), have a 2-byte representation. + +Initially, JavaScript was based on UTF-16 encoding that only allowed 2 bytes per character. But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol of Unicode. + +So rare symbols that require more than 2 bytes are encoded with a pair of 2-byte characters called "a surrogate pair". + +As a side effect, the length of such symbols is `2`: + +```js run +alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X +alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY +alert( '𩷶'.length ); // 2, a rare Chinese character +``` + +That's because surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! + +We actually have a single symbol in each of the strings above, but the `length` property shows a length of `2`. + +Getting a symbol can also be tricky, because most language features treat surrogate pairs as two characters. + +For example, here we can see two odd characters in the output: + +```js run +alert( '𝒳'[0] ); // shows strange symbols... +alert( '𝒳'[1] ); // ...pieces of the surrogate pair +``` + +Pieces of a surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. + +Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. + +So the methods [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) and [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) were added in JavaScript to deal with surrogate pairs. + +They are essentially the same as [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt), but they treat surrogate pairs correctly. + +One can see the difference here: + +```js run +// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of 𝒳: + +alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 + +// codePointAt is surrogate-pair aware +alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair +``` + +That said, if we take from position 1 (and that's rather incorrect here), then they both return only the 2nd part of the pair: + +```js run +alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 +alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 +// meaningless 2nd half of the pair +``` + +You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. + +````warn header="Takeaway: splitting strings at an arbitrary point is dangerous" +We can't just split a string at an arbitrary position, e.g. take `str.slice(0, 4)` and expect it to be a valid string, e.g.: + +```js run +alert( 'hi 😂'.slice(0, 4) ); // hi [?] +``` + +Here we can see a garbage character (first half of the smile surrogate pair) in the output. + +Just be aware of it if you intend to reliably work with surrogate pairs. May not be a big problem, but at least you should understand what happens. +```` + +## Diacritical marks and normalization + +In many languages, there are symbols that are composed of the base character with a mark above/under it. + +For instance, the letter `a` can be the base character for these characters: `àáâäãåā`. + +Most common "composite" characters have their own code in the Unicode table. But not all of them, because there are too many possible combinations. + +To support arbitrary compositions, the Unicode standard allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it. + +For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. + +```js run +alert( 'S\u0307' ); // Ṡ +``` + +If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. + +For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. + +For example: + +```js run +alert( 'S\u0307\u0323' ); // Ṩ +``` + +This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. + +For instance: + +```js run +let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below +let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above + +alert( `s1: ${s1}, s2: ${s2}` ); + +alert( s1 == s2 ); // false though the characters look identical (?!) +``` + +To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. + +It is implemented by [str.normalize()](mdn:js/String/normalize). + +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` + +It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +In reality, this is not always the case. The reason is that the symbol `Ṩ` is "common enough", so Unicode creators included it in the main table and gave it the code. + +If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md new file mode 100644 index 000000000..777bf703c --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md @@ -0,0 +1,483 @@ + +# WeakRef and FinalizationRegistry + +```warn header="\"Hidden\" features of the language" +This article covers a very narrowly focused topic, that most developers extremely rarely encounter in practice (and may not even be aware of its existence). + +We recommend skipping this chapter if you have just started learning JavaScript. +``` + +Recalling the basic concept of the *reachability principle* from the chapter, +we can note that the JavaScript engine is guaranteed to keep values in memory that are accessible or in use. + +For example: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// let's overwrite the value of the user variable +user = null; + +// the reference is lost and the object will be deleted from memory + +``` + +Or a similar, but slightly more complicated code with two strong references: + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// copied the strong reference to the object into the admin variable +*!* +let admin = user; +*/!* + +// let's overwrite the value of the user variable +user = null; + +// the object is still reachable through the admin variable +``` +The object `{ name: "John" }` would only be deleted from memory if there were no strong references to it (if we also overwrote the value of the `admin` variable). + +In JavaScript, there is a concept called `WeakRef`, which behaves slightly differently in this case. + + +````smart header="Terms: \"Strong reference\", \"Weak reference\"" +**Strong reference** - is a reference to an object or value, that prevents them from being deleted by the garbage collector. Thereby, keeping the object or value in memory, to which it points. + +This means, that the object or value remains in memory and is not collected by the garbage collector as long, as there are active strong references to it. + +In JavaScript, ordinary references to objects are strong references. For example: + +```js +// the user variable holds a strong reference to this object +let user = { name: "John" }; +``` +**Weak reference** - is a reference to an object or value, that does *not* prevent them from being deleted by the garbage collector. +An object or value can be deleted by the garbage collector if, the only remaining references to them are weak references. +```` + +## WeakRef + + +````warn header="Note of caution" +Before we dive into it, it is worth noting that the correct use of the structures discussed in this article requires very careful thought, and they are best avoided if possible. +```` + +`WeakRef` - is an object, that contains a weak reference to another object, called `target` or `referent`. + +The peculiarity of `WeakRef` is that it does not prevent the garbage collector from deleting its referent-object. In other words, a `WeakRef` object does not keep the `referent` object alive. + +Now let's take the `user` variable as the "referent" and create a weak reference from it to the `admin` variable. +To create a weak reference, you need to use the `WeakRef` constructor, passing in the target object (the object you want a weak reference to). + +In our case — this is the `user` variable: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// the admin variable holds a weak reference to the object +*!* +let admin = new WeakRef(user); +*/!* + +``` + +The diagram below depicts two types of references: a strong reference using the `user` variable and a weak reference using the `admin` variable: + +![](weakref-finalizationregistry-01.svg) + +Then, at some point, we stop using the `user` variable - it gets overwritten, goes out of scope, etc., while keeping the `WeakRef` instance in the `admin` variable: + +```js +// let's overwrite the value of the user variable +user = null; +``` + +A weak reference to an object is not enough to keep it "alive". When the only remaining references to a referent-object are weak references, the garbage collector is free to destroy this object and use its memory for something else. + +However, until the object is actually destroyed, the weak reference may return it, even if there are no more strong references to this object. +That is, our object becomes a kind of "[Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat)" – we cannot know for sure whether it's "alive" or "dead": + +![](weakref-finalizationregistry-02.svg) + +At this point, to get the object from the `WeakRef` instance, we will use its `deref()` method. + +The `deref()` method returns the referent-object that the `WeakRef` points to, if the object is still in memory. If the object has been deleted by the garbage collector, then the `deref()` method will return `undefined`: + + +```js +let ref = admin.deref(); + +if (ref) { + // the object is still accessible: we can perform any manipulations with it +} else { + // the object has been collected by the garbage collector +} +``` + +## WeakRef use cases + +`WeakRef` is typically used to create caches or [associative arrays](https://en.wikipedia.org/wiki/Associative_array) that store resource-intensive objects. +This allows one to avoid preventing these objects from being collected by the garbage collector solely based on their presence in the cache or associative array. + +One of the primary examples - is a situation when we have numerous binary image objects (for instance, represented as `ArrayBuffer` or `Blob`), and we want to associate a name or path with each image. +Existing data structures are not quite suitable for these purposes: + +- Using `Map` to create associations between names and images, or vice versa, will keep the image objects in memory since they are present in the `Map` as keys or values. +- `WeakMap` is ineligible for this goal either: because the objects represented as `WeakMap` keys use weak references, and are not protected from deletion by the garbage collector. + +But, in this situation, we need a data structure that would use weak references in its values. + +For this purpose, we can use a `Map` collection, whose values are `WeakRef` instances referring to the large objects we need. +Consequently, we will not keep these large and unnecessary objects in memory longer than they should be. + +Otherwise, this is a way to get the image object from the cache if it is still reachable. +If it has been garbage collected, we will re-generate or re-download it again. + +This way, less memory is used in some situations. + +## Example №1: using WeakRef for caching + +Below is a code snippet that demonstrates the technique of using `WeakRef`. + +In short, we use a `Map` with string keys and `WeakRef` objects as their values. +If the `WeakRef` object has not been collected by the garbage collector, we get it from the cache. +Otherwise, we re-download it again and put it in the cache for further possible reuse: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { // (1) + const imgCache = new Map(); // (2) + + return (imgName) => { // (3) + const cachedImg = imgCache.get(imgName); // (4) + + if (cachedImg?.deref()) { // (5) + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); // (6) + imgCache.set(imgName, new WeakRef(newImg)); // (7) + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +Let's delve into the details of what happened here: +1. `weakRefCache` - is a higher-order function that takes another function, `fetchImg`, as an argument. In this example, we can neglect a detailed description of the `fetchImg` function, since it can be any logic for downloading images. +2. `imgCache` - is a cache of images, that stores cached results of the `fetchImg` function, in the form of string keys (image name) and `WeakRef` objects as their values. +3. Return an anonymous function that takes the image name as an argument. This argument will be used as a key for the cached image. +4. Trying to get the cached result from the cache, using the provided key (image name). +5. If the cache contains a value for the specified key, and the `WeakRef` object has not been deleted by the garbage collector, return the cached result. +6. If there is no entry in the cache with the requested key, or `deref()` method returns `undefined` (meaning that the `WeakRef` object has been garbage collected), the `fetchImg` function downloads the image again. +7. Put the downloaded image into the cache as a `WeakRef` object. + +Now we have a `Map` collection, where the keys - are image names as strings, and values - are `WeakRef` objects containing the images themselves. + +This technique helps to avoid allocating a large amount of memory for resource-intensive objects, that nobody uses anymore. +It also saves memory and time in case of reusing cached objects. + +Here is a visual representation of what this code looks like: + +![](weakref-finalizationregistry-03.svg) + +But, this implementation has its drawbacks: over time, `Map` will be filled with strings as keys, that point to a `WeakRef`, whose referent-object has already been garbage collected: + +![](weakref-finalizationregistry-04.svg) + +One way to handle this problem - is to periodically scavenge the cache and clear out "dead" entries. +Another way - is to use finalizers, which we will explore next. + + +## Example №2: Using WeakRef to track DOM objects + +Another use case for `WeakRef` - is tracking DOM objects. + +Let's imagine a scenario where some third-party code or library interacts with elements on our page as long as they exist in the DOM. +For example, it could be an external utility for monitoring and notifying about the system's state (commonly so-called "logger" – a program that sends informational messages called "logs"). + +Interactive example: + +[codetabs height=420 src="weakref-dom"] + +When the "Start sending messages" button is clicked, in the so-called "logs display window" (an element with the `.window__body` class), messages (logs) start to appear. + +But, as soon as this element is deleted from the DOM, the logger should stop sending messages. +To reproduce the removal of this element, just click the "Close" button in the top right corner. + +In order not to complicate our work, and not to notify third-party code every time our DOM-element is available, and when it is not, it will be enough to create a weak reference to it using `WeakRef`. + +Once the element is removed from the DOM, the logger will notice it and stop sending messages. + +Now let's take a closer look at the source code (*tab `index.js`*): + +1. Get the DOM-element of the "Start sending messages" button. +2. Get the DOM-element of the "Close" button. +3. Get the DOM-element of the logs display window using the `new WeakRef()` constructor. This way, the `windowElementRef` variable holds a weak reference to the DOM-element. +4. Add an event listener on the "Start sending messages" button, responsible for starting the logger when clicked. +5. Add an event listener on the "Close" button, responsible for closing the logs display window when clicked. +6. Use `setInterval` to start displaying a new message every second. +7. If the DOM-element of the logs display window is still accessible and kept in memory, create and send a new message. +8. If the `deref()` method returns `undefined`, it means that the DOM-element has been deleted from memory. In this case, the logger stops displaying messages and clears the timer. +9. `alert`, which will be called, after the DOM-element of the logs display window is deleted from memory (i.e. after clicking the "Close" button). **Note, that deletion from memory may not happen immediately, as it depends only on the internal mechanisms of the garbage collector.** + + We cannot control this process directly from the code. However, despite this, we still have the option to force garbage collection from the browser. + + In Google Chrome, for example, to do this, you need to open the developer tools (`key:Ctrl` + `key:Shift` + `key:J` on Windows/Linux or `key:Option` + `key:⌘` + `key:J` on macOS), go to the "Performance" tab, and click on the bin icon button – "Collect garbage": + + ![](google-chrome-developer-tools.png) + +
+ This functionality is supported in most modern browsers. After the actions are taken, the alert will trigger immediately. + +## FinalizationRegistry + +Now it is time to talk about finalizers. Before we move on, let's clarify the terminology: + +**Cleanup callback (finalizer)** - is a function that is executed, when an object, registered in the `FinalizationRegistry`, is deleted from memory by the garbage collector. + +Its purpose - is to provide the ability to perform additional operations, related to the object, after it has been finally deleted from memory. + +**Registry** (or `FinalizationRegistry`) - is a special object in JavaScript that manages the registration and unregistration of objects and their cleanup callbacks. + +This mechanism allows registering an object to track and associate a cleanup callback with it. +Essentially it is a structure that stores information about registered objects and their cleanup callbacks, and then automatically invokes those callbacks when the objects are deleted from memory. + +To create an instance of the `FinalizationRegistry`, it needs to call its constructor, which takes a single argument - the cleanup callback (finalizer). + +Syntax: + +```js +function cleanupCallback(heldValue) { + // cleanup callback code +} + +const registry = new FinalizationRegistry(cleanupCallback); +``` + +Here: + +- `cleanupCallback` - a cleanup callback that will be automatically called when a registered object is deleted from memory. +- `heldValue` - the value that is passed as an argument to the cleanup callback. If `heldValue` is an object, the registry keeps a strong reference to it. +- `registry` - an instance of `FinalizationRegistry`. + +`FinalizationRegistry` methods: + +- `register(target, heldValue [, unregisterToken])` - used to register objects in the registry. + + `target` - the object being registered for tracking. If the `target` is garbage collected, the cleanup callback will be called with `heldValue` as its argument. + + Optional `unregisterToken` – an unregistration token. It can be passed to unregister an object before the garbage collector deletes it. Typically, the `target` object is used as `unregisterToken`, which is the standard practice. +- `unregister(unregisterToken)` - the `unregister` method is used to unregister an object from the registry. It takes one argument - `unregisterToken` (the unregister token that was obtained when registering the object). + +Now let's move on to a simple example. Let's use the already-known `user` object and create an instance of `FinalizationRegistry`: + +```js +let user = { name: "John" }; + +const registry = new FinalizationRegistry((heldValue) => { + console.log(`${heldValue} has been collected by the garbage collector.`); +}); +``` + +Then, we will register the object, that requires a cleanup callback by calling the `register` method: + +```js +registry.register(user, user.name); +``` + +The registry does not keep a strong reference to the object being registered, as this would defeat its purpose. If the registry kept a strong reference, then the object would never be garbage collected. + +If the object is deleted by the garbage collector, our cleanup callback may be called at some point in the future, with the `heldValue` passed to it: + +```js +// When the user object is deleted by the garbage collector, the following message will be printed in the console: +"John has been collected by the garbage collector." +``` + +There are also situations where, even in implementations that use a cleanup callback, there is a chance that it will not be called. + +For example: +- When the program fully terminates its operation (for example, when closing a tab in a browser). +- When the `FinalizationRegistry` instance itself is no longer reachable to JavaScript code. + If the object that creates the `FinalizationRegistry` instance goes out of scope or is deleted, the cleanup callbacks registered in that registry might also not be invoked. + +## Caching with FinalizationRegistry + +Returning to our *weak* cache example, we can notice the following: +- Even though the values wrapped in the `WeakRef` have been collected by the garbage collector, there is still an issue of "memory leakage" in the form of the remaining keys, whose values have been collected by the garbage collector. + +Here is an improved caching example using `FinalizationRegistry`: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { + const imgCache = new Map(); + + *!* + const registry = new FinalizationRegistry((imgName) => { // (1) + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); + }); + */!* + + return (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref()) { + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); + imgCache.set(imgName, new WeakRef(newImg)); + *!* + registry.register(newImg, imgName); // (2) + */!* + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +1. To manage the cleanup of "dead" cache entries, when the associated `WeakRef` objects are collected by the garbage collector, we create a `FinalizationRegistry` cleanup registry. + + The important point here is, that in the cleanup callback, it should be checked, if the entry was deleted by the garbage collector and not re-added, in order not to delete a "live" entry. +2. Once the new value (image) is downloaded and put into the cache, we register it in the finalizer registry to track the `WeakRef` object. + +This implementation contains only actual or "live" key/value pairs. +In this case, each `WeakRef` object is registered in the `FinalizationRegistry`. +And after the objects are cleaned up by the garbage collector, the cleanup callback will delete all `undefined` values. + +Here is a visual representation of the updated code: + +![](weakref-finalizationregistry-05.svg) + +A key aspect of the updated implementation is that finalizers allow parallel processes to be created between the "main" program and cleanup callbacks. +In the context of JavaScript, the "main" program - is our JavaScript-code, that runs and executes in our application or web page. + +Hence, from the moment an object is marked for deletion by the garbage collector, and to the actual execution of the cleanup callback, there may be a certain time gap. +It is important to understand that during this time gap, the main program can make any changes to the object or even bring it back to memory. + +That's why, in the cleanup callback, we must check to see if an entry has been added back to the cache by the main program to avoid deleting "live" entries. +Similarly, when searching for a key in the cache, there is a chance that the value has been deleted by the garbage collector, but the cleanup callback has not been executed yet. + +Such situations require special attention if you are working with `FinalizationRegistry`. + +## Using WeakRef and FinalizationRegistry in practice + +Moving from theory to practice, imagine a real-life scenario, where a user synchronizes their photos on a mobile device with some cloud service +(such as [iCloud](https://en.wikipedia.org/wiki/ICloud) or [Google Photos](https://en.wikipedia.org/wiki/Google_Photos)), +and wants to view them from other devices. In addition to the basic functionality of viewing photos, such services offer a lot of additional features, for example: + +- Photo editing and video effects. +- Creating "memories" and albums. +- Video montage from a series of photos. +- ...and much more. + +Here, as an example, we will use a fairly primitive implementation of such a service. +The main point - is to show a possible scenario of using `WeakRef` and `FinalizationRegistry` together in real life. + +Here is what it looks like: + +![](weakref-finalizationregistry-demo-01.png) + +
+On the left side, there is a cloud library of photos (they are displayed as thumbnails). +We can select the images we need and create a collage, by clicking the "Create collage" button on the right side of the page. +Then, the resulting collage can be downloaded as an image. +

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

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

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

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

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

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

Messages:

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

+ Photo Library Collage +

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

+
+ +
+
+

Logger:

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

Logger:

'; +}; + +const downloadCollage = () => { + const date = new Date(); + const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`; + const img = canvasEl.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = fileName; + link.href = img; + link.click(); + link.remove(); +}; + +const changeLayout = ({ target }) => { + state.currentLayout = JSON.parse(target.value); +}; + +// Listeners. +selectEl.addEventListener("change", changeLayout); +createCollageBtn.addEventListener("click", createCollage); +startOverBtn.addEventListener("click", startOver); +downloadBtn.addEventListener("click", downloadCollage); diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 000000000..f0140c116 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js @@ -0,0 +1,321 @@ +const loggerContainerEl = document.querySelector(".loggerContainer"); + +export const images = [ + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + }, + { + img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60", + }, + { + img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552", + }, + { + img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b", + }, + { + img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470", + }, + { + img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318", + }, + { + img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9", + }, + { + img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3", + }, + { + img: "https://images.unsplash.com/photo-1533105079780-92b9be482077", + }, + { + img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963", + }, + { + img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7", + }, + { + img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007", + }, + { + img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd", + }, + { + img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd", + }, + { + img: "https://images.unsplash.com/photo-1518684079-3c830dcef090", + }, + { + img: "https://images.unsplash.com/photo-1505832018823-50331d70d237", + }, + { + img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1", + }, + { + img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17", + }, + { + img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af", + }, + { + img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a", + }, + { + img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1", + }, + { + img: "https://images.unsplash.com/photo-1499363536502-87642509e31b", + }, + { + img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7", + }, + { + img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5", + }, + { + img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48", + }, + { + img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6", + }, + { + img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f", + }, + { + img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053", + }, + { + img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc", + }, + { + img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d", + }, + { + img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6", + }, + { + img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169", + }, + { + img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96", + }, + { + img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f", + }, + { + img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3", + }, + { + img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af", + }, + { + img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226", + }, + { + img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45", + }, + { + img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83", + }, + { + img: "https://images.unsplash.com/photo-1545389336-cf090694435e", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4", + }, + { + img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a", + }, + { + img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40", + }, + { + img: "https://images.unsplash.com/photo-1506125840744-167167210587", + }, + { + img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b", + }, + { + img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535", + }, + { + img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7", + }, + { + img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c", + }, + { + img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7", + }, + { + img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59", + }, + { + img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f", + }, + { + img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc", + }, + { + img: "https://images.unsplash.com/photo-1510662145379-13537db782dc", + }, + { + img: "https://images.unsplash.com/photo-1573790387438-4da905039392", + }, + { + img: "https://images.unsplash.com/photo-1512757776214-26d36777b513", + }, + { + img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b", + }, + { + img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7", + }, + { + img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a", + }, +]; +export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format"; + +// Console styles. +export const CONSOLE_BASE_STYLES = [ + "font-size: 12px", + "padding: 4px", + "border: 2px solid #5a5a5a", + "color: white", +].join(";"); +export const CONSOLE_PRIMARY = [ + CONSOLE_BASE_STYLES, + "background-color: #13315a", +].join(";"); +export const CONSOLE_SUCCESS = [ + CONSOLE_BASE_STYLES, + "background-color: #385a4e", +].join(";"); +export const CONSOLE_ERROR = [ + CONSOLE_BASE_STYLES, + "background-color: #5a1a24", +].join(";"); + +// Layouts. +export const LAYOUT_4_COLUMNS = { + name: "Layout 4 columns", + columns: 4, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUT_8_COLUMNS = { + name: "Layout 8 columns", + columns: 8, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS]; + +export const createImageFile = async (src) => + new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to construct image.")); + }); + +export const loadImage = async (url) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(String(response.status)); + } + + return await response.blob(); + } catch (e) { + console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR); + } +}; + +export const weakRefCache = (fetchImg) => { + const imgCache = new Map(); + const registry = new FinalizationRegistry(({ imgName, size, type }) => { + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) { + imgCache.delete(imgName); + console.log( + `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`, + CONSOLE_ERROR, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--error"); + logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + } + }); + + return async (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref() !== undefined) { + console.log( + `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`, + CONSOLE_SUCCESS, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--success"); + logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + return cachedImg?.deref(); + } + + const newImg = await fetchImg(imgName); + console.log( + `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`, + CONSOLE_PRIMARY, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--primary"); + logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + imgCache.set(imgName, new WeakRef(newImg)); + registry.register(newImg, { + imgName, + size: newImg.size, + type: newImg.type, + }); + + return newImg; + }; +}; + +export const stateObj = { + loading: false, + drawing: true, + collageRendered: false, + currentLayout: LAYOUTS[0], + selectedImages: new Set(), +}; diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index e18335f38..f7f2be91d 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -212,7 +212,7 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu ## See it for yourself -To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index de47eac9f..405129694 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -55,7 +55,7 @@ Also, there's a global variable named by `id` that references the element: ``` ```warn header="Please don't use id-named global variables to access elements" -This behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), so it's a kind of standard. But it is supported mainly for compatibility. +This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from. 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 aed342b20..99dde5bcd 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 @@ -28,7 +28,7 @@ The classes are: - [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole. - The `document` global object belongs exactly to this class. It servers as an entry point to the DOM. + The `document` global object belongs exactly to this class. It serves as an entry point to the DOM. - [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by: - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `

Hello

`. diff --git a/2-ui/1-document/06-dom-attributes-and-properties/article.md b/2-ui/1-document/06-dom-attributes-and-properties/article.md index e39a54256..b02f626dc 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/article.md +++ b/2-ui/1-document/06-dom-attributes-and-properties/article.md @@ -162,7 +162,7 @@ In the example below `id` is modified as an attribute, and we can see the proper ``` -But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back: +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: ```html run 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 644583c34..46aaa3b00 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -269,20 +269,6 @@ So nowadays `getComputedStyle` actually returns the resolved value of the proper We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed. For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. - -There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: - -```html run - - -``` ```` ```smart header="Styles applied to `:visited` links are hidden!" diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index 4775ff0eb..fc605c414 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -36,7 +36,7 @@ Additionally, there are derived properties: ```online For instance click this button to see its window coordinates: -

+

diff --git a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html index a4685a716..27df70939 100644 --- a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html +++ b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html @@ -9,8 +9,8 @@ [20] readyState:interactive [21] DOMContentLoaded [30] iframe onload - [40] readyState:complete [40] img onload + [40] readyState:complete [40] window onload --> diff --git a/2-ui/99-ui-misc/02-selection-range/article.md b/2-ui/99-ui-misc/02-selection-range/article.md index 9d6bb9c93..09a20bc67 100644 --- a/2-ui/99-ui-misc/02-selection-range/article.md +++ b/2-ui/99-ui-misc/02-selection-range/article.md @@ -354,7 +354,7 @@ The main selection properties are: ```smart header="Selection end/start vs Range" -There's an important differences of a selection anchor/focus compared with a `Range` start/end. +There's an important difference between a selection anchor/focus compared with a `Range` start/end. As we know, `Range` objects always have their start before the end. @@ -408,7 +408,7 @@ From – To There are two approaches to copying the selected content: 1. We can use `document.getSelection().toString()` to get it as text. -2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangesAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. +2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangeAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. Here's the demo of copying the selected content both as text and as DOM nodes: diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md index 3a0daa974..2911b76cf 100644 --- a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md @@ -40,10 +40,10 @@ console.log(7); To summarize, -1. Numbers `1` и `7` show up immediately, because simple `console.log` calls don't use any queues. +1. Numbers `1` and `7` show up immediately, because simple `console.log` calls don't use any queues. 2. Then, after the main code flow is finished, the microtask queue runs. - It has commands: `console.log(3); setTimeout(...4); console.log(5)`. - - Numbers `3` и `5` show up, while `setTimeout(() => console.log(4))` adds the `console.log(4)` call to the end of the macrotask queue. + - Numbers `3` and `5` show up, while `setTimeout(() => console.log(4))` adds the `console.log(4)` call to the end of the macrotask queue. - The macrotask queue is now: `console.log(2); console.log(6); console.log(4)`. 3. After the microtask queue becomes empty, the macrotask queue executes. It outputs `2`, `6`, `4`. diff --git a/2-ui/99-ui-misc/03-event-loop/article.md b/2-ui/99-ui-misc/03-event-loop/article.md index 3ea0c2c57..f33188491 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -17,7 +17,7 @@ The general algorithm of the engine: - execute them, starting with the oldest task. 2. Sleep until a task appears, then go to 1. -That's a formalization for what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates. +That's a formalization of what we see when browsing a page. The JavaScript engine does nothing most of the time, it only runs if a script/handler/event activates. Examples of tasks: @@ -30,19 +30,19 @@ Tasks are set -- the engine handles them -- then waits for more tasks (while sle It may happen that a task comes while the engine is busy, then it's enqueued. -The tasks form a queue, so-called "macrotask queue" (v8 term): +The tasks form a queue, the so-called "macrotask queue" ([v8](https://v8.dev/) term): ![](eventLoop.svg) -For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated on the picture above. +For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated in the picture above. -Tasks from the queue are processed on "first come – first served" basis. When the engine browser is done with the `script`, it handles `mousemove` event, then `setTimeout` handler, and so on. +Tasks from the queue are processed on a "first come – first served" basis. When the engine browser is done with the `script`, it handles `mousemove` event, then `setTimeout` handler, and so on. So far, quite simple, right? Two more details: 1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete. -2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after a time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop. +2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after some time, it raises an alert like "Page Unresponsive", suggesting killing the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to an infinite loop. That was the theory. Now let's see how we can apply that knowledge. @@ -54,7 +54,7 @@ For example, syntax-highlighting (used to colorize code examples on this page) i While the engine is busy with syntax highlighting, it can't do other DOM-related stuff, process user events, etc. It may even cause the browser to "hiccup" or even "hang" for a bit, which is unacceptable. -We can avoid problems by splitting the big task into pieces. Highlight first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on. +We can avoid problems by splitting the big task into pieces. Highlight the first 100 lines, then schedule `setTimeout` (with zero-delay) for the next 100 lines, and so on. To demonstrate this approach, for the sake of simplicity, instead of text-highlighting, let's take a function that counts from `1` to `1000000000`. diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index 01da2b304..f2c87d1e0 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -38,26 +38,6 @@ button.onclick = () => { This way users are somewhat protected from unwanted popups, but the functionality is not disabled totally. -What if the popup opens from `onclick`, but after `setTimeout`? That's a bit tricky. - -Try this code: - -```js run -// open after 3 seconds -setTimeout(() => window.open('http://google.com'), 3000); -``` - -The popup opens in Chrome, but gets blocked in Firefox. - -...If we decrease the delay, the popup works in Firefox too: - -```js run -// open after 1 seconds -setTimeout(() => window.open('http://google.com'), 1000); -``` - -The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it -- removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not. - ## window.open The syntax to open a popup is: `window.open(url, name, params)`: diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index d45ee391d..4420f43c7 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -44,7 +44,7 @@ One way to communicate with another server was to submit a `
` there. Peopl */!* - + *!* */!* @@ -169,6 +169,7 @@ For cross-origin request, by default JavaScript may only access so-called "safe" - `Cache-Control` - `Content-Language` +- `Content-Length` - `Content-Type` - `Expires` - `Last-Modified` @@ -176,12 +177,6 @@ For cross-origin request, by default JavaScript may only access so-called "safe" Accessing any other response header causes an error. -```smart -There's no `Content-Length` header in the list! - -This header contains the full response length. So, if we're downloading something and would like to track the percentage of progress, then an additional permission is required to access that header (see below). -``` - To grant JavaScript access to any other response header, the server must send the `Access-Control-Expose-Headers` header. It contains a comma-separated list of unsafe header names that should be made accessible. For example: @@ -190,14 +185,15 @@ For example: 200 OK Content-Type:text/html; charset=UTF-8 Content-Length: 12345 +Content-Encoding: gzip API-Key: 2c9de507f2c54aa1 Access-Control-Allow-Origin: https://javascript.info *!* -Access-Control-Expose-Headers: Content-Length,API-Key +Access-Control-Expose-Headers: Content-Encoding,API-Key */!* ``` -With such an `Access-Control-Expose-Headers` header, the script is allowed to read the `Content-Length` and `API-Key` headers of the response. +With such an `Access-Control-Expose-Headers` header, the script is allowed to read the `Content-Encoding` and `API-Key` headers of the response. ## "Unsafe" requests diff --git a/5-network/06-fetch-api/article.md b/5-network/06-fetch-api/article.md index c3f75175f..5f55c78ef 100644 --- a/5-network/06-fetch-api/article.md +++ b/5-network/06-fetch-api/article.md @@ -21,10 +21,10 @@ let promise = fetch(url, { // depending on the request body "Content-Type": "text/plain;charset=UTF-8" }, - body: undefined // string, FormData, Blob, BufferSource, or URLSearchParams + body: undefined, // string, FormData, Blob, BufferSource, or URLSearchParams referrer: "about:client", // or "" to send no Referer header, // or an url from the current origin - referrerPolicy: "no-referrer-when-downgrade", // no-referrer, origin, same-origin... + referrerPolicy: "strict-origin-when-cross-origin", // no-referrer-when-downgrade, no-referrer, origin, same-origin... mode: "cors", // same-origin, no-cors credentials: "same-origin", // omit, include cache: "default", // no-store, reload, no-cache, force-cache, or only-if-cached @@ -85,13 +85,13 @@ Unlike the `referrer` option that allows to set the exact `Referer` value, `refe Possible values are described in the [Referrer Policy specification](https://w3c.github.io/webappsec-referrer-policy/): -- **`"no-referrer-when-downgrade"`** -- the default value: full `Referer` is always sent, unless we send a request from HTTPS to HTTP (to the less secure protocol). +- **`"strict-origin-when-cross-origin"`** -- the default value: for same-origin send the full `Referer`, for cross-origin send only the origin, unless it's HTTPS→HTTP request, then send nothing. +- **`"no-referrer-when-downgrade"`** -- full `Referer` is always sent, unless we send a request from HTTPS to HTTP (to the less secure protocol). - **`"no-referrer"`** -- never send `Referer`. - **`"origin"`** -- only send the origin in `Referer`, not the full page URL, e.g. only `http://site.com` instead of `http://site.com/path`. - **`"origin-when-cross-origin"`** -- send the full `Referer` to the same origin, but only the origin part for cross-origin requests (as above). - **`"same-origin"`** -- send the full `Referer` to the same origin, but no `Referer` for cross-origin requests. - **`"strict-origin"`** -- send only the origin, not the `Referer` for HTTPS→HTTP requests. -- **`"strict-origin-when-cross-origin"`** -- for same-origin send the full `Referer`, for cross-origin send only the origin, unless it's HTTPS→HTTP request, then send nothing. - **`"unsafe-url"`** -- always send the full url in `Referer`, even for HTTPS→HTTP requests. Here's a table with all combinations: @@ -99,12 +99,12 @@ Here's a table with all combinations: | Value | To same origin | To another origin | HTTPS→HTTP | |-------|----------------|-------------------|------------| | `"no-referrer"` | - | - | - | -| `"no-referrer-when-downgrade"` or `""` (default) | full | full | - | +| `"no-referrer-when-downgrade"` | full | full | - | | `"origin"` | origin | origin | origin | | `"origin-when-cross-origin"` | full | origin | origin | | `"same-origin"` | full | - | - | | `"strict-origin"` | origin | origin | - | -| `"strict-origin-when-cross-origin"` | full | origin | - | +| `"strict-origin-when-cross-origin"` or `""` (default) | full | origin | - | | `"unsafe-url"` | full | full | full | Let's say we have an admin zone with a URL structure that shouldn't be known from outside of the site. diff --git a/5-network/09-resume-upload/article.md b/5-network/09-resume-upload/article.md index 7eedc3fbd..b0aa447d6 100644 --- a/5-network/09-resume-upload/article.md +++ b/5-network/09-resume-upload/article.md @@ -48,7 +48,7 @@ To resume upload, we need to know *exactly* the number of bytes received by the 3. Then, we can use `Blob` method `slice` to send the file from `startByte`: ```js - xhr.open("POST", "upload", true); + xhr.open("POST", "upload"); // File id, so that the server knows which file we upload xhr.setRequestHeader('X-File-Id', fileId); diff --git a/5-network/10-long-polling/article.md b/5-network/10-long-polling/article.md index be367cee7..e9d8abe39 100644 --- a/5-network/10-long-polling/article.md +++ b/5-network/10-long-polling/article.md @@ -1,6 +1,6 @@ # Long polling -Long polling is the simplest way of having persistent connection with server, that doesn't use any specific protocol like WebSocket or Server Side Events. +Long polling is the simplest way of having persistent connection with server, that doesn't use any specific protocol like WebSocket or Server Sent Events. Being very easy to implement, it's also good enough in a lot of cases. @@ -29,7 +29,7 @@ The flow: 3. When a message appears - the server responds to the request with it. 4. The browser makes a new request immediately. -The situation when the browser sent a request and has a pending connection with the server, is standard for this method. Only when a message is delivered, the connection is reestablished. +This situation, where the browser has sent a request and keeps a pending connection with the server, is standard for this method. Only when a message is delivered, the connection is closed and reestablished. ![](long-polling.svg) diff --git a/5-network/11-websocket/article.md b/5-network/11-websocket/article.md index adebd6c74..268b674f0 100644 --- a/5-network/11-websocket/article.md +++ b/5-network/11-websocket/article.md @@ -56,7 +56,7 @@ socket.onclose = function(event) { }; socket.onerror = function(error) { - alert(`[error] ${error.message}`); + alert(`[error]`); }; ``` @@ -91,7 +91,7 @@ Sec-WebSocket-Version: 13 - `Origin` -- the origin of the client page, e.g. `https://javascript.info`. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compatibility issues. But the `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. - `Connection: Upgrade` -- signals that the client would like to change the protocol. - `Upgrade: websocket` -- the requested protocol is "websocket". -- `Sec-WebSocket-Key` -- a random browser-generated key for security. +- `Sec-WebSocket-Key` -- a random browser-generated key, used to ensure that the server supports WebSocket protocol. It's random to prevent proxies from caching any following communication. - `Sec-WebSocket-Version` -- WebSocket protocol version, 13 is the current one. ```smart header="WebSocket handshake can't be emulated" @@ -107,7 +107,7 @@ Connection: Upgrade Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g= ``` -Here `Sec-WebSocket-Accept` is `Sec-WebSocket-Key`, recoded using a special algorithm. The browser uses it to make sure that the response corresponds to the request. +Here `Sec-WebSocket-Accept` is `Sec-WebSocket-Key`, recoded using a special algorithm. Upon seeing it, the browser understands that the server really does support the WebSocket protocol. Afterwards, the data is transferred using the WebSocket protocol, we'll see its structure ("frames") soon. And that's not HTTP at all. diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index b88849457..1b9e93414 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -2,17 +2,17 @@ Cookies are small strings of data that are stored directly in the browser. They are a part of the HTTP protocol, defined by the [RFC 6265](https://tools.ietf.org/html/rfc6265) specification. -Cookies are usually set by a web-server using the response `Set-Cookie` HTTP-header. Then, the browser automatically adds them to (almost) every request to the same domain using the `Cookie` HTTP-header. +Cookies are usually set by a web server using the response `Set-Cookie` HTTP header. Then, the browser automatically adds them to (almost) every request to the same domain using the `Cookie` HTTP header. One of the most widespread use cases is authentication: -1. Upon sign in, the server uses the `Set-Cookie` HTTP-header in the response to set a cookie with a unique "session identifier". -2. Next time when the request is sent to the same domain, the browser sends the cookie over the net using the `Cookie` HTTP-header. +1. Upon sign-in, the server uses the `Set-Cookie` HTTP header in the response to set a cookie with a unique "session identifier". +2. Next time the request is sent to the same domain, the browser sends the cookie over the net using the `Cookie` HTTP header. 3. So the server knows who made the request. We can also access cookies from the browser, using `document.cookie` property. -There are many tricky things about cookies and their options. In this chapter we'll cover them in detail. +There are many tricky things about cookies and their attributes. In this chapter, we'll cover them in detail. ## Reading from document.cookie @@ -31,17 +31,17 @@ alert( document.cookie ); // cookie1=value1; cookie2=value2;... ``` -The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie. +The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie. -To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that. +To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that. -We leave it as an exercise for the reader. Also, at the end of the chapter you'll find helper functions to manipulate cookies. +We leave it as an exercise for the reader. Also, at the end of the chapter, you'll find helper functions to manipulate cookies. ## Writing to document.cookie We can write to `document.cookie`. But it's not a data property, it's an [accessor (getter/setter)](info:property-accessors). An assignment to it is treated specially. -**A write operation to `document.cookie` updates only cookies mentioned in it, but doesn't touch other cookies.** +**A write operation to `document.cookie` updates only the cookie mentioned in it and doesn't touch other cookies.** For instance, this call sets a cookie with the name `user` and value `John`: @@ -50,12 +50,12 @@ document.cookie = "user=John"; // update only cookie named 'user' alert(document.cookie); // show all cookies ``` -If you run it, then probably you'll see multiple cookies. That's because the `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`. +If you run it, you will likely see multiple cookies. That's because the `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`. Technically, name and value can have any characters. To keep the valid formatting, they should be escaped using a built-in `encodeURIComponent` function: ```js run -// special characters (spaces), need encoding +// special characters (spaces) need encoding let name = "my name"; let value = "John Smith" @@ -67,29 +67,20 @@ alert(document.cookie); // ...; my%20name=John%20Smith ```warn header="Limitations" -There are few limitations: +There are a few limitations: +- You can only set/update a single cookie at a time using `document.cookie`. - The `name=value` pair, after `encodeURIComponent`, should not exceed 4KB. So we can't store anything huge in a cookie. - The total number of cookies per domain is limited to around 20+, the exact limit depends on the browser. ``` -Cookies have several options, many of them are important and should be set. +Cookies have several attributes, many of which are important and should be set. -The options are listed after `key=value`, delimited by `;`, like this: +The attributes are listed after `key=value`, delimited by `;`, like this: ```js run document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT" ``` -## path - -- **`path=/mypath`** - -The url path prefix must be absolute. It makes the cookie accessible for pages under that path. By default, it's the current path. - -If a cookie is set with `path=/admin`, it's visible at pages `/admin` and `/admin/something`, but not at `/home` or `/adminpage`. - -Usually, we should set `path` to the root: `path=/` to make the cookie accessible from all website pages. - ## domain - **`domain=site.com`** @@ -102,7 +93,7 @@ It's a safety restriction, to allow us to store sensitive data in cookies that s By default, a cookie is accessible only at the domain that set it. -Please note, by default a cookie is also not shared to a subdomain as well, such as `forum.site.com`. +Please note, by default, a cookie is not shared with a subdomain, such as `forum.site.com`. ```js // if we set a cookie at site.com website... @@ -114,7 +105,7 @@ alert(document.cookie); // no user ...But this can be changed. If we'd like to allow subdomains like `forum.site.com` to get a cookie set at `site.com`, that's possible. -For that to happen, when setting a cookie at `site.com`, we should explicitly set the `domain` option to the root domain: `domain=site.com`. Then all subdomains will see such cookie. +For that to happen, when setting a cookie at `site.com`, we should explicitly set the `domain` attribute to the root domain: `domain=site.com`. Then all subdomains will see such a cookie. For example: @@ -129,19 +120,31 @@ document.cookie = "user=John; *!*domain=site.com*/!*" alert(document.cookie); // has cookie user=John ``` -For historical reasons, `domain=.site.com` (with a dot before `site.com`) also works the same way, allowing access to the cookie from subdomains. That's an old notation and should be used if we need to support very old browsers. +```warn header="Legacy syntax" +Historically, `domain=.site.com` (with a dot before `site.com`) used to work the same way, allowing access to the cookie from subdomains. Leading dots in domain names are now ignored, but some browsers may decline to set the cookie containing such dots. +``` + +To summarize, the `domain` attribute allows to make a cookie accessible at subdomains. + +## path + +- **`path=/mypath`** + +The URL path prefix must be absolute. It makes the cookie accessible for pages under that path. By default, it's the current path. + +If a cookie is set with `path=/admin`, it's visible on pages `/admin` and `/admin/something`, but not at `/home`, `/home/admin` or `/`. -To summarize, the `domain` option allows to make a cookie accessible at subdomains. +Usually, we should set `path` to the root: `path=/` to make the cookie accessible from all website pages. If this attribute is not set the default is calculated using [this method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#path_default_value). ## expires, max-age -By default, if a cookie doesn't have one of these options, it disappears when the browser is closed. Such cookies are called "session cookies" +By default, if a cookie doesn't have one of these attributes, it disappears when the browser/tab is closed. Such cookies are called "session cookies" -To let cookies survive a browser close, we can set either the `expires` or `max-age` option. +To let cookies survive a browser close, we can set either the `expires` or `max-age` attribute. `max-Age` has precedence if both are set. - **`expires=Tue, 19 Jan 2038 03:14:07 GMT`** -The cookie expiration date defines the time, when the browser will automatically delete it. +The cookie expiration date defines the time when the browser will automatically delete it (according to the browser's time zone). The date must be exactly in this format, in the GMT timezone. We can use `date.toUTCString` to get it. For instance, we can set the cookie to expire in 1 day: @@ -156,7 +159,7 @@ If we set `expires` to a date in the past, the cookie is deleted. - **`max-age=3600`** -Is an alternative to `expires` and specifies the cookie's expiration in seconds from the current moment. +It's an alternative to `expires` and specifies the cookie's expiration in seconds from the current moment. If set to zero or a negative value, the cookie is deleted: @@ -178,7 +181,7 @@ The cookie should be transferred only over HTTPS. That is, cookies are domain-based, they do not distinguish between the protocols. -With this option, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, the `secure` flag is the right thing. +With this attribute, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, the `secure` flag is the right thing. ```js // assuming we're on https:// now @@ -188,49 +191,49 @@ document.cookie = "user=John; secure"; ## samesite -That's another security attribute `samesite`. It's designed to protect from so-called XSRF (cross-site request forgery) attacks. +This is another security attribute `samesite`. It's designed to protect from so-called XSRF (cross-site request forgery) attacks. To understand how it works and when it's useful, let's take a look at XSRF attacks. ### XSRF attack -Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request, so that it recognizes you and performs all sensitive financial operations. +Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request so that it recognizes you and performs all sensitive financial operations. Now, while browsing the web in another window, you accidentally come to another site `evil.com`. That site has JavaScript code that submits a form `` to `bank.com` with fields that initiate a transaction to the hacker's account. -The browser sends cookies every time you visit the site `bank.com`, even if the form was submitted from `evil.com`. So the bank recognizes you and actually performs the payment. +The browser sends cookies every time you visit the site `bank.com`, even if the form was submitted from `evil.com`. So the bank recognizes you and performs the payment. ![](cookie-xsrf.svg) -That's a so-called "Cross-Site Request Forgery" (in short, XSRF) attack. +This is a so-called "Cross-Site Request Forgery" (in short, XSRF) attack. -Real banks are protected from it of course. All forms generated by `bank.com` have a special field, a so-called "XSRF protection token", that an evil page can't generate or extract from a remote page. It can submit a form there, but can't get the data back. The site `bank.com` checks for such token in every form it receives. +Real banks are protected from it of course. All forms generated by `bank.com` have a special field, a so-called "XSRF protection token", that an evil page can't generate or extract from a remote page. It can submit a form there, but can't get the data back. The site `bank.com` checks for such a token in every form it receives. Such a protection takes time to implement though. We need to ensure that every form has the required token field, and we must also check all requests. -### Enter cookie samesite option +### Use cookie samesite attribute -The cookie `samesite` option provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". +The cookie `samesite` attribute provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". It has two possible values: -- **`samesite=strict` (same as `samesite` without value)** +- **`samesite=strict`** A cookie with `samesite=strict` is never sent if the user comes from outside the same site. -In other words, whether a user follows a link from their mail or submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent. +In other words, whether a user follows a link from their email, submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent. -If authentication cookies have the `samesite` option, then a XSRF attack has no chances to succeed, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment. +If authentication cookies have the `samesite=strict` attribute, then an XSRF attack has no chance of succeeding, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment. -The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite` cookie, e.g. a form submission from another page at `bank.com`. +The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite=strict` cookie, e.g. a form submission from another page at `bank.com`. Although, there's a small inconvenience. -When a user follows a legitimate link to `bank.com`, like from their own notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case. +When a user follows a legitimate link to `bank.com`, like from their notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case. -We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then, a person coming from outside of the site will see a welcome, but payments must be initiated from the bank's website, for the second cookie to be sent. +We could work around that by using two cookies: one for "general recognition", only to say: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then, a person coming from outside of the site will see a welcome, but payments must be initiated from the bank's website, for the second cookie to be sent. -- **`samesite=lax`** +- **`samesite=lax` (same as `samesite` without value)** A more relaxed approach that also protects from XSRF and doesn't break the user experience. @@ -239,40 +242,40 @@ Lax mode, just like `strict`, forbids the browser to send cookies when coming fr A `samesite=lax` cookie is sent if both of these conditions are true: 1. The HTTP method is "safe" (e.g. GET, but not POST). - The full list of safe HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231). Basically, these are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method. + The full list of safe HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231#section-4.2.1). These are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method. 2. The operation performs a top-level navigation (changes URL in the browser address bar). - That's usually true, but if the navigation is performed in an `