diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..490051876
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: iliakan
diff --git a/.gitignore b/.gitignore
index 6f90fd190..1a71fb7c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ sftp-config.json
Thumbs.db
+/svgs
\ No newline at end of file
diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md
index b2769be08..77ef9e981 100644
--- a/1-js/01-getting-started/1-intro/article.md
+++ b/1-js/01-getting-started/1-intro/article.md
@@ -1,6 +1,10 @@
# Uvod u JavaScript
+<<<<<<< HEAD
Da vidimo Å¡ta je tako specijalno u JavaScriptu, Å¡ta možemo postiÄi, i koje druge tehnologije su dobri suigraÄi sa njim.
+=======
+Let's see what's so special about JavaScript, what we can achieve with it, and what other technologies play well with it.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Sta je JavaScript?
@@ -25,26 +29,44 @@ PretraživaÄi imaju ugraÄen motor (eng. engine) ponekad nazvan "JavaScript vir
RazliÄiti motori imaju razliÄita "kodna imena" . Na primjer:
+<<<<<<< HEAD
- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- u Chrome-u i Operi.
- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- u Firefox-u.
- ...Postoje joÅ¡ neka kodna imena kao Å¡to su "Trident" i "Chakra" za razliÄite verzije Internet Explorer-a, "ChakraCore" za Microsoft Edge, "Nitro" i "SquirrelFish" za Safari, itd.
Pojmove iznad je poželjno zapamtiti jer se koriste i spominju u programerskim Älancima na internetu. Mi Äemo ih takoÄer koristiti. Na primjer, ako "svojstvo X je podržano od strane V8", onda vjerovatno radi u Chrome-u i Operi.
+=======
+- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome, Opera and Edge.
+- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- in Firefox.
+- ...There are other codenames like "Chakra" for IE, "JavaScriptCore", "Nitro" and "SquirrelFish" for Safari, etc.
+
+The terms above are good to remember because they are used in developer articles on the internet. We'll use them too. For instance, if "a feature X is supported by V8", then it probably works in Chrome, Opera and Edge.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```smart header="Kako motori rade?"
Motori su komplikovani. Ali osnove su jednostavne.
+<<<<<<< HEAD
1. Motor (ugraÄen ako je u pitanju pretraživaÄ) Äita ("raÅ¡Älanjuje") skriptu.
2. Onda pretvara ("sastavlja") skriptu u mašinski jezik.
3. Onda se pokreÄe maÅ¡inski kod, i to veoma brzo.
+=======
+1. The engine (embedded if it's a browser) reads ("parses") the script.
+2. Then it converts ("compiles") the script to machine code.
+3. And then the machine code runs, pretty fast.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Motor primjenjuje optimizacije u svakom koraku procesa. Äak gleda sastavljenu (kompiliranu) skriptu dok se izvrÅ¡ava, analizira podatke koje prolaze kroz nju, i dalje optimizira maÅ¡inski kod na osnovu tog znanja.
```
## Å ta može JavaScript uraditi u pretraživaÄu?
+<<<<<<< HEAD
Moderni JavaScript je "siguran" programski jezik. Ne pruža niski pristup memoriji ili procesoru, jer je u poÄetku napravljen za pretraživaÄ, kojem to nije potrebno.
+=======
+Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or the CPU, because it was initially created for browsers which do not require it.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
JavaScript-ove sposobnosti uveliko ovise o okolini u kojoj se izvrÅ¡ava. Na primjer, [Node.js](https://wikipedia.org/wiki/Node.js) podržava funkcije koje omoguÄavaju JavaScript-u da Äita/piÅ¡e proizvoljne datoteke, izvrÅ¡ava mrežne zahtjeve, itd.
@@ -60,7 +82,11 @@ Na primjer, JavaScript u pretraživacu može da:
## Å ta ne može JavaScript uraditi u pretraživaÄu?
+<<<<<<< HEAD
JavaScript-ove moguÄnosti u pretraživaÄu su ograniÄene radi korisniÄke sigurnosti. Cilj je sprijeÄiti zlim stranicama pristup privatnim informacijama ili naÅ¡koditi korisniÄkim podacima.
+=======
+JavaScript's abilities in the browser are limited to protect the user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Primjeri nekih ograniÄenja su:
@@ -68,6 +94,7 @@ Primjeri nekih ograniÄenja su:
Moderni pretraživaÄi omoguÄavaju mu da radi sa datotekama, ali je pristup ograniÄen i samo obezbjeÄen kada korisnik izvrÅ¡i odreÄene akcije, kao Å¡to su "spuÅ¡tanje" datoteka u prozor pretraživaÄa ili odabirom istih putem ` ` tag-a.
+<<<<<<< HEAD
Postoje naÄini da komunicira sa kamerom/mikrofonom i ostalim ureÄajima, ali za to je potrebna eksplicitna dozvola od korisnika. To je napravljeno kako stranice koje imaju ukljucen JavaScript ne bi potajno ukljuÄile web-kameru, posmatrali vas i vaÅ¡u okolinu i poslali te informacije [NSA](https://en.wikipedia.org/wiki/National_Security_Agency).
- RazliÄiti tabovi/prozori u veÄini sluÄajeva ne znaju jedno drugo. Ponekad znaju, na primjer kada jedan prozor koristi JavaScript da otvori drugi. Ali Äak i u tom sluÄaju, JavaScript sa jedne stranice ne može pristupiti drugu ako dolaze sa drugaÄijih stranica (drugaÄijih domena, protokola ili portova).
@@ -79,21 +106,44 @@ Primjeri nekih ograniÄenja su:

Ovakva ograniÄenja ne postoje ako se JavaScript koristi izvan pretraživaÄa, na primjer na serveru. Moderni pretraživaÄi isto tako dopuÅ¡taju plugin-ove/ekstenzije koje mogu pitati i tražiti proÅ¡irene dozvole.
+=======
+ There are ways to interact with the camera/microphone and other devices, but they require a user's explicit permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the [NSA](https://en.wikipedia.org/wiki/National_Security_Agency).
+- Different tabs/windows generally do not know about each other. Sometimes they do, for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other page if they come from different sites (from a different domain, protocol or port).
+
+ This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and must contain special JavaScript code that handles it. We'll cover that in the tutorial.
+
+ This limitation is, again, for the user's safety. A page from `http://anysite.com` which a user has opened must not be able to access another browser tab with the URL `http://gmail.com`, for example, and steal information from there.
+- JavaScript can easily communicate over the net to the server where the current page came from. But its ability to receive data from other sites/domains is crippled. Though possible, it requires explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's a safety limitation.
+
+
+
+Such limitations do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow plugins/extensions which may ask for extended permissions.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Å ta Äini JavaScript unikatnim?
Postoje bar *tri* odliÄne stvari u JavaScriptu:
```compare
+<<<<<<< HEAD
+ Potpuna integracija sa HTML/CSS.
+ Jednostavne stvari su uraÄene na jednostavan naÄin.
+ PodrÅ¡ka od svih glavnih pretraživaÄa i ukljuÄen je automatski.
+=======
++ Full integration with HTML/CSS.
++ Simple things are done simply.
++ Supported by all major browsers and enabled by default.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```
JavaScript je jedina tehnologija u pretraživaÄu koja kombinira ove tri stvari.
To je ono Å¡to Äini JavaScript unikatnim jezikom. To je razlog Å¡to je on najrasprostranjeniji alat za izradu pretraživaÄkog interfejsa.
+<<<<<<< HEAD
Isto tako, JavaScript omoguÄava izradu servera, mobilnih aplikacija, itd.
+=======
+That said, JavaScript can be used to create servers, mobile applications, etc.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Jezici koji su "bolji od" JavaScript-a
@@ -101,21 +151,42 @@ Sintaksa JavaScript-a ne odgovara svaÄijim potrebama. RazliÄiti ljudi žele ra
To je i oÄekivano, jer projekti i zahtjevi su drugaÄiji za sve ljude.
+<<<<<<< HEAD
Nedavno mnoÅ¡tvo novih jezika se pojavilo, koji su *prevedeni* (eng. transpiled) u JavaScript prije nego Å¡to se pokrenu u pretraživaÄu.
+=======
+So, recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run in the browser.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Moderne alatke Äine prevoÄenje veoma brzim i transparentnim, i zapravo dopuÅ¡taju programerima da kodiraju u drugim jezicima prije nego Å¡to se automatski prevede "ispod haube".
Primjeri takvih jezika:
+<<<<<<< HEAD
- [CoffeeScript](http://coffeescript.org/) je "sintaktiÄki Å¡eÄer" za JavaScript. Uvodi kraÄu sintaksu, Å¡to nam dopuÅ¡ta da piÅ¡emo jasniji i precizniji kod. ObiÄno, Ruby programeri ga vole.
- [TypeScript](http://www.typescriptlang.org/) je koncentrisan na dodavanje "strogog tipkanja podataka" (eng. strict data typing) da bi olakšao razvoj i podršku složenih sistema. Razvijen je od strane Microsoft-a.
- [Flow](http://flow.org/) isto dodaje strogo tipkanje podataka, ali na drugi naÄin. Razvijen je od strane Facebook-a.
- [Dart](https://www.dartlang.org/) je samostalni jezik koji ima svoj motor koji se pokreÄe izvan pretraživaÄkih okruženja (kao npr. u mobilnim aplikacijama), ali isto može biti preveden u JavaScript. Razvijen od strane Google-a.
Postoji ih viÅ¡e. Naravno, Äak i ako koristimo jedan od ovih prevedenih jezika, moramo dobro znati JavaScript kako bismo stvarno shvatili Å¡ta se dogaÄa i odvija.
+=======
+- [CoffeeScript](https://coffeescript.org/) is "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it.
+- [TypeScript](https://www.typescriptlang.org/) is concentrated on adding "strict data typing" to simplify the development and support of complex systems. It is developed by Microsoft.
+- [Flow](https://flow.org/) also adds data typing, but in a different way. Developed by Facebook.
+- [Dart](https://www.dartlang.org/) is a standalone language that has its own engine that runs in non-browser environments (like mobile apps), but also can be transpiled to JavaScript. Developed by Google.
+- [Brython](https://brython.info/) is a Python transpiler to JavaScript that enables the writing of applications in pure Python without JavaScript.
+- [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) is a modern, concise and safe programming language that can target the browser or Node.
+
+There are more. Of course, even if we use one of these transpiled languages, we should also know JavaScript to really understand what we're doing.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Sažetak
+<<<<<<< HEAD
- JavaScript je u poÄetku bio napravljen i osmiÅ¡ljen kao jezik koji se mogao koristiti samo u pretraživaÄu, ali se sada koristi u mnogim drugim okruženjima.
- Danas, JavaScript ima unikatnu poziciju kao najÄesÄÄe usvojen jezik koji se koristi u pretraživaÄima, a ima potpunu integraciju sa HTML-om i CSS-om.
- Postoje mnogi jezici koji se prevedu u JavaScript kod, oni pružaju odreÄene karakteristike. PreporuÄeno ih je prouÄiti, bar ukratko, nakon Å¡to savladate JavaScript.
+=======
+- JavaScript was initially created as a browser-only language, but it is now used in many other environments as well.
+- Today, JavaScript has a unique position as the most widely-adopted browser language, fully integrated with HTML/CSS.
+- There are many languages that get "transpiled" to JavaScript and provide certain features. It is recommended to take a look at them, at least briefly, after mastering JavaScript.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md
index 3b3e91cdd..4a5ce583c 100644
--- a/1-js/01-getting-started/2-manuals-specifications/article.md
+++ b/1-js/01-getting-started/2-manuals-specifications/article.md
@@ -1,7 +1,11 @@
# PriruÄnici i specifikacije
+<<<<<<< HEAD
Ova knjiga je *tutorijal*. Ima u cilju da ti postepeno pomogne da nauÄiÅ¡ ovaj jezik. Ali kada se upoznaÅ¡ sa osnovama, trebat Äe ti drugi izvori za uÄenje.
+=======
+This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other resources.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Specifikacija
@@ -9,14 +13,23 @@ Ova knjiga je *tutorijal*. Ima u cilju da ti postepeno pomogne da nauÄiÅ¡ ovaj
PoÅ¡to je veoma formalizirano, teÅ¡ko je shvatiti odmah u poÄetku. Tako da ako želite najpouzdaniji izvor informacija o detaljima ovog jezika, spefifikacija je dobro mjesto. Ali nije za svakodnevnu upotrebu.
+<<<<<<< HEAD
Nova verzija specifikacije izlazi svake godine. IzmeÄu ovih izdanja, najnoviji specifikacijski nacrt se nalazi na ovom linku .
+=======
+A new specification version is released every year. Between these releases, the latest specification draft is at .
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Da proÄitate najnovije karakteristike, ukljuÄujuÄi one koje su skoro postale "standard" (takozvana "faza 3"), pogledajte prijedloge na ovom linku .
+<<<<<<< HEAD
Isto tako, ako programirate u pretraživaÄu, postoje druge specifikacije koje su spomenute u [drugom dijelu](info:browser-environment) tutorijala.
+=======
+Also, if you're developing for the browser, then there are other specifications covered in the [second part](info:browser-environment) of the tutorial.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## PriruÄnici
+<<<<<<< HEAD
- **MDN (Mozilla) JavaScript Referenca** je priruÄnik sa primjerima i ostalim informacijama. OdliÄan je izvor gdje možete naÄi dubinske informacije o individualnim funkcijama jezika, metodama, itd.
Možete naÄi na ovom linku .
@@ -27,6 +40,13 @@ Isto tako, ako programirate u pretraživaÄu, postoje druge specifikacije koje s
- **MSDN** â Microsoft priruÄnik sa mnoÅ¡tvo informacija, ukljuÄujuÄi JavaScript (Äesto se na stranici naziva JScript). Ako neko želi neÅ¡to specifiÄno za Internet Explorer, najbolje je otiÄi ovdje: .
Isto tako, možemo koristiti pretrage sa frazama kao što su "RegExp MSDN" ili "RegExp MSDN jscript".
+=======
+- **MDN (Mozilla) JavaScript Reference** is the main manual with examples and other information. It's great to get in-depth information about individual language functions, methods etc.
+
+ You can find it at .
+
+Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for the `parseInt` function.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Tablice kompatibilinosti
@@ -34,9 +54,16 @@ JavaScript je jezik koji se razvija, nove karakteristike se dodaju redovno.
Da vidimo njihovu podrÅ¡ku meÄu pretraživaÄima i ostalim motorima, pogledajte ove linkove:
+<<<<<<< HEAD
- - po-funkciji tablice podrške, na primjer da vidimo koji motori podržavaju moderne kriptografske funkcije: .
- - tablica sa karakteristkima jezika i motorima koji podržavaju ili ne podržavaju te karakteristike.
Svi ovi resursi su korisni tokom izrade pravih projekata, jer sadržavaju dragocjene informacije o detaljima jezika, njihovoj podršci itd.
+=======
+- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: .
+- - a table with language features and engines that support those or don't support.
+
+All these resources are useful in real-life development, as they contain valuable information about language details, their support, etc.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Molimo vas da ih zapamtite (ili ovu stranicu) za sluÄajeve kada vam trebale dubinske informacije o nekoj posebnoj karakteristici jezika.
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 320f733eb..41ebb52fc 100644
--- a/1-js/01-getting-started/3-code-editors/article.md
+++ b/1-js/01-getting-started/3-code-editors/article.md
@@ -12,8 +12,13 @@ IDE uÄitava projekat (koji se može sastojiti od viÅ¡e datoteka), omoguÄava na
Ako joÅ¡ niste odabrali IDE, možete uzeti u obzir sljedeÄe opcije:
+<<<<<<< HEAD
- [Visual Studio Code](https://code.visualstudio.com/) (radi na razliÄitim operativnim sistemima (cross-platform), besplatan).
- [WebStorm](http://www.jetbrains.com/webstorm/) (radi na razliÄitim operativnim sistemima (cross-platform), plaÄa se).
+=======
+- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free).
+- [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, paid).
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Za Windows, postoji "Visual Studio", kojeg ne treba mijeÅ¡ati sa "Visual Studio Code". "Visual Studio" je program koji se plaÄa i on radi samo na Windows operativnom sistemu, dobro prilagoÄen za .NET platformu. Isto tako je dobar za JavaScript. Postoji i besplatna verzija na sljedeÄem linku [Visual Studio Community](https://www.visualstudio.com/vs/community/).
@@ -29,6 +34,7 @@ Glavna razlika izmeÄu "laganog editora" i "IDE" jeste to Å¡to IDE radi dobro ka
U praksi, lagani editori veÄinom imaju dosta viÅ¡e plugin-ova (dodataka) ukljuÄujuÄi analizatore i automatsko dovrÅ¡avanje koda na nivou direktorija, tako da nema neke taÄne granice izmeÄu IDE i laganog editora.
+<<<<<<< HEAD
SljedeÄe opcije zaslužuju vaÅ¡u pažnju:
- [Atom](https://atom.io/) (cross-platform, besplatan).
@@ -36,6 +42,13 @@ SljedeÄe opcije zaslužuju vaÅ¡u pažnju:
- [Sublime Text](http://www.sublimetext.com) (cross-platform, besplatan odreÄeno vrijeme).
- [Notepad++](https://notepad-plus-plus.org/) (Windows, besplatan).
- [Vim](http://www.vim.org/) i [Emacs](https://www.gnu.org/software/emacs/) su isto zanimljivi ako ih znate koristiti.
+=======
+There are many options, for instance:
+
+- [Sublime Text](https://www.sublimetext.com/) (cross-platform, shareware).
+- [Notepad++](https://notepad-plus-plus.org/) (Windows, free).
+- [Vim](https://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Nemojmo se prepirati
@@ -43,4 +56,13 @@ Editori u listi iznad su editori koje ja koristim ili moji prijatelji koje smatr
Postoje joÅ¡ izvanrednih editora u naÅ¡em velikom svijetu. Molim vas da izaberete onaj koji se vama najviÅ¡e sviÄa.
+<<<<<<< HEAD
Izbor editora, kao i bilo kojeg drugog alata, je individualna stvar i zavisi od vaÅ¡ih projekata, navika kao i liÄnih preferencija.
+=======
+The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences.
+
+The author's personal opinion:
+
+- I'd use [Visual Studio Code](https://code.visualstudio.com/) if I develop mostly frontend.
+- Otherwise, if it's mostly another language/platform and partially frontend, then consider other editors, such as XCode (Mac), Visual Studio (Windows) or Jetbrains family (Webstorm, PHPStorm, RubyMine etc, depending on the language).
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md
index c2ce4d210..06567c8f0 100644
--- a/1-js/01-getting-started/4-devtools/article.md
+++ b/1-js/01-getting-started/4-devtools/article.md
@@ -22,7 +22,7 @@ Alatke za programere Äe se otvoriti na tabu sa konzolom po uobiÄajenom.
Izgledati Äe otprilike ovako:
-
+
Izgled vaÅ¡e alatke zavisi od verzije Chrome-a. Vremenom se izgled mijenja, ali trebalo bi izgledati sliÄno.
@@ -49,7 +49,11 @@ Njihov izgled i osjeÄaj je jako sliÄan. Kada znate koristiti jednu vrstu ove a
Safari (Mac pretraživaÄ, nije podržan od strane Windows/Linux) je ovdje malo specijalan. Moramo ukljuÄiti "meni za razvoj" (eng. develop menu) prvo.
+<<<<<<< HEAD
Otvorite postavke i idite na "napredni" (eng. advanced) dio. Tu se na dnu nalazi opcija koju trebate zabilježiti:
+=======
+Open Settings and go to the "Advanced" pane. There's a checkbox at the bottom:
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11

diff --git a/1-js/01-getting-started/4-devtools/chrome.png b/1-js/01-getting-started/4-devtools/chrome.png
deleted file mode 100644
index 4cb3ea2f4..000000000
Binary files a/1-js/01-getting-started/4-devtools/chrome.png and /dev/null differ
diff --git a/1-js/01-getting-started/4-devtools/chrome.webp b/1-js/01-getting-started/4-devtools/chrome.webp
new file mode 100644
index 000000000..bdf067079
Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome.webp differ
diff --git a/1-js/01-getting-started/4-devtools/[email protected] b/1-js/01-getting-started/4-devtools/[email protected]
new file mode 100644
index 000000000..2aeca5898
Binary files /dev/null and b/1-js/01-getting-started/4-devtools/[email protected] differ
diff --git a/1-js/01-getting-started/4-devtools/[email protected] b/1-js/01-getting-started/4-devtools/[email protected]
deleted file mode 100644
index b87404a8f..000000000
Binary files a/1-js/01-getting-started/4-devtools/[email protected] and /dev/null differ
diff --git a/1-js/01-getting-started/4-devtools/safari.png b/1-js/01-getting-started/4-devtools/safari.png
index 64c7a3f6c..4538827eb 100644
Binary files a/1-js/01-getting-started/4-devtools/safari.png and b/1-js/01-getting-started/4-devtools/safari.png differ
diff --git a/1-js/01-getting-started/4-devtools/[email protected] b/1-js/01-getting-started/4-devtools/[email protected]
index 27def4d09..1561b2bd9 100644
Binary files a/1-js/01-getting-started/4-devtools/[email protected] and b/1-js/01-getting-started/4-devtools/[email protected] differ
diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md
index 0d2f56157..39ad34535 100644
--- a/1-js/02-first-steps/01-hello-world/article.md
+++ b/1-js/02-first-steps/01-hello-world/article.md
@@ -9,7 +9,11 @@ Prvo, hajmo vidjeti kako povezati skriptu sa web stranicom. Za okruženja sa ser
## "script" oznaka (eng. tag)
+<<<<<<< HEAD
JavaScript programi mogu biti ubaÄeni u bilo koji dio HTML dokumenta uz pomoÄ `
```
+<<<<<<< HEAD
Ovdje, `/put/do/script.js` je apsolutan put do skripte od korijena stranice. Možete pružiti relativni put od trenutne stranice. Na primjer, `src="script.js"` bi znaÄilo `"script.js"` je u trenutnom folderu.
+=======
+Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="script.js"`, just like `src="./script.js"`, would mean a file `"script.js"` in the current folder.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Možemo dati i potpuni URL takoÄer. Na primjer:
diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md
index 032e41931..cf524269e 100644
--- a/1-js/02-first-steps/02-structure/article.md
+++ b/1-js/02-first-steps/02-structure/article.md
@@ -46,7 +46,11 @@ alert(3 +
+ 2);
```
+<<<<<<< HEAD
Kod Äe ispisati `6` jer JavaScript ne ubacuje taÄku-zarez ovdje. Ovdje je intuitivno oÄigledno da ako linija zavrÅ¡i sa plus znakom `"+"`, onda je to nepotpun izraz, tako da taÄka-zarez znak nije potreban. I u ovom sluÄaju radi kao i Å¡to je predviÄeno.
+=======
+The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so a semicolon there would be incorrect. And in this case, that works as intended.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
**Ali postoje situacije kada JavaScript "ne uspije" da pretpostavi gdje treba biti taÄka-zarez.
@@ -56,28 +60,45 @@ GreÅ¡ke koje se javljaju u ovim sluÄajevima su malo teže za pronaÄi i popravi
Ako te zanima konkretan primjer ovakve greške, pogledaj ovaj kod:
```js run
-[1, 2].forEach(alert)
+alert("Hello");
+
+[1, 2].forEach(alert);
```
+<<<<<<< HEAD
Ne trebate znati znaÄenje ovih zagrada `[]` i `forEach` joÅ¡. Kasnije Äemo ih nauÄiti. Za sada, zapamti rezultat koda: pokaže `1` pa onda `2`.
Sada, hajmo dodati `alert` prije koda i *ne* zavrÅ¡iti ga sa taÄkom-zarez.
```js run no-beautify
alert("Ovdje Äe biti greÅ¡ka")
+=======
+No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of running the code: it shows `Hello`, then `1`, then `2`.
+
+Now let's remove the semicolon after the `alert`:
+
+```js run no-beautify
+alert("Hello")
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
-[1, 2].forEach(alert)
+[1, 2].forEach(alert);
```
+<<<<<<< HEAD
Sada ako pokrenemo kod, samo Äe se prvi `alert` pokazati i onda imamo greÅ¡ku!
Ali sve je dobro ponovo ako dodamo taÄku-zarez poslije `alert`:
```js run
alert("Sada je sve ok");
+=======
+The difference compared to the code above is only one character: the semicolon at the end of the first line is gone.
-[1, 2].forEach(alert)
-```
+If we run this code, only the first `Hello` shows (and there's an error, you may need to open the console to see it). There are no numbers any more.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
+
+That's because JavaScript does not assume a semicolon before square brackets `[...]`. So, the code in the last example is treated as a single statement.
+<<<<<<< HEAD
Sada imamo "Sada je sve ok" poruku a nakon toga `1` i `2`.
@@ -90,6 +111,17 @@ alert("Ovdje Äe biti greÅ¡ka")[1, 2].forEach(alert)
```
Ali trebaju biti dvije razliÄite izjave, a ne jedna. Povezivanje u ovom sluÄaju je pogreÅ¡no, i zbog toga se javlja greÅ¡ka. Ovo se može desiti u joÅ¡ nekim situacijama.
+=======
+Here's how the engine sees it:
+
+```js run no-beautify
+alert("Hello")[1, 2].forEach(alert);
+```
+
+Looks weird, right? Such merging in this case is just wrong. We need to put a semicolon after `alert` for the code to work correctly.
+
+This can happen in other situations also.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
````
Mi preporuÄujemo pisanje taÄke-zareza izmeÄu izjava iako su odvojene novom linijom. Ovo pravilo je Å¡iroko usvojeno od strane zajednice. Hajdemo opet napomenuti *moguÄe* je izostaviti taÄku-zarez u veÄini vremena. Ali je sigurnije -- pogotovo za poÄetnika -- da se koriste.
diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md
index e7ed2907e..2388acf6c 100644
--- a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md
+++ b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md
@@ -6,7 +6,11 @@ To je jednostavno:
let imeNašePlanete = "Zemlja";
```
+<<<<<<< HEAD
Zapamtite, možemo koristiti kraÄe ime poput `planeta`, ali možda ne bi bilo oÄigledno na koju se planetu odnosi. Bolje je biti opÅ¡irniji kada imenujemo varijable. Ali ne trebamo biti preopÅ¡irni.
+=======
+Note, we could use a shorter name `planet`, but it might not be obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Ime trenutnog posjetioca stranice
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 5b695cfa8..a2dbd7907 100644
--- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md
+++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md
@@ -12,13 +12,24 @@ const roÄendan = '18.04.1982';
const godine = nekiKod(roÄendan);
```
+<<<<<<< HEAD
Ovdje imamo konstantu `roÄendan` i `godine` koje su izraÄunate sa `roÄendan` uz pomoÄ nekog koda (kod nije napisan radi kratkoÄe, i jer detalji ovdje nisu toliko bitni).
+=======
+Here we have a constant `birthday` for the date, and also the `age` constant.
+
+The `age` is calculated from `birthday` using `someCode()`, which means a function call that we didn't explain yet (we will soon!), but the details don't matter here, the point is that `age` is calculated somehow based on the `birthday`.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Da li bi bilo ispravno da se koriste velika slova za `roÄendan`? Za `godine`? Ili za oboje?
```js
+<<<<<<< HEAD
const ROÄENDAN = '18.04.1982'; // napisati velikim?
const GODINE = nekiKod(ROÄENDAN); // napisati velikim?
-```
+=======
+const BIRTHDAY = '18.04.1982'; // make birthday uppercase?
+const AGE = someCode(BIRTHDAY); // make age uppercase?
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
+```
diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md
index fa8eb887c..37ffe3148 100644
--- a/1-js/02-first-steps/04-variables/article.md
+++ b/1-js/02-first-steps/04-variables/article.md
@@ -24,7 +24,11 @@ Sada, možemo staviti podatke u nju putem operatora dodjele `=`:
let poruka;
*!*
+<<<<<<< HEAD
poruka = 'Hello'; // store the string
+=======
+message = 'Hello'; // store the string 'Hello' in the variable named message
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
*/!*
```
@@ -63,7 +67,12 @@ let godine = 25;
let poruka = 'Hello';
```
+<<<<<<< HEAD
Neki ljudi isto deklarišu više varijabli u ovom multilinijskom stilu:
+=======
+Some people also define multiple variables in this multiline style:
+
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js no-beautify
let korisnik = 'John',
godine = 25,
@@ -87,22 +96,37 @@ U starijim skriptama, možete naÄi joÅ¡ jednu kljuÄnu rijeÄ: `var` umjesto `l
*!*var*/!* poruka = 'Hello';
```
+<<<<<<< HEAD
`var` kljuÄna rijec je *skoro* ista kao `let`. Isto deklariÅ¡e varijablu, ali u malom drugaÄijem, "stara-Å¡kola" (eng. old-school) naÄinu.
Postoji manjih razlika izmeÄu `let` i `var`, ali one za nas nisu joÅ¡ bitne. ProuÄit Äemo ih u detalje u poglavlju .
+=======
+The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way.
+
+There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter .
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
````
## Stvarna životna analogija
Možemo lagano shvatiti koncept "varijable" ako je zamislimo kao "kutiju" za podatke, sa unikatno imenovanim naljepnicama.
+<<<<<<< HEAD
Na primjer, varijabla `poruka` može biti zamišljena kao kutija na kojoj piše `"poruka"` koja u sebi ima vrijednost "Hello!".
+=======
+For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it:
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11

Možemo postaviti bilo koju vrijednost u kutiju.
+<<<<<<< HEAD
Isto tako je možemo promijeniti koliko puta hoÄemo:
+=======
+We can also change it as many times as we want:
+
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js run
let poruka;
@@ -148,12 +172,21 @@ let poruka = "Ono"; // SyntaxError: 'poruka' has already been declared (ova gre
Tako da varijablu samo jednom trebamo deklarisati, i onda se na nju odnositi bez `let`.
````
+<<<<<<< HEAD
```smart header="Funkcionalni jezici"
Interesantno je napomenuti da postoje [funkcionalni](https://en.wikipedia.org/wiki/Functional_programming) programski jezici, kao što je [Scala](http://www.scala-lang.org/) ili [Erlang](http://www.erlang.org/) koji zabranjuju mijenjanje vrijednosti varijable.
+=======
+```smart header="Functional languages"
+It's interesting to note that there exist so-called [pure functional](https://en.wikipedia.org/wiki/Purely_functional_programming) programming languages, such as [Haskell](https://en.wikipedia.org/wiki/Haskell), that forbid changing variable values.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
U takvim jezicima, kada je vrijednost pohranjena "u kutiju", tu je zauvijek. Ako želimo da pohranimo nešto drugo, jezik nas tjera da napravimo novu kutiju (da deklarišemo novu varijablu). Ne možemo ponovo upotrijebiti staru.
+<<<<<<< HEAD
Iako zvuÄi malo Äudno na prvi pogled, ovi jezici su veoma sposobni za ozbiljan razvoj. JoÅ¡ bolje od toga, postoje podruÄja kao Å¡to su paralelno raÄunanje gdje ova limitacija doprinosi odreÄene prednosti. UÄenje ovakvih jezika (iako ih ne planirate koristiti ubrzo) je preporuÄeno da proÅ¡irite um.
+=======
+Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```
## Imenovanje varijabli [#variable-naming]
@@ -170,7 +203,11 @@ let korisniÄkoIme;
let test123;
```
+<<<<<<< HEAD
Kada ime sadrži viÅ¡e rijeÄi, [camelCase](https://en.wikipedia.org/wiki/CamelCase) se Äesto koristi. To je: rijeÄi idu jedna iza druge, svaka rijeÄ osim prve zapoÄinje sa velikim slovom `mojeVeomaDugoIme`.
+=======
+When the name contains multiple words, [camelCase](https://en.wikipedia.org/wiki/CamelCase) is commonly used. That is: words go one after another, with each word except the first starting with a capital letter: `myVeryLongName`.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Å ta je interesantno -- znak dolara `'$'` i donja crtica `'_'` mogu se isto koristiti u imenima. To su obiÄni simboli, kao i slova, bez specijalnog znaÄenja.
@@ -191,6 +228,7 @@ let 1a; // ne smije zapoÄeti sa brojem
let my-name; // crtica nije dozvoljena u imenu
```
+<<<<<<< HEAD
```smart header="Velika i mala slova su bitna"
Varijable nazvane `jabuka` i `jabuKA` su dvije razliÄite varijable.
```
@@ -199,13 +237,25 @@ Varijable nazvane `jabuka` i `jabuKA` su dvije razliÄite varijable.
/ Napomena: Ja prevodim sa engleskog na bosanski jezik, zato koristim Ä,Ä,Å¡,Ä,ž u varijablama, ali preporuÄeno je koristiti normalna engleska slova. Od sada Äe varijable i cijeli kod biti na engleskom jeziku, ne brinite, i dalje Äu prevesti tekst zadataka, ali Äe kod u istim biti na engleski. - prevodioc @aiyodev /
MoguÄe je koristiti bilo koji jezik, ukljuÄujuÄi ÄiriliÄna slova i Äak hieroglife, kao ovdje:
+=======
+```smart header="Case matters"
+Variables named `apple` and `APPLE` are two different variables.
+```
+
+````smart header="Non-Latin letters are allowed, but not recommended"
+It is possible to use any language, including Cyrillic letters, Chinese logograms and so on, like this:
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js
let Ð¸Ð¼Ñ = '...';
let æ = '...';
```
+<<<<<<< HEAD
TehniÄki, ovdje nema greÅ¡ke. Ovakva imena su dozvoljena, ali postoji internacionalna konvencija da se koristi engleski jezik prilikom imenovanja. Iako piÅ¡emo malu skriptu, može imati dug život ispred sebe. Ljudi iz ostalih zemalja Äe možda nekada trebati da proÄitaju.
+=======
+Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
````
````warn header="Rezervisana imena"
@@ -260,12 +310,20 @@ const myBirthday = '18.04.1982';
myBirthday = '01.01.2001'; // greška, ne može se promijeniti vrijednost konstante!
```
+<<<<<<< HEAD
Kada je programer siguran da se varijabla nikada neÄe promijeniti, onda je mogu deklarisati putem `const` da garantuju i da jasno komuniciraju tu Äinjenicu svima.
+=======
+When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
### Konstante napisane velikim slovima
+<<<<<<< HEAD
Postoji široko rasprostranjena praksa da se konstante koriste kao nadimak za vrijednosti koje su poznate prije izvršavanja i koje je teško zapamtiti.
+=======
+There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Takve konstante nazivamo koristeÄi velika slova i donje crtice.
@@ -290,16 +348,29 @@ Prednosti:
Kada trebamo koristiti velika slova za konstante a kada Äemo ih nazvati normalno? Hajmo uÄiniti to malo jasnije.
+<<<<<<< HEAD
"Konstantna" varijabla znaÄi da se njena vrijednost nikada ne mijenja. Ali postoje konstante koje su poznate prije izvrÅ¡avanja (kao heksadecimalna vrijednost za crvenu boju) i postoje konstante koje su *izraÄunate* tokom izvrÅ¡avanja, ali se opet ne mijenjaju nakon poÄetne dodjele.
Na primjer:
+=======
+Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment.
+
+For instance:
+
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js
const pageLoadTime = /* vrijeme potrebno da se stranica uÄita */;
```
+<<<<<<< HEAD
Vrijednost `pageLoadTime` nije poznata prije uÄitavanja stranice, tako da je ovako nazvana. Ali i dalje je konstanta jer ne mijenja vrijednost poslije inicijalne dodjele.
U drugim rijeÄima, konstante napisane velikim slovima se samo koriste kao nadimci za "tvrdo-kodirane" vrijednosti.
+=======
+The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment.
+
+In other words, capital-named constants are only used as aliases for "hard-coded" values.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Imenujte varijable ispravno
@@ -307,18 +378,31 @@ PriÄajuÄi o varijablama, ima joÅ¡ jedna ekstremno bitna stvar.
Ime varijable mora imati jasno, oÄigledno znaÄenje, koje opisuje podatke koje u sebe pohranjuje.
+<<<<<<< HEAD
Imenovanje varijabli je jedna od najvažnijih i najkompleksnijih vjeÅ¡tina u programiranju. Brz pogled na neÄija imena varijabli može otkriti da li je taj kod pisan od strane poÄetnika ili iskusnog programera.
U pravom projektu, veÄina vremena je potroÅ¡ena na modificiranje i proÅ¡irivanje veÄ postojeÄe baze koda a ne pisanja neÄega totalno odvojenog od poÄetka. Kada se vratimo na kod nakon nekog vremena, puno je lakÅ¡e naÄi informacije koje su dobro imenovane. Ili, u drugim rijeÄima, kada varijable imaju jasna imena.
+=======
+Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer.
+
+In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Molimo vas, provedite vremena razmiÅ¡ljajuÄi o dobrom imenu za varijablu prije nego Å¡to je deklariÅ¡ete. RadeÄi to, isplatit Äe vam se kasnije.
Neka dobra pravila za pratiti su:
+<<<<<<< HEAD
- Koristite imena koja ljudi mogu proÄitati kao Å¡to su `userName` ili `shoppingCart`.
- Nemojte koristiti skraÄenice ili kratka imena kao `a`, `b` ili `c`, osim ako znate sto posto Å¡ta radite.
- Nek vam imena budu maksimalno detaljna, ali i sažeta. Primjeri loÅ¡ih imena su `data` i `value`. Takva imena ne govore niÅ¡ta. Okej ih je samo koristiti u kontekstu kada je izuzetno oÄigledno na Å¡ta se varijabla odnosi.
- Prihvatite uslove unutar vašeg tima. Ako je posjetilac stranice "user" onda trebamo nazvati srodne varijable `currentUser` ili `newUser` umjesto `currentVisitor` ili `newManInTown`.
+=======
+- Use human-readable names like `userName` or `shoppingCart`.
+- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing.
+- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing.
+- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
ZvuÄi jednostavno? Itekako jeste, ali kreiranje deskriptivnih i sažetih imena za varijable u praksi nije. Probaj sam.
diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md
index 1e5d07ba7..9992c0da1 100644
--- a/1-js/02-first-steps/05-types/article.md
+++ b/1-js/02-first-steps/05-types/article.md
@@ -46,13 +46,23 @@ Pored obiÄnih brojeva, postoje takozvane "specialne numeriÄke vrijednosti" (en
alert( "not a number" / 2 ); // NaN, ovakva podjela je nepravilna
```
+<<<<<<< HEAD
`NaN` je ljepljiv. Bilo koje dodatne operacije na `NaN` vraÄaju `NaN`:
+=======
+ `NaN` is sticky. Any further mathematical operation on `NaN` returns `NaN`:
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js run
- alert( "not a number" / 2 + 5 ); // NaN
+ alert( NaN + 1 ); // NaN
+ alert( 3 * NaN ); // NaN
+ alert( "not a number" / 2 - 1 ); // NaN
```
+<<<<<<< HEAD
Tako da, ako je negdje `NaN` u matematiÄkom izrazu, Å¡iri se na Äitav rezultat.
+=======
+ So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result (there's only one exception to that: `NaN ** 0` is `1`).
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```smart header="MatematiÄke operacije su sigurne"
Raditi matematiku u JavaScript-u je "sigurno". Možemo uraditi bilo Å¡ta: podijeliti sa nulom, ophoditi se sa ne numeriÄkim tekstom kao brojem, itd.
@@ -64,11 +74,30 @@ Specijalne numeriÄke vrijednosti formalno pripadaju "number" tipu. Naravno, nis
NauÄit Äemo viÅ¡e o brojevima i kako raditi sa njima u poglavlju .
+<<<<<<< HEAD
## BigInt (veliki cijeli broj)
U JavaScript-u, "number" tip ne može reprezentirati vrijednost cijelih brojeva veÄih od (253 -1) (to je `9007199254740991`), ili manjih od -(253 -1) za negativne brojeve. To je tehniÄko ograniÄenje izazvano od unutraÅ¡nje reprezentacije.
Za veÄinu namjena to je dovoljno, ali nekada nam trebaju veoma veliki brojevi, na primjer za kriptografiju ili vremenske oznake precizne u mikrosekundu.
+=======
+## BigInt [#bigint-type]
+
+In JavaScript, the "number" type cannot safely represent integer values larger than (253 -1) (that's `9007199254740991`), or less than -(253 -1) for negatives.
+
+To be really precise, the "number" type can store larger integers (up to 1.7976931348623157 * 10308 ), but outside of the safe integer range ±(253 -1) there'll be a precision error, because not all digits fit into the fixed 64-bit storage. So an "approximate" value may be stored.
+
+For example, these two numbers (right above the safe range) are the same:
+
+```js
+console.log(9007199254740991 + 1); // 9007199254740992
+console.log(9007199254740991 + 2); // 9007199254740992
+```
+
+So to say, all odd integers greater than (253 -1) can't be stored at all in the "number" type.
+
+For most purposes ±(253 -1) range is quite enough, but sometimes we need the entire range of really big integers, e.g. for cryptography or microsecond-precision timestamps.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
`BigInt` tip je nedavno dodan u jezik da reprezentira cijele brojeve proizvoljne dužine.
@@ -81,11 +110,15 @@ const bigInt = 1234567890123456789012345678901234567890n;
Jer `BigInt` brojevi su rijetko potrebni, neÄemo ovdje iÄi u detalje, ali imamo odvojeno poglavlje gdje možete nauÄiti viÅ¡e o njima . ProÄitajte to ako i kad radili sa velikim brojevima.
+<<<<<<< HEAD
```smart header="Problemi kompatibilnosti"
Trenutno `BigInt` je podržan u Firefox-u/Chrome-u/Edge-u, ali nije u Safari-u/Internet Explorer-u.
```
## String (tekst)
+=======
+## String
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Tekst u JavaScript-u mora biti okružen navodnicima.
@@ -127,7 +160,11 @@ NauÄit Äemo o tekstu viÅ¡e u ovom poglavlju .
```smart header="Ne postoji *character* tip"
U nekim jezicima, postoji specialan "character" tip za pojedinaÄan karakter (znak,slovo,broj). Na primjer, u C jeziku i u Javi ima, i zove se "char".
+<<<<<<< HEAD
U JavaScript-u, ne postoji takav tip. Postoji samo jedan tip: `string`. Tekst se može sastojati samo od jednog karaktera ili više njih.
+=======
+In JavaScript, there is no such type. There's only one type: `string`. A string may consist of zero characters (be empty), one character or many of them.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```
## Boolean (logiÄki tip)
@@ -208,6 +245,7 @@ PoÅ¡to su tako bitni, objekti zaslužuju specijalni tretman. O njima Äemo kasni
## typeof operator [#type-typeof]
+<<<<<<< HEAD
`typeof` operator vraÄa tip podatka datog argumenta. Koristan je kada želimo procesirati vrijednosti razliÄitih tipova drugaÄije ili kada želimo uraditi brzu provjeru.
Podržava dvije vrste sintakse:
@@ -218,6 +256,11 @@ Podržava dvije vrste sintakse:
U drugim rijeÄima, radi sa zagradama ili bez njih. Rezultat je isti.
Poziv na `typeof x` vraÄa tekst sa imenom tipa podatka:
+=======
+The `typeof` operator returns the type of the operand. It's useful when we want to process values of different types differently or just want to do a quick check.
+
+A call to `typeof x` returns a string with the type name:
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js
typeof undefined // "undefined"
@@ -247,14 +290,33 @@ typeof alert // "function" (3)
Zadnje tri linije možda trebaju dodatno objašnjenje:
+<<<<<<< HEAD
1. `Math` je ugraÄen (eng. built-in) objekat koji pruža matematiÄke operacije. UÄit Äemo viÅ¡e o tome poglavlju . Ovjde, služi samo kao primjer objekta.
2. Rezultat `typeof null` je `"object"`. To je zvaniÄno priznata greÅ¡ka u ponaÅ¡asanju `typeof`, dolazi iz ranih dana JavaScript-a i zadržano je za kompatibilnost. Definitivno, `null` nije objekat. To je specijalna vrijednost sa svojim odvojenim tipom.
3. Rezultat `typeof alert` je `"function"`, jer je `alert` funkcija. UÄit Äemo o funkcijama u sljedeÄim poglavljima gdje Äemo vidjeti da ne postoji specijalni "function" tip u JavaScript-u. Funkcije pripadaju tipu objekat (eng. object). Ali `typeof` se ophodi drugaÄije, i vraÄa `"function"`. I ovo isto dolazi iz ranih dana JavaScript-a. TehniÄki, ovakvo ponaÅ¡anje nije taÄno, ali može biti prikladno u praksi.
## Sažetak
+=======
+1. `Math` is a built-in object that provides mathematical operations. We will learn it in the chapter . Here, it serves just as an example of an object.
+2. The result of `typeof null` is `"object"`. That's an officially recognized error in `typeof`, coming from very early days of JavaScript and kept for compatibility. Definitely, `null` is not an object. It is a special value with a separate type of its own. The behavior of `typeof` is wrong here.
+3. The result of `typeof alert` is `"function"`, because `alert` is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently, returning `"function"`. That also comes from the early days of JavaScript. Technically, such behavior isn't correct, but can be convenient in practice.
+
+```smart header="The `typeof(x)` syntax"
+You may also come across another syntax: `typeof(x)`. It's the same as `typeof x`.
+
+To put it clear: `typeof` is an operator, not a function. The parentheses here aren't a part of `typeof`. It's the kind of parentheses used for mathematical grouping.
+
+Usually, such parentheses contain a mathematical expression, such as `(2 + 2)`, but here they contain only one argument `(x)`. Syntactically, they allow to avoid a space between the `typeof` operator and its argument, and some people like it.
+
+Some people prefer `typeof(x)`, although the `typeof x` syntax is much more common.
+```
+
+## Summary
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Postoji 8 osnovnih tipova podataka u JavaScript-u.
+<<<<<<< HEAD
- `number` za brojeve bilo koje vrste: cijeli broj ili broj sa decimalnim zarezom, cijeli brojevi su ograniÄeni do ±253 .
- `bigint` je za cijele brojeve proizvoljne dužine.
- `string` za tekst. String može imati nula ili više karaktera, ne postoji odvojen singl-karakter tip podatka.
@@ -263,11 +325,29 @@ Postoji 8 osnovnih tipova podataka u JavaScript-u.
- `undefined` za ne dodijeljene vrijednosti -- samostalni tip koji imaju samo jednu vrijednost `undefined`.
- `object` za kompleksnije strukture podataka.
- `symbol` za unikatne identifikatore.
+=======
+- Seven primitive data types:
+ - `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253 -1).
+ - `bigint` for integer numbers of arbitrary length.
+ - `string` for strings. A string may have zero or more characters, there's no separate single-character type.
+ - `boolean` for `true`/`false`.
+ - `null` for unknown values -- a standalone type that has a single value `null`.
+ - `undefined` for unassigned values -- a standalone type that has a single value `undefined`.
+ - `symbol` for unique identifiers.
+- And one non-primitive data type:
+ - `object` for more complex data structures.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
`typeof` operator nam dozvoljava da vidimo koji tip podatka je pohranjen u nekoj varijabli.
+<<<<<<< HEAD
- Dvije vrste: `typeof x` ili `typeof(x)`.
- VraÄa tekst sa imenom tipa podatka, na primjer `"string"`.
- Za `null` vraÄa `"object"` -- ovo je greÅ¡ka u jeziku, nije zapravo objekat.
+=======
+- Usually used as `typeof x`, but `typeof(x)` is also possible.
+- Returns a string with the name of the type, like `"string"`.
+- For `null` returns `"object"` -- this is an error in the language, it's not actually an object.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
U sljedeÄim poglavljima, koncetrirat Äemo se na primitivne vrijednosti i kada se upoznamo s njima, preÄi Äemo na objekte.
diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md
index 29ac6aa45..e3fa88281 100644
--- a/1-js/02-first-steps/06-alert-prompt-confirm/article.md
+++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md
@@ -30,8 +30,13 @@ Prikaže modalni prozorÄiÄ sa tekstualnom porukom, polje za unos, i tipke OK/C
`default`
: Neobavezni drugi parametar, inicijalna vrijednost za polje unosa.
+<<<<<<< HEAD
```smart header="Srednje zagrade u sintaksi `[...]`"
Srednje zagrade oko `default` u sintaksi pokazuju da je taj parametar opcionalan, nije potreban.
+=======
+```smart header="The square brackets in syntax `[...]`"
+The square brackets around `default` in the syntax above denote that the parameter is optional, not required.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```
Posjetilac može neÅ¡to u polje napisati i pritisnuti OK. Onda Äemo dobiti taj tekst kao `result`. Ili mogu prekinuti unos pritiskom na Cancel ili `key:Esc` tipku, i tada dobijamo `null` kao `rezultat`.
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 79f75c8e5..06f536862 100644
--- a/1-js/02-first-steps/07-type-conversions/article.md
+++ b/1-js/02-first-steps/07-type-conversions/article.md
@@ -6,8 +6,13 @@ Na primjer, `alert` automatski pretvara bilo koju vrijednost u string da je prik
Postoje sluÄajevi kada trebamo eksplicitno pretvoriti vrijednost u oÄekivani tip podatka.
+<<<<<<< HEAD
```smart header="Ne priÄamo joÅ¡ o objektima"
U ovom poglavlju, neÄemo preÄi objekte. Za sada priÄamo samo o primitivnim vrijednostima i tipovima.
+=======
+```smart header="Not talking about objects yet"
+In this chapter, we won't cover objects. For now, we'll just be talking about primitives.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Kasnije, kada nauÄimo o objektima, u poglavlju pogledat Äemo kako se objekti uklapaju.
```
@@ -34,7 +39,11 @@ Pretvaranje u string je veÄinom oÄigledno. `false` postaje `"false"`, `null` p
## Pretvaranje u brojeve
+<<<<<<< HEAD
Pretvaranje u brojeve se u matematiÄkim funkcijama i izrazima vrÅ¡i automatski.
+=======
+Numeric conversion in mathematical functions and expressions happens automatically.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Na primjer, kada primjenjujemo dijeljenje `/` na vrijednostima koje nisu brojevi:
@@ -69,8 +78,13 @@ Pravila prilikom pretvaranja u brojeve:
|-------|-------------|
|`undefined`|`NaN`|
|`null`|`0`|
+<<<<<<< HEAD
|true i false | `1` i `0` |
| `string` | Razmaci sa poÄetka i kraja Äe biti izbrisani. Ako je preostali string prazan, rezultat je `0`. U suprotnom, broj je "oÄitan" iz string-a. GreÅ¡ka prilikom konverzije Äe dati `NaN`. |
+=======
+|true and false | `1` and `0` |
+| `string` | Whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. |
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Primjer:
@@ -130,7 +144,11 @@ Pravila konverzije:
|`undefined`|`NaN`|
|`null`|`0`|
|true / false | `1 / 0` |
+<<<<<<< HEAD
| `string` | String je proÄitan "kakav jeste", razmaci sa obe strane su ignorisani. Prazan string je `0`. GreÅ¡ka prilikom konverzije daje `NaN`. |
+=======
+| `string` | The string is read "as is", whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. |
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
**`Pretvaranje u boolean`** -- Javlja se u logiÄkim operacijama. Konverzija može biti izvrÅ¡ena sa `Boolean(value)`.
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 d7c925f28..a1f6eb092 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
@@ -9,7 +9,6 @@ true + false = 1
"$" + 4 + 5 = "$45"
"4" - 2 = 2
"4px" - 2 = NaN
-7 / 0 = Infinity
" -9 " + 5 = " -9 5" // (3)
" -9 " - 5 = -14 // (4)
null + 1 = 1 // (5)
@@ -17,6 +16,7 @@ undefined + 1 = NaN // (6)
" \t \n" - 2 = -2 // (7)
```
+<<<<<<< HEAD
1. Sabiranje sa string-om `"" + 1` pretvara `1` u string: `"" + 1 = "1"`, onda imamo `"1" + 0`, isto pravilo je primijenjeno.
2. Oduzimanje `-` (kao veÄina matematiÄkih operacija) radi samo sa brojevima, uvijek pretvara prazan string `""` u `0`.
3. Sabiranje sa string-om nadodaje broj `5` na string.
@@ -24,3 +24,12 @@ undefined + 1 = NaN // (6)
5. `null` postaje `0` nakon numeriÄke konverzije.
6. `undefined` postaje `NaN` nakon numeriÄke konverzije.
7. Razmaci su sklonjeni sa poÄetka i kraja string-a kada ga pretvaramo u broj. Ovdje se Äitav string sastoji od razmaka, kao Å¡to su `\t`, `\n` i "obiÄni" razmak izmeÄu njih. Tako da, sliÄno praznom string-u, postaje `0`.
+=======
+1. The addition with a string `"" + 1` converts `1` to a string: `"" + 1 = "1"`, and then we have `"1" + 0`, the same rule is applied.
+2. The subtraction `-` (like most math operations) only works with numbers, it converts an empty string `""` to `0`.
+3. The addition with a string appends the number `5` to the string.
+4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it).
+5. `null` becomes `0` after the numeric conversion.
+6. `undefined` becomes `NaN` after the numeric conversion.
+7. Space characters are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md
index 6b8f4b129..0df3a0ba0 100644
--- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md
+++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md
@@ -16,7 +16,6 @@ true + false
"$" + 4 + 5
"4" - 2
"4px" - 2
-7 / 0
" -9 " + 5
" -9 " - 5
null + 1
diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md
index bdaa7fb59..eeea00e2d 100644
--- a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md
+++ b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md
@@ -9,7 +9,11 @@ let b = "2"; // prompt("Second number?", 2);
alert(a + b); // 12
```
+<<<<<<< HEAD
Ono Å¡ta mi trebamo uraditi jeste pretvoriti string-ove u brojeve prije `+`. Na primjer, koristeÄi `Number()` ili dodavajuÄi `+` ispred njih.
+=======
+What we should do is to convert strings to numbers before `+`. For example, using `Number()` or prepending them with `+`.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Na primjer, prije `prompt`:
diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md
index b2dc2e6d9..634e75df7 100644
--- a/1-js/02-first-steps/08-operators/article.md
+++ b/1-js/02-first-steps/08-operators/article.md
@@ -50,23 +50,46 @@ Rezultat `a % b` je [ostatak](https://en.wikipedia.org/wiki/Remainder) prilikom
Na primjer:
```js run
+<<<<<<< HEAD
alert( 5 % 2 ); // 1, ostatak prilikom dijeljenja 5 sa 2
alert( 8 % 3 ); // 2, ostatak prilikom dijeljenja 8 sa 3
+=======
+alert( 5 % 2 ); // 1, the remainder of 5 divided by 2
+alert( 8 % 3 ); // 2, the remainder of 8 divided by 3
+alert( 8 % 4 ); // 0, the remainder of 8 divided by 4
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```
### Eksponencija **
+<<<<<<< HEAD
Operator eksponencije `a ** b` množi `a` sa sobom `b` puta.
+=======
+The exponentiation operator `a ** b` raises `a` to the power of `b`.
+
+In school maths, we write that as ab .
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Na primjer:
```js run
+<<<<<<< HEAD
alert( 2 ** 2 ); // 4 (2 pomnoženo sa sobom 2 puta)
alert( 2 ** 3 ); // 8 (2 * 2 * 2, 3 puta)
alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2, 4 puta)
```
MatematiÄki, eksponencija je odreÄena i za ne-cijele brojeve. Na primjer, korijen je eksponencija od `1/2`:
+=======
+alert( 2 ** 2 ); // 2² = 4
+alert( 2 ** 3 ); // 2³ = 8
+alert( 2 ** 4 ); // 2â´ = 16
+```
+
+Just like in maths, the exponentiation operator is defined for non-integer numbers as well.
+
+For example, a square root is an exponentiation by ½:
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js run
alert( 4 ** (1/2) ); // 2 (stepen od 1/2 je isti kao i kvadratni korijen)
@@ -76,7 +99,11 @@ alert( 8 ** (1/3) ); // 2 (stepen od 1/3 je isti kao kubiÄni korijen)
## Spajanje stringova sa binarnim +
+<<<<<<< HEAD
Upoznajmo se sa moguÄnostima JavaScript operatora koji su izvan Å¡kolske aritmetike.
+=======
+Let's meet the features of JavaScript operators that are beyond school arithmetics.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
ObiÄno, plus operator `+` sabira brojeve.
@@ -104,7 +131,16 @@ Evo jedan kompleksniji primjer:
alert(2 + 2 + '1' ); // "41" a ne "221"
```
+<<<<<<< HEAD
Ovdje, operatori rade jedan poslije drugog. Prvi `+` sabira dva broja, tako da vraÄa `4`, onda sljedeÄi `+` dodaje string `1`, tako da bude `4 + '1' = 41`.
+=======
+Here, operators work one after another. The first `+` sums two numbers, so it returns `4`, then the next `+` adds the string `1` to it, so it's like `4 + '1' = '41'`.
+
+```js run
+alert('1' + 2 + 2); // "122" and not "14"
+```
+Here, the first operand is a string, the compiler treats the other two operands as strings too. The `2` gets concatenated to `'1'`, so it's like `'1' + 2 = "12"` and `"12" + 2 = "122"`.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Binarni `+` je jedini operator koji podržava string-ove na ovaj naÄin. Ostali aritmetiÄki operatori rade samo sa brojevima i uvijek pretvaraju operand-e u brojeve.
@@ -180,11 +216,16 @@ Zagrade nadjaÄavaju prioritete, tako da ako nismo zadovoljni sa uobiÄajenim re
Postoji mnogo operatora u JavaScript-u. Svaki operator ima odreÄeni nivo prioriteta. Onaj sa veÄim nivoom se prvi izvrÅ¡ava. Ako je prioritet isti, red izvrÅ¡avanja je od lijeve ka desnoj strani.
+<<<<<<< HEAD
Evo ekstrakta iz [tabele prioriteta](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence) (ne trebate ovo zapamtiti, ali zapamtite samo da su unary operatori veÄi od odgovarajuÄeg binarnog):
+=======
+Here's an extract from the [precedence table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones):
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
| Prioritet | Ime | Znak |
|------------|------|------|
| ... | ... | ... |
+<<<<<<< HEAD
| 17 | unary plus | `+` |
| 17 | unary negacija | `-` |
| 16 | eksponencija | `**` |
@@ -197,12 +238,32 @@ Evo ekstrakta iz [tabele prioriteta](https://developer.mozilla.org/en/JavaScript
| ... | ... | ... |
Kao Å¡to možemo da vidimo, "unary plus" ima prioritet `17` koji je veÄi od `13` prioriteta "sabiranja" (binarni plus). Zato, u izrazu `"+apples + +oranges"`, unary plus se prije sabiranja izvrÅ¡ava.
+=======
+| 14 | unary plus | `+` |
+| 14 | unary negation | `-` |
+| 13 | exponentiation | `**` |
+| 12 | multiplication | `*` |
+| 12 | division | `/` |
+| 11 | addition | `+` |
+| 11 | subtraction | `-` |
+| ... | ... | ... |
+| 2 | assignment | `=` |
+| ... | ... | ... |
+
+As we can see, the "unary plus" has a priority of `14` which is higher than the `11` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Dodjela
+<<<<<<< HEAD
Zapamtite da je dodjela `=` isto operator. Na listi prioriteta ima mali prioritet `3`.
Zato, kada dodijelimo vrijednost varijabli, `x = 2 * 2 + 1`, kalkulacije su izvrÅ¡ene prvo pa je `=` evaluiran, pohranjujuÄi rezultat u varijablu `x`.
+=======
+Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `2`.
+
+That's why, when we assign a variable, like `x = 2 * 2 + 1`, the calculations are done first and then the `=` is evaluated, storing the result in `x`.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js
let x = 2 * 2 + 1;
@@ -214,7 +275,11 @@ alert( x ); // 5
Äinjenica da je `=` operator, a ne "magiÄni" jeziÄki konstrukt ima interesantnu implikaciju.
+<<<<<<< HEAD
VeÄina operatora u JavaScript-u vraÄaju vrijednost. To je oÄigledno za `+` i `-`, ali to isto vrijedi za `=`.
+=======
+All operators in JavaScript return a value. That's obvious for `+` and `-`, but also true for `=`.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
Poziv `x = value` piÅ¡e `value` u `x` *pa je vraÄa*.
@@ -256,7 +321,11 @@ alert( c ); // 4
LanÄane dodjele se evaluiraju s desna na lijevo. Prvo, najdesniji izraz `2 + 2` je evaluiran pa je dodijeljen varijablama na lijevoj strani: `c`, `b` i `a`. Na kraju, sve varijable dijele istu vrijednost.
+<<<<<<< HEAD
JoÅ¡ jednom, kako bi naÅ¡ kod bio Äitljiviji, bolje ga je podijeliti u nekoliko linija:
+=======
+Once again, for the purposes of readability it's better to split such code into a few lines:
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```js
c = 2 + 2;
@@ -294,9 +363,13 @@ Takvi operatori imaju isti prioritet kao i prilikom normalnog dodijeljivanja, ta
```js run
let n = 2;
-n *= 3 + 5;
+n *= 3 + 5; // right part evaluated first, same as n *= 8
+<<<<<<< HEAD
alert( n ); // 16 (desni dio je prvi evaluiran, isto kao n *= 8)
+=======
+alert( n ); // 16
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
```
## PoveÄavanje/smanjivanje (eng. increment/decrement)
@@ -428,7 +501,11 @@ Lista operatora:
- RIGHT SHIFT ( `>>` )
- ZERO-FILL RIGHT SHIFT ( `>>>` )
+<<<<<<< HEAD
Ovi operatori se veoma rijetko koriste, kada trebamo da radimo sa brojevima na veoma niskom (bitwise) nivou. Ovi operatori nam neÄe trebati u bližoj buduÄnosti, jer u web programiranju imaju malu upotrebu, ali u nekim specijalnim oblastima kao Å¡to su kriptografija, su korisni. Možete proÄitati [Bitwise Operatori](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) poglavlje na MDN kada vam zatreba.
+=======
+These operators are used very rarely, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) chapter on MDN when a need arises.
+>>>>>>> ff804bc19351b72bc5df7766f4b9eb8249a3cb11
## Zarez (eng. comma)
diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md
index a86a9f73e..632b1cf4e 100644
--- a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md
+++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md
@@ -14,7 +14,7 @@ Some of the reasons:
1. Obviously, true.
2. Dictionary comparison, hence false. `"a"` is smaller than `"p"`.
-3. Again, dictionary comparison, first char of `"2"` is greater than the first char of `"1"`.
+3. Again, dictionary comparison, first char `"2"` is greater than the first char `"1"`.
4. Values `null` and `undefined` equal each other only.
5. Strict equality is strict. Different types from both sides lead to false.
6. Similar to `(4)`, `null` only equals `undefined`.
diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md
index a323dc93d..a24af5688 100644
--- a/1-js/02-first-steps/09-comparison/article.md
+++ b/1-js/02-first-steps/09-comparison/article.md
@@ -7,11 +7,11 @@ In JavaScript they are written like this:
- Greater/less than: a > b, a < b.
- Greater/less than or equals: a >= b, a <= b.
- Equals: `a == b`, please note the double equality sign `==` means the equality test, while a single one `a = b` means an assignment.
-- Not equals. In maths the notation is ≠, but in JavaScript it's written as a != b.
+- Not equals: In maths the notation is ≠, but in JavaScript it's written as a != b.
-In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities.
+In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities.
-At the end you'll find a good recipe to avoid "javascript quirks"-related issues.
+At the end you'll find a good recipe to avoid "JavaScript quirks"-related issues.
## Boolean is the result
@@ -57,7 +57,9 @@ The algorithm to compare two strings is simple:
4. Repeat until the end of either string.
5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater.
-In the examples above, the comparison `'Z' > 'A'` gets to a result at the first step while the strings `"Glow"` and `"Glee"` are compared character-by-character:
+In the first example above, the comparison `'Z' > 'A'` gets to a result at the first step.
+
+The second comparison `'Glow'` and `'Glee'` needs more steps as strings are compared character-by-character:
1. `G` is the same as `G`.
2. `l` is the same as `l`.
@@ -210,5 +212,5 @@ Why did we go over these examples? Should we remember these peculiarities all th
- Comparison operators return a boolean value.
- Strings are compared letter-by-letter in the "dictionary" order.
- When values of different types are compared, they get converted to numbers (with the exclusion of a strict equality check).
-- The values `null` and `undefined` equal `==` each other and do not equal any other value.
+- The values `null` and `undefined` are equal `==` to themselves and each other, but do not equal any other value.
- Be careful when using comparisons like `>` or `<` with variables that can occasionally be `null/undefined`. Checking for `null/undefined` separately is a good idea.
diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md
index a4d943245..4305584fa 100644
--- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md
+++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md
@@ -6,7 +6,7 @@ importance: 2
Using the `if..else` construct, write the code which asks: 'What is the "official" name of JavaScript?'
-If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "Didn't know? ECMAScript!"
+If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "You don't know? ECMAScript!"

diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md
index 7327243b1..82e8800b9 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 (cond) {
## The "else" clause
-The `if` statement may contain an optional "else" block. It executes when the condition is false.
+The `if` statement may contain an optional `else` block. It executes when the condition is falsy.
For example:
```js run
@@ -181,9 +181,9 @@ alert( message );
It may be difficult at first to grasp what's going on. But after a closer look, we can see that it's just an ordinary sequence of tests:
1. The first question mark checks whether `age < 3`.
-2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon '":"', checking `age < 18`.
-3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon '":"', checking `age < 100`.
-4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon '":"', returning `'What an unusual age!'`.
+2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon ":", checking `age < 18`.
+3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon ":", checking `age < 100`.
+4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon ":", returning `'What an unusual age!'`.
Here's how this looks using `if..else`:
diff --git a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
index 5c2455ef4..368b59409 100644
--- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
+++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
@@ -1,6 +1,6 @@
The answer: `null`, because it's the first falsy value from the list.
```js run
-alert( 1 && null && 2 );
+alert(1 && null && 2);
```
diff --git a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md
index cc00ca9fc..fc9e336c1 100644
--- a/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md
+++ b/1-js/02-first-steps/11-logical-operators/6-check-if-in-range/task.md
@@ -4,6 +4,6 @@ importance: 3
# Check the range between
-Write an "if" condition to check that `age` is between `14` and `90` inclusively.
+Write an `if` condition to check that `age` is between `14` and `90` inclusively.
"Inclusively" means that `age` can reach the edges `14` or `90`.
diff --git a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md
index 7c22d6ad1..9b947d00f 100644
--- a/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md
+++ b/1-js/02-first-steps/11-logical-operators/7-check-if-out-range/task.md
@@ -4,6 +4,6 @@ importance: 3
# Check the range outside
-Write an `if` condition to check that `age` is NOT between 14 and 90 inclusively.
+Write an `if` condition to check that `age` is NOT between `14` and `90` inclusively.
Create two variants: the first one using NOT `!`, the second one -- without it.
diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md
index a30db7aae..604606259 100644
--- a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md
+++ b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md
@@ -3,19 +3,19 @@
```js run demo
let userName = prompt("Who's there?", '');
-if (userName == 'Admin') {
+if (userName === 'Admin') {
let pass = prompt('Password?', '');
- if (pass == 'TheMaster') {
+ if (pass === 'TheMaster') {
alert( 'Welcome!' );
- } else if (pass == '' || pass == null) {
+ } else if (pass === '' || pass === null) {
alert( 'Canceled' );
} else {
alert( 'Wrong password' );
}
-} else if (userName == '' || userName == null) {
+} else if (userName === '' || userName === null) {
alert( 'Canceled' );
} else {
alert( "I don't know you" );
diff --git a/1-js/02-first-steps/11-logical-operators/article.md b/1-js/02-first-steps/11-logical-operators/article.md
index f45cbe456..78c4fd2f1 100644
--- a/1-js/02-first-steps/11-logical-operators/article.md
+++ b/1-js/02-first-steps/11-logical-operators/article.md
@@ -1,6 +1,6 @@
# Logical operators
-There are three logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT).
+There are four logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT), `??` (Nullish Coalescing). Here we cover the first three, the `??` operator is in the next article.
Although they are called "logical", they can be applied to values of any type, not only boolean. Their result can also be of any type.
@@ -64,7 +64,7 @@ if (hour < 10 || hour > 18 || isWeekend) {
}
```
-## OR "||" finds the first truthy value
+## OR "||" finds the first truthy value [#or-finds-the-first-truthy-value]
The logic described above is somewhat classical. Now, let's bring in the "extra" features of JavaScript.
@@ -84,7 +84,7 @@ The OR `||` operator does the following:
A value is returned in its original form, without the conversion.
-In other words, a chain of OR `"||"` returns the first truthy value or the last one if no truthy value is found.
+In other words, a chain of OR `||` returns the first truthy value or the last one if no truthy value is found.
For instance:
@@ -101,9 +101,9 @@ This leads to some interesting usage compared to a "pure, classical, boolean-onl
1. **Getting the first truthy value from a list of variables or expressions.**
- For instance, we have `firstName`, `lastName` and `nickName` variables, all optional.
+ For instance, we have `firstName`, `lastName` and `nickName` variables, all optional (i.e. can be undefined or have falsy values).
- Let's use OR `||` to choose the one that has the data and show it (or `anonymous` if nothing set):
+ Let's use OR `||` to choose the one that has the data and show it (or `"Anonymous"` if nothing set):
```js run
let firstName = "";
@@ -115,7 +115,7 @@ This leads to some interesting usage compared to a "pure, classical, boolean-onl
*/!*
```
- If all variables were falsy, `Anonymous` would show up.
+ If all variables were falsy, `"Anonymous"` would show up.
2. **Short-circuit evaluation.**
@@ -123,7 +123,7 @@ This leads to some interesting usage compared to a "pure, classical, boolean-onl
It means that `||` processes its arguments until the first truthy value is reached, and then the value is returned immediately, without even touching the other argument.
- That importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call.
+ The importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call.
In the example below, only the second message is printed:
@@ -223,8 +223,8 @@ The precedence of AND `&&` operator is higher than OR `||`.
So the code `a && b || c && d` is essentially the same as if the `&&` expressions were in parentheses: `(a && b) || (c && d)`.
````
-````warn header="Don't replace `if` with || or &&"
-Sometimes, people use the AND `&&` operator as a "shorter to write `if`".
+````warn header="Don't replace `if` with `||` or `&&`"
+Sometimes, people use the AND `&&` operator as a "shorter way to write `if`".
For instance:
@@ -244,7 +244,7 @@ let x = 1;
if (x > 0) alert( 'Greater than zero!' );
```
-Although, the variant with `&&` appears shorter, `if` is more obvious and tends to be a little bit more readable. So we recommend using every construct for its purpose: use `if` if we want if and use `&&` if we want AND.
+Although, the variant with `&&` appears shorter, `if` is more obvious and tends to be a little bit more readable. So we recommend using every construct for its purpose: use `if` if we want `if` and use `&&` if we want AND.
````
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 c72dd91d6..0b2f092ab 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
@@ -2,32 +2,58 @@
[recent browser="new"]
-The nullish coalescing operator `??` provides a short syntax for selecting a first "defined" variable from the list.
+The nullish coalescing operator is written as two question marks `??`.
+
+As it treats `null` and `undefined` similarly, we'll use a special term here, in this article. For brevity, we'll say that a value is "defined" when it's neither `null` nor `undefined`.
The result of `a ?? b` is:
-- `a` if it's not `null` or `undefined`,
-- `b`, otherwise.
+- if `a` is defined, then `a`,
+- if `a` isn't defined, then `b`.
+
+In other words, `??` returns the first argument if it's not `null/undefined`. Otherwise, the second one.
+
+The nullish coalescing operator isn't anything completely new. It's just a nice syntax to get the first "defined" value of the two.
-So, `x = a ?? b` is a short equivalent to:
+We can rewrite `result = a ?? b` using the operators that we already know, like this:
```js
-x = (a !== null && a !== undefined) ? a : b;
+result = (a !== null && a !== undefined) ? a : b;
+```
+
+Now it should be absolutely clear what `??` does. Let's see where it helps.
+
+The common use case for `??` is to provide a default value.
+
+For example, here we show `user` if its value isn't `null/undefined`, otherwise `Anonymous`:
+
+```js run
+let user;
+
+alert(user ?? "Anonymous"); // Anonymous (user is undefined)
```
-Here's a longer example.
+Here's the example with `user` assigned to a name:
+
+```js run
+let user = "John";
+
+alert(user ?? "Anonymous"); // John (user is not null/undefined)
+```
-Imagine, we have a user, and there are variables `firstName`, `lastName` or `nickName` for their first name, last name and the nick name. All of them may be undefined, if the user decided not to enter any value.
+We can also use a sequence of `??` to select the first value from a list that isn't `null/undefined`.
-We'd like to display the user name: one of these three variables, or show "Anonymous" if nothing is set.
+Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be not defined, if the user decided not to fill in the corresponding values.
-Let's use the `??` operator to select the first defined one:
+We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are `null/undefined`.
+
+Let's use the `??` operator for that:
```js run
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
-// show the first not-null/undefined value
+// shows the first defined value:
*!*
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder
*/!*
@@ -35,23 +61,34 @@ alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder
## Comparison with ||
-The OR `||` operator can be used in the same way as `??`. Actually, we can replace `??` with `||` in the code above and get the same result, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value).
-
-The important difference is that:
-- `||` returns the first *truthy* value.
-- `??` returns the first *defined* value.
+The OR `||` operator can be used in the same way as `??`, as it was described in the [previous chapter](info:logical-operators#or-finds-the-first-truthy-value).
-This matters a lot when we'd like to treat `null/undefined` differently from `0`.
+For example, in the code above we could replace `??` with `||` and still get the same result:
-For example, consider this:
+```js run
+let firstName = null;
+let lastName = null;
+let nickName = "Supercoder";
-```js
-height = height ?? 100;
+// shows the first truthy value:
+*!*
+alert(firstName || lastName || nickName || "Anonymous"); // Supercoder
+*/!*
```
-This sets `height` to `100` if it's not defined.
+Historically, the OR `||` operator was there first. It's been there since the beginning of JavaScript, so developers were using it for such purposes for a long time.
+
+On the other hand, the nullish coalescing operator `??` was added to JavaScript only recently, and the reason for that was that people weren't quite happy with `||`.
-Let's compare it with `||`:
+The important difference between them is that:
+- `||` returns the first *truthy* value.
+- `??` returns the first *defined* value.
+
+In other words, `||` doesn't distinguish between `false`, `0`, an empty string `""` and `null/undefined`. They are all the same -- falsy values. If any of these is the first argument of `||`, then we'll get the second argument as the result.
+
+In practice though, we may want to use default value only when the variable is `null/undefined`. That is, when the value is really unknown/not set.
+
+For example, consider this:
```js run
let height = 0;
@@ -60,19 +97,20 @@ alert(height || 100); // 100
alert(height ?? 100); // 0
```
-Here, `height || 100` treats zero height as unset, same as `null`, `undefined` or any other falsy value. So the result is `100`.
-
-The `height ?? 100` returns `100` only if `height` is exactly `null` or `undefined`. So the `alert` shows the height value `0` "as is".
+- The `height || 100` checks `height` for being a falsy value, and it's `0`, falsy indeed.
+ - so the result of `||` is the second argument, `100`.
+- The `height ?? 100` checks `height` for being `null/undefined`, and it's not,
+ - so the result is `height` "as is", that is `0`.
-Which behavior is better depends on a particular use case. When zero height is a valid value, then `??` is preferrable.
+In practice, the zero height is often a valid value, that shouldn't be replaced with the default. So `??` does just the right thing.
## Precedence
-The precedence of the `??` operator is rather low: `5` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table).
+The precedence of the `??` operator is the same as `||`. They both equal `3` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table).
-So `??` is evaluated after most other operations, but before `=` and `?`.
+That means that, just like `||`, the nullish coalescing operator `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`.
-If we need to choose a value with `??` in a complex expression, then consider adding parentheses:
+So we may need to add parentheses in expressions like this:
```js run
let height = null;
@@ -84,18 +122,19 @@ let area = (height ?? 100) * (width ?? 50);
alert(area); // 5000
```
-Otherwise, if we omit parentheses, `*` has the higher precedence than `??` and would run first.
-
-That would work be the same as:
+Otherwise, if we omit parentheses, then as `*` has the higher precedence than `??`, it would execute first, leading to incorrect results.
```js
-// probably not correct
+// without parentheses
+let area = height ?? 100 * width ?? 50;
+
+// ...works this way (not what we want):
let area = height ?? (100 * width) ?? 50;
```
-There's also a related language-level limitation.
+### Using ?? with && or ||
-**Due to safety reasons, it's forbidden to use `??` together with `&&` and `||` operators.**
+Due to safety reasons, JavaScript forbids using `??` together with `&&` and `||` operators, unless the precedence is explicitly specified with parentheses.
The code below triggers a syntax error:
@@ -103,7 +142,7 @@ The code below triggers a syntax error:
let x = 1 && 2 ?? 3; // Syntax error
```
-The limitation is surely debatable, but it was added to the language specification with the purpose to avoid programming mistakes, as people start to switch to `??` from `||`.
+The limitation is surely debatable, it was added to the language specification with the purpose to avoid programming mistakes, when people start to switch from `||` to `??`.
Use explicit parentheses to work around it:
@@ -117,7 +156,7 @@ alert(x); // 2
## Summary
-- The nullish coalescing operator `??` provides a short way to choose a "defined" value from the list.
+- The nullish coalescing operator `??` provides a short way to choose the first "defined" value from a list.
It's used to assign default values to variables:
@@ -126,5 +165,5 @@ alert(x); // 2
height = height ?? 100;
```
-- The operator `??` has a very low precedence, a bit higher than `?` and `=`.
+- The operator `??` has a very low precedence, only a bit higher than `?` and `=`, so consider adding parentheses when using it in an expression.
- It's forbidden to use it with `||` or `&&` without explicit parentheses.
diff --git a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md
index 2e04a78c4..c7de5f09b 100644
--- a/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md
+++ b/1-js/02-first-steps/13-while-for/6-repeat-until-correct/solution.md
@@ -10,6 +10,6 @@ do {
The loop `do..while` repeats while both checks are truthy:
1. The check for `num <= 100` -- that is, the entered value is still not greater than `100`.
-2. The check `&& num` is false when `num` is `null` or a empty string. Then the `while` loop stops too.
+2. The check `&& num` is false when `num` is `null` or an empty string. Then the `while` loop stops too.
P.S. If `num` is `null` then `num <= 100` is `true`, so without the 2nd check the loop wouldn't stop if the user clicks CANCEL. Both checks are required.
diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md
index b3e3953b8..d1b749888 100644
--- a/1-js/02-first-steps/13-while-for/article.md
+++ b/1-js/02-first-steps/13-while-for/article.md
@@ -6,6 +6,19 @@ For example, outputting goods from a list one after another or just running the
*Loops* are a way to repeat the same code multiple times.
+```smart header="The for..of and for..in loops"
+A small announcement for advanced readers.
+
+This article covers only basic loops: `while`, `do..while` and `for(..;..;..)`.
+
+If you came to this article searching for other types of loops, here are the pointers:
+
+- See [for..in](info:object#forin) to loop over object properties.
+- See [for..of](info:array#loops) and [iterables](info:iterable) for looping over arrays and iterable objects.
+
+Otherwise, please read on.
+```
+
## The "while" loop
The `while` loop has the following syntax:
@@ -106,7 +119,7 @@ Let's examine the `for` statement part-by-part:
| part | | |
|-------|----------|----------------------------------------------------------------------------|
-| begin | `i = 0` | Executes once upon entering the loop. |
+| begin | `let i = 0` | Executes once upon entering the loop. |
| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. |
| body | `alert(i)`| Runs again and again while the condition is truthy. |
| step| `i++` | Executes after the body on each iteration. |
@@ -162,10 +175,8 @@ for (i = 0; i < 3; i++) { // use an existing variable
alert(i); // 3, visible, because declared outside of the loop
```
-
````
-
### Skipping parts
Any part of `for` can be skipped.
@@ -268,7 +279,7 @@ for (let i = 0; i < 10; i++) {
From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`.
-But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability.
+But as a side effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability.
````
````warn header="No `break/continue` to the right side of '?'"
@@ -286,7 +297,6 @@ if (i > 5) {
...and rewrite it using a question mark:
-
```js no-beautify
(i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here
```
@@ -318,9 +328,10 @@ alert('Done!');
We need a way to stop the process if the user cancels the input.
-The ordinary `break` after `input` would only break the inner loop. That's not sufficient--labels, come to the rescue!
+The ordinary `break` after `input` would only break the inner loop. That's not sufficient -- labels, come to the rescue!
A *label* is an identifier with a colon before a loop:
+
```js
labelName: for (...) {
...
@@ -342,6 +353,7 @@ The `break ` statement in the loop below breaks out to the label:
// do something with the value...
}
}
+
alert('Done!');
```
@@ -362,13 +374,26 @@ The `continue` directive can also be used with a label. In this case, code execu
Labels do not allow us to jump into an arbitrary place in the code.
For example, it is impossible to do this:
+
```js
-break label; // doesn't jumps to the label below
+break label; // jump to the label below (doesn't work)
label: for (...)
```
-A call to `break/continue` is only possible from inside a loop and the label must be somewhere above the directive.
+A `break` directive must be inside a code block. Technically, any labelled code block will do, e.g.:
+
+```js
+label: {
+ // ...
+ break label; // works
+ // ...
+}
+```
+
+...Although, 99.9% of the time `break` is used inside loops, as we've seen in the examples above.
+
+A `continue` is only possible from inside a loop.
````
## Summary
diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md
index 314c6cef8..d86babcec 100644
--- a/1-js/02-first-steps/14-switch/article.md
+++ b/1-js/02-first-steps/14-switch/article.md
@@ -47,7 +47,7 @@ switch (a) {
break;
*/!*
case 5:
- alert( 'Too large' );
+ alert( 'Too big' );
break;
default:
alert( "I don't know such values" );
@@ -139,7 +139,7 @@ switch (a) {
Now both `3` and `5` show the same message.
-The ability to "group" cases is a side-effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`.
+The ability to "group" cases is a side effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`.
## Type matters
diff --git a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md
index e41c80418..e3a0df77c 100644
--- a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md
+++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md
@@ -1 +1,3 @@
-No difference.
\ No newline at end of file
+No difference!
+
+In both cases, `return confirm('Did parents allow you?')` executes exactly when the `if` condition is falsy.
\ No newline at end of file
diff --git a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md
index c8ee9618f..e48502642 100644
--- a/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md
+++ b/1-js/02-first-steps/15-function-basics/2-rewrite-function-question-or/solution.md
@@ -14,4 +14,4 @@ function checkAge(age) {
}
```
-Note that the parentheses around `age > 18` are not required here. They exist for better readabilty.
+Note that the parentheses around `age > 18` are not required here. They exist for better readability.
diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md
index b56fbc67d..415fed3e0 100644
--- a/1-js/02-first-steps/15-function-basics/article.md
+++ b/1-js/02-first-steps/15-function-basics/article.md
@@ -20,11 +20,11 @@ function showMessage() {
}
```
-The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above) and finally the code of the function, also named "the function body", between curly braces.
+The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above, we'll see examples later) and finally the code of the function, also named "the function body", between curly braces.
```js
-function name(parameters) {
- ...body...
+function name(parameter1, parameter2, ... parameterN) {
+ // body
}
```
@@ -137,26 +137,23 @@ It's a good practice to minimize the use of global variables. Modern code has fe
## Parameters
-We can pass arbitrary data to functions using parameters (also called *function arguments*) .
+We can pass arbitrary data to functions using parameters.
In the example below, the function has two parameters: `from` and `text`.
```js run
-function showMessage(*!*from, text*/!*) { // arguments: from, text
+function showMessage(*!*from, text*/!*) { // parameters: from, text
alert(from + ': ' + text);
}
-*!*
-showMessage('Ann', 'Hello!'); // Ann: Hello! (*)
-showMessage('Ann', "What's up?"); // Ann: What's up? (**)
-*/!*
+*!*showMessage('Ann', 'Hello!');*/!* // Ann: Hello! (*)
+*!*showMessage('Ann', "What's up?");*/!* // Ann: What's up? (**)
```
When the function is called in lines `(*)` and `(**)`, the given values are copied to local variables `from` and `text`. Then the function uses them.
Here's one more example: we have a variable `from` and pass it to the function. Please note: the function changes `from`, but the change is not seen outside, because a function always gets a copy of the value:
-
```js run
function showMessage(from, text) {
@@ -175,9 +172,21 @@ showMessage(from, "Hello"); // *Ann*: Hello
alert( from ); // Ann
```
+When a value is passed as a function parameter, it's also called an *argument*.
+
+In other words, to put these terms straight:
+
+- A parameter is the variable listed inside the parentheses in the function declaration (it's a declaration time term).
+- An argument is the value that is passed to the function when it is called (it's a call time term).
+
+We declare functions listing their parameters, then call them passing arguments.
+
+In the example above, one might say: "the function `showMessage` is declared with two parameters, then called with two arguments: `from` and `"Hello"`".
+
+
## Default values
-If a parameter is not provided, then its value becomes `undefined`.
+If a function is called, but an argument is not provided, then the corresponding value becomes `undefined`.
For instance, the aforementioned function `showMessage(from, text)` can be called with a single argument:
@@ -185,9 +194,9 @@ For instance, the aforementioned function `showMessage(from, text)` can be calle
showMessage("Ann");
```
-That's not an error. Such a call would output `"Ann: undefined"`. There's no `text`, so it's assumed that `text === undefined`.
+That's not an error. Such a call would output `"*Ann*: undefined"`. As the value for `text` isn't passed, it becomes `undefined`.
-If we want to use a "default" `text` in this case, then we can specify it after `=`:
+We can specify the so-called "default" (to use if omitted) value for a parameter in the function declaration, using `=`:
```js run
function showMessage(from, *!*text = "no text given"*/!*) {
@@ -197,7 +206,13 @@ function showMessage(from, *!*text = "no text given"*/!*) {
showMessage("Ann"); // Ann: no text given
```
-Now if the `text` parameter is not passed, it will get the value `"no text given"`
+Now if the `text` parameter is not passed, it will get the value `"no text given"`.
+
+The default value also jumps in if the parameter exists, but strictly equals `undefined`, like this:
+
+```js
+showMessage("Ann", undefined); // Ann: no text given
+```
Here `"no text given"` is a string, but it can be a more complex expression, which is only evaluated and assigned if the parameter is missing. So, this is also possible:
@@ -211,19 +226,55 @@ function showMessage(from, text = anotherFunction()) {
```smart header="Evaluation of default parameters"
In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter.
-In the example above, `anotherFunction()` is called every time `showMessage()` is called without the `text` parameter.
+In the example above, `anotherFunction()` isn't called at all, if the `text` parameter is provided.
+
+On the other hand, it's independently called every time when `text` is missing.
+```
+
+````smart header="Default parameters in old JavaScript code"
+Several years ago, JavaScript didn't support the syntax for default parameters. So people used other ways to specify them.
+
+Nowadays, we can come across them in old scripts.
+
+For example, an explicit check for `undefined`:
+
+```js
+function showMessage(from, text) {
+*!*
+ if (text === undefined) {
+ text = 'no text given';
+ }
+*/!*
+
+ alert( from + ": " + text );
+}
+```
+
+...Or using the `||` operator:
+
+```js
+function showMessage(from, text) {
+ // If the value of text is falsy, assign the default value
+ // this assumes that text == "" is the same as no text at all
+ text = text || 'no text given';
+ ...
+}
```
+````
+
### Alternative default parameters
-Sometimes it makes sense to set default values for parameters not in the function declaration, but at a later stage, during its execution.
+Sometimes it makes sense to assign default values for parameters at a later stage after the function declaration.
-To check for an omitted parameter, we can compare it with `undefined`:
+We can check if the parameter is passed during the function execution, by comparing it with `undefined`:
```js run
function showMessage(text) {
+ // ...
+
*!*
- if (text === undefined) {
+ if (text === undefined) { // if the parameter is missing
text = 'empty message';
}
*/!*
@@ -237,18 +288,18 @@ showMessage(); // empty message
...Or we could use the `||` operator:
```js
-// if text parameter is omitted or "" is passed, set it to 'empty'
function showMessage(text) {
+ // if text is undefined or otherwise falsy, set it to 'empty'
text = text || 'empty';
...
}
```
-Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when falsy values, such as `0`, are considered regular:
+Modern JavaScript engines support the [nullish coalescing operator](info:nullish-coalescing-operator) `??`, it's better when most falsy values, such as `0`, should be considered "normal":
```js run
-// if there's no "count" parameter, show "unknown"
function showCount(count) {
+ // if count is undefined or null, show "unknown"
alert(count ?? "unknown");
}
@@ -409,9 +460,9 @@ These examples assume common meanings of prefixes. You and your team are free to
```smart header="Ultrashort function names"
Functions that are used *very often* sometimes have ultrashort names.
-For example, the [jQuery](http://jquery.com) framework defines a function with `$`. The [Lodash](http://lodash.com/) library has its core function named `_`.
+For example, the [jQuery](https://jquery.com/) framework defines a function with `$`. The [Lodash](https://lodash.com/) library has its core function named `_`.
-These are exceptions. Generally functions names should be concise and descriptive.
+These are exceptions. Generally function names should be concise and descriptive.
```
## Functions == Comments
@@ -477,7 +528,7 @@ function name(parameters, delimited, by, comma) {
To make the code clean and easy to understand, it's recommended to use mainly local variables and parameters in the function, not outer variables.
-It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side-effect.
+It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side effect.
Function naming:
diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md
index a8ccd6c6c..c6dd891bd 100644
--- a/1-js/02-first-steps/16-function-expressions/article.md
+++ b/1-js/02-first-steps/16-function-expressions/article.md
@@ -12,7 +12,9 @@ function sayHi() {
There is another syntax for creating a function that is called a *Function Expression*.
-It looks like this:
+It allows us to create a new function in the middle of any expression.
+
+For example:
```js
let sayHi = function() {
@@ -20,9 +22,19 @@ let sayHi = function() {
};
```
-Here, the function is created and assigned to the variable explicitly, like any other value. No matter how the function is defined, it's just a value stored in the variable `sayHi`.
+Here we can see a variable `sayHi` getting a value, the new function, created as `function() { alert("Hello"); }`.
+
+As the function creation happens in the context of the assignment expression (to the right side of `=`), this is a *Function Expression*.
+
+Please note, there's no name after the `function` keyword. Omitting a name is allowed for Function Expressions.
+
+Here we immediately assign it to the variable, so the meaning of these code samples is the same: "create a function and put it into the variable `sayHi`".
+
+In more advanced situations, that we'll come across later, a function may be created and immediately called or scheduled for a later execution, not stored anywhere, thus remaining anonymous.
-The meaning of these code samples is the same: "create a function and put it into the variable `sayHi`".
+## Function is a value
+
+Let's reiterate: no matter how the function is created, a function is a value. Both examples above store a function in the `sayHi` variable.
We can even print out that value using `alert`:
@@ -63,14 +75,14 @@ Here's what happens above in detail:
2. Line `(2)` copies it into the variable `func`. Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself.
3. Now the function can be called as both `sayHi()` and `func()`.
-Note that we could also have used a Function Expression to declare `sayHi`, in the first line:
+We could also have used a Function Expression to declare `sayHi`, in the first line:
```js
-let sayHi = function() {
+let sayHi = function() { // (1) create
alert( "Hello" );
};
-let func = sayHi;
+let func = sayHi; //(2)
// ...
```
@@ -78,7 +90,7 @@ Everything would work the same.
````smart header="Why is there a semicolon at the end?"
-You might wonder, why does Function Expression have a semicolon `;` at the end, but Function Declaration does not:
+You might wonder, why do Function Expressions have a semicolon `;` at the end, but Function Declarations do not:
```js
function sayHi() {
@@ -90,9 +102,9 @@ let sayHi = function() {
}*!*;*/!*
```
-The answer is simple:
-- There's no need for `;` at the end of code blocks and syntax structures that use them like `if { ... }`, `for { }`, `function f { }` etc.
-- A Function Expression is used inside the statement: `let sayHi = ...;`, as a value. It's not a code block, but rather an assignment. The semicolon `;` is recommended at the end of statements, no matter what the value is. So the semicolon here is not related to the Function Expression itself, it just terminates the statement.
+The answer is simple: a Function Expression is created here as `function(â¦) {â¦}` inside the assignment statement: `let sayHi = â¦;`. The semicolon `;` is recommended at the end of the statement, it's not a part of the function syntax.
+
+The semicolon would be there for a simpler assignment, such as `let sayHi = 5;`, and it's also there for a function assignment.
````
## Callback functions
@@ -132,13 +144,13 @@ function showCancel() {
ask("Do you agree?", showOk, showCancel);
```
-In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such function usually draws a nice-looking question window. But that's another story.
+In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such functions usually draw a nice-looking question window. But that's another story.
**The arguments `showOk` and `showCancel` of `ask` are called *callback functions* or just *callbacks*.**
The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for "yes" answer, and `showCancel` for "no" answer.
-We can use Function Expressions to write the same function much shorter:
+We can use Function Expressions to write an equivalent, shorter function:
```js run no-beautify
function ask(question, yes, no) {
@@ -174,7 +186,7 @@ Let's formulate the key differences between Function Declarations and Expression
First, the syntax: how to differentiate between them in the code.
-- *Function Declaration:* a function, declared as a separate statement, in the main code flow.
+- *Function Declaration:* a function, declared as a separate statement, in the main code flow:
```js
// Function Declaration
@@ -182,7 +194,7 @@ First, the syntax: how to differentiate between them in the code.
return a + b;
}
```
-- *Function Expression:* a function, created inside an expression or inside another syntax construct. Here, the function is created at the right side of the "assignment expression" `=`:
+- *Function Expression:* a function, created inside an expression or inside another syntax construct. Here, the function is created on the right side of the "assignment expression" `=`:
```js
// Function Expression
@@ -279,7 +291,7 @@ if (age < 18) {
welcome(); // \ (runs)
*/!*
// |
- function welcome() { // |
+ function welcome() { // |
alert("Hello!"); // | Function Declaration is available
} // | everywhere in the block where it's declared
// |
@@ -289,7 +301,7 @@ if (age < 18) {
} else {
- function welcome() {
+ function welcome() {
alert("Greetings!");
}
}
@@ -348,7 +360,7 @@ welcome(); // ok now
```smart header="When to choose Function Declaration versus Function Expression?"
-As a rule of thumb, when we need to declare a function, the first to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared.
+As a rule of thumb, when we need to declare a function, the first thing to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared.
That's also better for readability, as it's easier to look up `function f(â¦) {â¦}` in the code than `let f = function(â¦) {â¦};`. Function Declarations are more "eye-catching".
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 3ea112473..041db18bc 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,7 +1,7 @@
```js run
function ask(question, yes, no) {
- if (confirm(question)) yes()
+ if (confirm(question)) yes();
else no();
}
diff --git a/1-js/02-first-steps/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 2f44db27e..e18c08a83 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
@@ -5,7 +5,7 @@ Replace Function Expressions with arrow functions in the code below:
```js run
function ask(question, yes, no) {
- if (confirm(question)) yes()
+ if (confirm(question)) yes();
else no();
}
diff --git a/1-js/02-first-steps/17-arrow-functions-basics/article.md b/1-js/02-first-steps/17-arrow-functions-basics/article.md
index e0fb5bda5..50c0d475d 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
@@ -5,15 +5,15 @@ There's another very simple and concise syntax for creating functions, that's of
It's called "arrow functions", because it looks like this:
```js
-let func = (arg1, arg2, ...argN) => expression
+let func = (arg1, arg2, ..., argN) => expression;
```
-...This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result.
+This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result.
In other words, it's the shorter version of:
```js
-let func = function(arg1, arg2, ...argN) {
+let func = function(arg1, arg2, ..., argN) {
return expression;
};
```
@@ -33,7 +33,7 @@ let sum = function(a, b) {
alert( sum(1, 2) ); // 3
```
-As you can, see `(a, b) => a + b` means a function that accepts two arguments named `a` and `b`. Upon the execution, it evaluates the expression `a + b` and returns the result.
+As you can see, `(a, b) => a + b` means a function that accepts two arguments named `a` and `b`. Upon the execution, it evaluates the expression `a + b` and returns the result.
- If we have only one argument, then parentheses around parameters can be omitted, making that even shorter.
@@ -48,7 +48,7 @@ As you can, see `(a, b) => a + b` means a function that accepts two arguments na
alert( double(3) ); // 6
```
-- If there are no arguments, parentheses will be empty (but they should be present):
+- If there are no arguments, parentheses are empty, but they must be present:
```js run
let sayHi = () => alert("Hello!");
@@ -64,7 +64,7 @@ For instance, to dynamically create a function:
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
- () => alert('Hello') :
+ () => alert('Hello!') :
() => alert("Greetings!");
welcome();
@@ -76,9 +76,9 @@ They are very convenient for simple one-line actions, when we're just too lazy t
## Multiline arrow functions
-The examples above took arguments from the left of `=>` and evaluated the right-side expression with them.
+The arrow functions that we've seen so far were very simple. They took arguments from the left of `=>`, evaluated and returned the right-side expression with them.
-Sometimes we need something a little bit more complex, like multiple expressions or statements. It is also possible, but we should enclose them in curly braces. Then use a normal `return` within them.
+Sometimes we need a more complex function, with multiple expressions and statements. In that case, we can enclose them in curly braces. The major difference is that curly braces require a `return` within them to return a value (just like a regular function does).
Like this:
@@ -86,7 +86,7 @@ Like this:
let sum = (a, b) => { // the curly brace opens a multiline function
let result = a + b;
*!*
- return result; // if we use curly braces, then we need an explicit "return"
+ return result; // if we use curly braces, then we need an explicit "return"
*/!*
};
@@ -105,7 +105,7 @@ For now, we can already use arrow functions for one-line actions and callbacks.
## Summary
-Arrow functions are handy for one-liners. They come in two flavors:
+Arrow functions are handy for simple actions, especially for one-liners. They come in two flavors:
-1. Without curly braces: `(...args) => expression` -- the right side is an expression: the function evaluates it and returns the result.
+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.
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 91be0aa45..e7ddacac4 100644
--- a/1-js/02-first-steps/18-javascript-specials/article.md
+++ b/1-js/02-first-steps/18-javascript-specials/article.md
@@ -55,7 +55,7 @@ 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.
-Without `"use strict"`, everything still works, but some features behave in the old-fashion, "compatible" way. We'd generally prefer the modern behavior.
+Without `"use strict"`, everything still works, but some features behave in the old-fashioned, "compatible" way. We'd generally prefer the modern behavior.
Some modern features of the language (like classes that we'll study in the future) enable strict mode implicitly.
@@ -103,13 +103,13 @@ More in: and .
We're using a browser as a working environment, so basic UI functions will be:
-[`prompt(question, [default])`](mdn:api/Window/prompt)
+[`prompt(question, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt)
: Ask a `question`, and return either what the visitor entered or `null` if they clicked "cancel".
-[`confirm(question)`](mdn:api/Window/confirm)
+[`confirm(question)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)
: Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`.
-[`alert(message)`](mdn:api/Window/alert)
+[`alert(message)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert)
: Output a `message`.
All these functions are *modal*, they pause the code execution and prevent the visitor from interacting with the page until they answer.
@@ -144,7 +144,7 @@ Assignments
: There is a simple assignment: `a = b` and combined ones like `a *= 2`.
Bitwise
-: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Reference/Operators/Bitwise_Operators) when they are needed.
+: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed.
Conditional
: The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`.
@@ -256,7 +256,7 @@ We covered three ways to create a function in JavaScript:
3. Arrow functions:
```js
- // expression at the right side
+ // expression on the right side
let sum = (a, b) => a + b;
// or multi-line syntax with { ... }, need return here:
@@ -273,7 +273,7 @@ We covered three ways to create a function in JavaScript:
```
-- Functions may have local variables: those declared inside its body. Such variables are only visible inside the function.
+- Functions may have local variables: those declared inside its body or its parameter list. Such variables are only visible inside the function.
- Parameters can have default values: `function sum(a = 1, b = 2) {...}`.
- Functions always return something. If there's no `return` statement, then the result is `undefined`.
diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md
index ee7dea4c4..4f50fb428 100644
--- a/1-js/03-code-quality/01-debugging-chrome/article.md
+++ b/1-js/03-code-quality/01-debugging-chrome/article.md
@@ -1,4 +1,4 @@
-# Debugging in Chrome
+# Debugging in the browser
Before writing more complex code, let's talk about debugging.
@@ -38,7 +38,7 @@ If we press `key:Esc`, then a console opens below. We can type commands there an
After a statement is executed, its result is shown below.
-For example, here `1+2` results in `3`, and `hello("debugger")` returns nothing, so the result is `undefined`:
+For example, here `1+2` results in `3`, while the function call `hello("debugger")` returns nothing, so the result is `undefined`:

@@ -63,12 +63,12 @@ We can always find a list of breakpoints in the right panel. That's useful when
- ...And so on.
```smart header="Conditional breakpoints"
-*Right click* on the line number allows to create a *conditional* breakpoint. It only triggers when the given expression is truthy.
+*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.
That's handy when we need to stop only for a certain variable value or for certain function parameters.
```
-## Debugger command
+## The command "debugger"
We can also pause the code by using the `debugger` command in it, like this:
@@ -84,8 +84,7 @@ function hello(name) {
}
```
-That's very convenient when we are in a code editor and don't want to switch to the browser and look up the script in developer tools to set the breakpoint.
-
+Such command works only when the development tools are open, otherwise the browser ignores it.
## Pause and look around
@@ -99,7 +98,7 @@ Please open the informational dropdowns to the right (labeled with arrows). They
1. **`Watch` -- shows current values for any expressions.**
- You can click the plus `+` and input an expression. The debugger will show its value at any moment, automatically recalculating it in the process of execution.
+ You can click the plus `+` and input an expression. The debugger will show its value, automatically recalculating it in the process of execution.
2. **`Call Stack` -- shows the nested calls chain.**
@@ -135,11 +134,11 @@ There are buttons for it at the top of the right panel. Let's engage them.
Clicking this again and again will step through all script statements one by one.
-- "Step over": run the next command, but *don't go into a function*, hotkey `key:F10`.
-: Similar to the previous the "Step" command, but behaves differently if the next statement is a function call. That is: not a built-in, like `alert`, but a function of our own.
+: Similar to the previous "Step" command, but behaves differently if the next statement is a function call (not a built-in, like `alert`, but a function of our own).
- The "Step" command goes into it and pauses the execution at its first line, while "Step over" executes the nested function call invisibly, skipping the function internals.
+ If we compare them, the "Step" command goes into a nested function call and pauses the execution at its first line, while "Step over" executes the nested function call invisibly to us, skipping the function internals.
- The execution is then paused immediately after that function.
+ The execution is then paused immediately after that function call.
That's good if we're not interested to see what happens inside the function call.
@@ -155,7 +154,7 @@ There are buttons for it at the top of the right panel. Let's engage them.
: That button does not move the execution. Just a mass on/off for breakpoints.
-- enable/disable automatic pause in case of an error.
-: When enabled, and the developer tools is open, a script error automatically pauses the execution. Then we can analyze variables to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment.
+: 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.
```smart header="Continue to here"
Right click on a line of code opens the context menu with a great option called "Continue to here".
@@ -187,7 +186,7 @@ As we can see, there are three main ways to pause a script:
2. The `debugger` statements.
3. An error (if dev tools are open and the button is "on").
-When paused, we can debug - examine variables and trace the code to see where the execution goes wrong.
+When paused, we can debug: examine variables and trace the code to see where the execution goes wrong.
There are many more options in developer tools than covered here. The full manual is at .
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..31b3bb7ae 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
@@ -3,7 +3,7 @@ You could note the following:
```js no-beautify
function pow(x,n) // <- no space between arguments
-{ // <- figure bracket on a separate line
+{ // <- curly brace on a separate line
let result=1; // <- no spaces before or after =
for(let i=0;i for more details about installation.
+It is also possible to download style rule sets from the web and extend them instead. See for more details about installation.
Also certain IDEs have built-in linting, which is convenient but not as customizable as ESLint.
diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md
index 0d11c6c52..af3a06c80 100644
--- a/1-js/03-code-quality/03-comments/article.md
+++ b/1-js/03-code-quality/03-comments/article.md
@@ -143,7 +143,7 @@ Such comments allow us to understand the purpose of the function and use it the
By the way, many editors like [WebStorm](https://www.jetbrains.com/webstorm/) can understand them as well and use them to provide autocomplete and some automatic code-checking.
-Also, there are tools like [JSDoc 3](https://github.com/jsdoc3/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at .
+Also, there are tools like [JSDoc 3](https://github.com/jsdoc/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at .
Why is the task solved this way?
: What's written is important. But what's *not* written may be even more important to understand what's going on. Why is the task solved exactly this way? The code gives no answer.
diff --git a/1-js/03-code-quality/04-ninja-code/article.md b/1-js/03-code-quality/04-ninja-code/article.md
index 982cc0214..96fdf4143 100644
--- a/1-js/03-code-quality/04-ninja-code/article.md
+++ b/1-js/03-code-quality/04-ninja-code/article.md
@@ -1,7 +1,7 @@
# Ninja code
-```quote author="Confucius"
+```quote author="Confucius (Analects)"
Learning without thought is labor lost; thought without learning is perilous.
```
@@ -104,8 +104,8 @@ A quick read of such code becomes impossible. And when there's a typo... Ummm...
## Smart synonyms
-```quote author="Confucius"
-The hardest thing of all is to find a black cat in a dark room, especially if there is no cat.
+```quote author="Laozi (Tao Te Ching)"
+The Tao that can be told is not the eternal Tao. The name that can be named is not the eternal name.
```
Using *similar* names for *same* things makes life more interesting and shows your creativity to the public.
diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md
index 68ffcae4d..4c2b1aa5e 100644
--- a/1-js/03-code-quality/05-testing-mocha/article.md
+++ b/1-js/03-code-quality/05-testing-mocha/article.md
@@ -2,7 +2,7 @@
Automated testing will be used in further tasks, and it's also widely used in real projects.
-## Why we need tests?
+## Why do we need tests?
When we write a function, we can usually imagine what it should do: which parameters give which results.
@@ -51,7 +51,7 @@ describe("pow", function() {
A spec has three main building blocks that you can see above:
`describe("title", function() { ... })`
-: What functionality we're describing. In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks.
+: What functionality we're describing? In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks.
`it("use case description", function() { ... })`
: In the title of `it` we *in a human-readable way* describe the particular use case, and the second argument is a function that tests it.
@@ -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 75db49d2f..5ca123908 100644
--- a/1-js/03-code-quality/06-polyfills/article.md
+++ b/1-js/03-code-quality/06-polyfills/article.md
@@ -1,54 +1,89 @@
-# Polyfills
+# Polyfills and transpilers
-The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](http://www.ecma-international.org/publications/standards/Ecma-262.htm).
+The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/).
Teams behind JavaScript engines have their own ideas about what to implement first. They may decide to implement proposals that are in draft and postpone things that are already in the spec, because they are less interesting or just harder to do.
-So it's quite common for an engine to implement only the part of the standard.
+So it's quite common for an engine to implement only part of the standard.
-A good page to see the current state of support for language features is (it's big, we have a lot to study yet).
+A good page to see the current state of support for language features is (it's big, we have a lot to study yet).
-## Babel
+As programmers, we'd like to use most recent features. The more good stuff - the better!
-When we use modern features of the language, some engines may fail to support such code. Just as said, not all features are implemented everywhere.
+On the other hand, how to make our modern code work on older engines that don't understand recent features yet?
-Here Babel comes to the rescue.
+There are two tools for that:
-[Babel](https://babeljs.io) is a [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler). It rewrites modern JavaScript code into the previous standard.
+1. Transpilers.
+2. Polyfills.
-Actually, there are two parts in Babel:
+Here, in this chapter, our purpose is to get the gist of how they work, and their place in web development.
-1. First, the transpiler program, which rewrites the code. The developer runs it on their own computer. It rewrites the code into the older standard. And then the code is delivered to the website for users. Modern project build systems like [webpack](http://webpack.github.io/) provide means to run transpiler automatically on every code change, so that it's very easy to integrate into development process.
+## Transpilers
-2. Second, the polyfill.
+A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that translates source code to another source code. It can parse ("read and understand") modern code and rewrite it using older syntax constructs, so that it'll also work in outdated engines.
- New language features may include new built-in functions and syntax constructs.
- The transpiler rewrites the code, transforming syntax constructs into older ones. But as for new built-in functions, we need to implement them. JavaScript is a highly dynamic language, scripts may add/modify any functions, so that they behave according to the modern standard.
+E.g. JavaScript before year 2020 didn't have the "nullish coalescing operator" `??`. So, if a visitor uses an outdated browser, it may fail to understand the code like `height = height ?? 100`.
- A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations.
+A transpiler would analyze our code and rewrite `height ?? 100` into `(height !== undefined && height !== null) ? height : 100`.
- Two interesting polyfills are:
- - [core js](https://github.com/zloirock/core-js) that supports a lot, allows to include only needed features.
- - [polyfill.io](http://polyfill.io) service that provides a script with polyfills, depending on the features and user's browser.
+```js
+// before running the transpiler
+height = height ?? 100;
-So, if we're going to use modern language features, a transpiler and a polyfill are necessary.
+// after running the transpiler
+height = (height !== undefined && height !== null) ? height : 100;
+```
-## Examples in the tutorial
+Now the rewritten code is suitable for older JavaScript engines.
+Usually, a developer runs the transpiler on their own computer, and then deploys the transpiled code to the server.
-````online
-Most examples are runnable at-place, like this:
+Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there.
-```js run
-alert('Press the "Play" button in the upper-right corner to run');
-```
+Modern project build systems, such as [webpack](https://webpack.js.org/), provide a means to run a transpiler automatically on every code change, so it's very easy to integrate into the development process.
+
+## Polyfills
+
+New language features may include not only syntax constructs and operators, but also built-in functions.
+
+For example, `Math.trunc(n)` is a function that "cuts off" the decimal part of a number, e.g `Math.trunc(1.23)` returns `1`.
+
+In some (very outdated) JavaScript engines, there's no `Math.trunc`, so such code will fail.
+
+As we're talking about new functions, not syntax changes, there's no need to transpile anything here. We just need to declare the missing function.
+
+A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations.
-Examples that use modern JS will work only if your browser supports it.
-````
+For this particular case, the polyfill for `Math.trunc` is a script that implements it, like this:
-```offline
-As you're reading the offline version, in PDF examples are not runnable. In EPUB some of them can run.
+```js
+if (!Math.trunc) { // if no such function
+ // implement it
+ Math.trunc = function(number) {
+ // Math.ceil and Math.floor exist even in ancient JavaScript engines
+ // they are covered later in the tutorial
+ return number < 0 ? Math.ceil(number) : Math.floor(number);
+ };
+}
```
-Google Chrome is usually the most up-to-date with language features, good to run bleeding-edge demos without any transpilers, but other modern browsers also work fine.
+JavaScript is a highly dynamic language. Scripts may add/modify any function, even built-in ones.
+
+One interesting polyfill library is [core-js](https://github.com/zloirock/core-js), which supports a wide range of features and allows you to include only the ones you need.
+
+## Summary
+
+In this chapter we'd like to motivate you to study modern and even "bleeding-edge" language features, even if they aren't yet well-supported by JavaScript engines.
+
+Just don't forget to use a transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). They'll ensure that the code works.
+
+For example, later when you're familiar with JavaScript, you can setup a code build system based on [webpack](https://webpack.js.org/) with the [babel-loader](https://github.com/babel/babel-loader) plugin.
+
+Good resources that show the current state of support for various features:
+- - for pure JavaScript.
+- - for browser-related functions.
+
+P.S. Google Chrome is usually the most up-to-date with language features, try it if a tutorial demo fails. Most tutorial demos work with any modern browser though.
+
diff --git a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md
index 33eb89220..6878ca088 100644
--- a/1-js/04-object-basics/01-object/8-multiply-numeric/task.md
+++ b/1-js/04-object-basics/01-object/8-multiply-numeric/task.md
@@ -2,9 +2,9 @@ importance: 3
---
-# Multiply numeric properties by 2
+# Multiply numeric property values by 2
-Create a function `multiplyNumeric(obj)` that multiplies all numeric properties of `obj` by `2`.
+Create a function `multiplyNumeric(obj)` that multiplies all numeric property values of `obj` by `2`.
For instance:
diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md
index 513f2f3e3..dc3ec1a64 100644
--- a/1-js/04-object-basics/01-object/article.md
+++ b/1-js/04-object-basics/01-object/article.md
@@ -5,7 +5,7 @@ As we know from the chapter , there are eight data types in JavaScri
In contrast, objects are used to store keyed collections of various data and more complex entities. In JavaScript, objects penetrate almost every aspect of the language. So we must understand them first before going in-depth anywhere else.
-An object can be created with figure brackets `{â¦}` with an optional list of *properties*. A property is a "key: value" pair, where `key` is a string (also called a "property name"), and `value` can be anything.
+An object can be created with curly braces `{â¦}` with an optional list of *properties*. A property is a "key: value" pair, where `key` is a string (also called a "property name"), and `value` can be anything.
We can imagine an object as a cabinet with signed files. Every piece of data is stored in its file by the key. It's easy to find a file by its name or add/remove a file.
@@ -20,7 +20,7 @@ let user = {}; // "object literal" syntax

-Usually, the figure brackets `{...}` are used. That declaration is called an *object literal*.
+Usually, the curly braces `{...}` are used. That declaration is called an *object literal*.
## Literals and properties
@@ -44,7 +44,7 @@ The resulting `user` object can be imagined as a cabinet with two signed files l

-We can add, remove and read files from it any time.
+We can add, remove and read files from it at any time.
Property values are accessible using the dot notation:
@@ -62,7 +62,7 @@ user.isAdmin = true;

-To remove a property, we can use `delete` operator:
+To remove a property, we can use the `delete` operator:
```js
delete user.age;
@@ -92,30 +92,6 @@ let user = {
```
That is called a "trailing" or "hanging" comma. Makes it easier to add/remove/move around properties, because all lines become alike.
-````smart header="Object with const can be changed"
-Please note: 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 no. The `const` fixes the value of `user`, but not its contents.
-
-The `const` would give an error only if we try to set `user=...` as a whole.
-
-There's another way to make constant object properties, we'll cover it later in the chapter .
-````
-
## Square brackets
For multiword properties, the dot access doesn't work:
@@ -225,13 +201,13 @@ let bag = {
};
```
-Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are also more cumbersome to write.
+Square brackets are much more powerful than dot notation. They allow any property names and variables. But they are also more cumbersome to write.
So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.
## Property value shorthand
-In real code we often use existing variables as values for property names.
+In real code, we often use existing variables as values for property names.
For instance:
@@ -276,7 +252,7 @@ let user = {
## Property names limitations
-As we already know, a variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc.
+As we already know, a variable cannot have a name equal to one of the language-reserved words like "for", "let", "return" etc.
But for an object property, there's no such restriction:
@@ -349,7 +325,7 @@ alert( "blabla" in user ); // false, user.blabla doesn't exist
Please note that on the left side of `in` there must be a *property name*. That's usually a quoted string.
-If we omit quotes, that means a variable, it should contain the actual name to be tested. For instance:
+If we omit quotes, that means a variable should contain the actual name to be tested. For instance:
```js run
let user = { age: 30 };
@@ -379,7 +355,7 @@ In the code above, the property `obj.test` technically exists. So the `in` opera
Situations like this happen very rarely, because `undefined` should not be explicitly assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code.
-## The "for..in" loop
+## The "for..in" loop [#forin]
To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before.
@@ -436,7 +412,7 @@ for (let code in codes) {
*/!*
```
-The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want `49` to be the first.
+The object may be used to suggest a list of options to the user. If we're making a site mainly for a German audience then we probably want `49` to be the first.
But if we run the code, we see a totally different picture:
@@ -448,9 +424,10 @@ The phone codes go in the ascending sorted order, because they are integers. So
````smart header="Integer properties? What's that?"
The "integer property" term here means a string that can be converted to-and-from an integer without a change.
-So, "49" is an integer property name, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not:
+So, `"49"` is an integer property name, because when it's transformed to an integer number and back, it's still the same. But `"+49"` and `"1.2"` are not:
```js run
+// Number(...) explicitly converts to a number
// Math.trunc is a built-in function that removes the decimal part
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" â not integer property
@@ -505,7 +482,7 @@ They store properties (key-value pairs), where:
To access a property, we can use:
- The dot notation: `obj.property`.
-- Square brackets notation `obj["property"]`. Square brackets allow to take the key from a variable, like `obj[varWithKey]`.
+- Square brackets notation `obj["property"]`. Square brackets allow taking the key from a variable, like `obj[varWithKey]`.
Additional operators:
- To delete a property: `delete obj.prop`.
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 c88872232..e80f748ab 100644
--- a/1-js/04-object-basics/02-object-copy/article.md
+++ b/1-js/04-object-basics/02-object-copy/article.md
@@ -1,25 +1,29 @@
-# Object copying, references
+# Object references and copying
-One of the fundamental differences of objects vs primitives is that they are stored and copied "by reference".
+One of the fundamental differences of objects versus primitives is that objects are stored and copied "by reference", whereas primitive values: strings, numbers, booleans, etc -- are always copied "as a whole value".
-Primitive values: strings, numbers, booleans -- are assigned/copied "as a whole value".
+That's easy to understand if we look a bit under the hood of what happens when we copy a value.
-For instance:
+Let's start with a primitive, such as a string.
+
+Here we put a copy of `message` into `phrase`:
```js
let message = "Hello!";
let phrase = message;
```
-As a result we have two independent variables, each one is storing the string `"Hello!"`.
+As a result we have two independent variables, each one storing the string `"Hello!"`.

+Quite an obvious result, right?
+
Objects are not like that.
-**A variable stores not the object itself, but its "address in memory", in other words "a reference" to it.**
+**A variable assigned to an object stores not the object itself, but its "address in memory" -- in other words "a reference" to it.**
-Here's the picture for the object:
+Let's look at an example of such a variable:
```js
let user = {
@@ -27,11 +31,19 @@ let user = {
};
```
+And here's how it's actually stored in memory:
+

-Here, the object is stored somewhere in memory. And the variable `user` has a "reference" to it.
+The object is stored somewhere in memory (at the right of the picture), while the `user` variable (at the left) has a "reference" to it.
+
+We may think of an object variable, such as `user`, like a sheet of paper with the address of the object on it.
-**When an object variable is copied -- the reference is copied, the object is not duplicated.**
+When we perform actions with the object, e.g. take a property `user.name`, the JavaScript engine looks at what's at that address and performs the operation on the actual object.
+
+Now here's why it's important.
+
+**When an object variable is copied, the reference is copied, but the object itself is not duplicated.**
For instance:
@@ -41,11 +53,13 @@ let user = { name: "John" };
let admin = user; // copy the reference
```
-Now we have two variables, each one with the reference to the same object:
+Now we have two variables, each storing a reference to the same object:

-We can use any variable to access the object and modify its contents:
+As you can see, there's still one object, but now with two variables that reference it.
+
+We can use either variable to access the object and modify its contents:
```js run
let user = { name: 'John' };
@@ -59,15 +73,13 @@ admin.name = 'Pete'; // changed by the "admin" reference
alert(*!*user.name*/!*); // 'Pete', changes are seen from the "user" reference
```
-The example above demonstrates that there is only one object. As if we had a cabinet with two keys and used one of them (`admin`) to get into it. Then, if we later use another key (`user`) we can see changes.
+It's as if we had a cabinet with two keys and used one of them (`admin`) to get into it and make changes. Then, if we later use another key (`user`), we are still opening the same cabinet and can access the changed contents.
## Comparison by reference
-The equality `==` and strict equality `===` operators for objects work exactly the same.
-
-**Two objects are equal only if they are the same object.**
+Two objects are equal only if they are the same object.
-Here two variables reference the same object, thus they are equal:
+For instance, here `a` and `b` reference the same object, thus they are equal:
```js run
let a = {};
@@ -77,7 +89,7 @@ alert( a == b ); // true, both variables reference the same object
alert( a === b ); // true
```
-And here two independent objects are not equal, even though both are empty:
+And here two independent objects are not equal, even though they look alike (both are empty):
```js run
let a = {};
@@ -86,17 +98,39 @@ let b = {}; // two independent objects
alert( a == b ); // false
```
-For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons occur very rarely, usually as a result of a coding mistake.
+For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake.
-## Cloning and merging, Object.assign
+````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.
-So, copying an object variable creates one more reference to the same object.
+For instance:
+
+```js run
+const user = {
+ name: "John"
+};
+
+*!*
+user.name = "Pete"; // (*)
+*/!*
+
+alert(user.name); // Pete
+```
-But what if we need to duplicate an object? Create an independent copy, a clone?
+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.
-That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. Actually, that's rarely needed. Copying by reference is good most of the time.
+In other words, the `const user` gives an error only if we try to set `user=...` as a whole.
-But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.
+That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter .
+````
+
+## Cloning and merging, Object.assign [#cloning-and-merging-object-assign]
+
+So, copying an object variable creates one more reference to the same object.
+
+But what if we need to duplicate an object?
+
+We can create a new object and replicate the structure of the existing one, by iterating over its properties and copying them on the primitive level.
Like this:
@@ -121,21 +155,22 @@ clone.name = "Pete"; // changed the data in it
alert( user.name ); // still John in the original object
```
-Also we can use the method [Object.assign](mdn:js/Object/assign) for that.
+We can also use the method [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign).
The syntax is:
```js
-Object.assign(dest, [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 };
@@ -147,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:
@@ -159,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
@@ -170,13 +208,18 @@ 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.
## Nested cloning
-Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them?
+Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects.
Like this:
```js run
@@ -191,9 +234,7 @@ let user = {
alert( user.sizes.height ); // 182
```
-Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes:
-
-Like this:
+Now it's not enough to copy `clone.sizes = user.sizes`, because `user.sizes` is an object, and will be copied by reference, so `clone` and `user` will share the same sizes:
```js run
let user = {
@@ -209,20 +250,76 @@ let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); // true, same object
// user and clone share sizes
-user.sizes.width++; // change a property from one place
-alert(clone.sizes.width); // 51, see the result from the other one
+user.sizes.width = 60; // change a property from one place
+alert(clone.sizes.width); // 60, get the result from the other one
+```
+
+To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning.
+
+
+### structuredClone
+
+The call `structuredClone(object)` clones the `object` with all nested properties.
+
+Here's how we can use it in our example:
+
+```js run
+let user = {
+ name: "John",
+ sizes: {
+ height: 182,
+ width: 50
+ }
+};
+
+*!*
+let clone = structuredClone(user);
+*/!*
+
+alert( user.sizes === clone.sizes ); // false, different objects
+
+// user and clone are totally unrelated now
+user.sizes.width = 60; // change a property from one place
+alert(clone.sizes.width); // 50, not related
```
-To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning".
+The `structuredClone` method can clone most data types, such as objects, arrays, primitive values.
+
+It also supports circular references, when an object property references the object itself (directly or via a chain or references).
+
+For instance:
+
+```js run
+let user = {};
+// let's create a circular reference:
+// user.me references the user itself
+user.me = user;
+
+let clone = structuredClone(user);
+alert(clone.me === clone); // true
+```
+
+As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well.
+
+Although, there are cases when `structuredClone` fails.
+
+For instance, when an object has a function property:
+
+```js run
+// error
+structuredClone({
+ f: function() {}
+});
+```
-There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](https://html.spec.whatwg.org/multipage/structured-data.html#safe-passing-of-structured-data).
+Function properties aren't supported.
-We can use recursion to implement it. Or, not to reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
+To handle such complex cases we may need to use a combination of cloning methods, write custom code or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
## Summary
-Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object.
+Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object itself.
All operations via copied references (like adding/removing properties) are performed on the same single object.
-To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
+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 e20e5a5d8..1b576d629 100644
--- a/1-js/04-object-basics/03-garbage-collection/article.md
+++ b/1-js/04-object-basics/03-garbage-collection/article.md
@@ -14,8 +14,8 @@ Simply put, "reachable" values are those that are accessible or usable somehow.
For instance:
- - Local variables and parameters of the current function.
- - Variables and parameters for other functions on the current chain of nested calls.
+ - The currently executing function, its local variables and parameters.
+ - Other functions on the current chain of nested calls, their local variables and parameters.
- Global variables.
- (there are some other, internal ones as well)
@@ -23,7 +23,7 @@ Simply put, "reachable" values are those that are accessible or usable somehow.
2. Any other value is considered reachable if it's reachable from a root by a reference or by a chain of references.
- For instance, if there's an object in a global variable, and that object has a property referencing another object, that object is considered reachable. And those that it references are also reachable. Detailed examples to follow.
+ For instance, if there's an object in a global variable, and that object has a property referencing another object, *that* object is considered reachable. And those that it references are also reachable. Detailed examples to follow.
There's a background process in the JavaScript engine that is called [garbage collector](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)). It monitors all objects and removes those that have become unreachable.
@@ -74,7 +74,7 @@ Now if we do the same:
user = null;
```
-...Then the object is still reachable via `admin` global variable, so it's in memory. If we overwrite `admin` too, then it can be removed.
+...Then the object is still reachable via `admin` global variable, so it must stay in memory. If we overwrite `admin` too, then it can be removed.
## Interlinked objects
@@ -169,11 +169,11 @@ The first step marks the roots:

-Then their references are marked:
+Then we follow their references and mark referenced objects:

-...And their references, while possible:
+...And continue to follow further references, while possible:

@@ -183,12 +183,12 @@ Now the objects that could not be visited in the process are considered unreacha
We can also imagine the process as spilling a huge bucket of paint from the roots, that flows through all references and marks all reachable objects. The unmarked ones are then removed.
-That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not affect the execution.
+That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not introduce any delays into the code execution.
Some of the optimizations:
-- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, they can be cleaned up aggressively. Those that survive for long enough, become "old" and are examined less often.
-- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine tries to split the garbage collection into pieces. Then the pieces are executed one by one, separately. That requires some extra bookkeeping between them to track changes, but we have many tiny delays instead of a big one.
+- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". In typical code, many objects have a short life span: they appear, do their job and die fast, so it makes sense to track new objects and clear the memory from them if that's the case. Those that survive for long enough, become "old" and are examined less often.
+- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine splits the whole set of existing objects into multiple parts. And then clear these parts one after another. There are many small garbage collections instead of a total one. That requires some extra bookkeeping between them to track changes, but we get many tiny delays instead of a big one.
- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution.
There exist other optimizations and flavours of garbage collection algorithms. As much as I'd like to describe them here, I have to hold off, because different engines implement different tweaks and techniques. And, what's even more important, things change as engines develop, so studying deeper "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below.
@@ -199,14 +199,14 @@ The main things to know:
- Garbage collection is performed automatically. We cannot force or prevent it.
- Objects are retained in memory while they are reachable.
-- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.
+- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole, as we've seen in the example above.
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, the more detailed information about V8 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).
-[V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn the 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 V8 engineers. I'm saying: "V8", because it is best covered with articles in 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.
+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/4-object-property-this/solution.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md
index c1aaf4f97..f33c9310e 100644
--- a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md
+++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md
@@ -7,7 +7,7 @@ function makeUser() {
name: "John",
ref: this
};
-};
+}
let user = makeUser();
@@ -45,7 +45,7 @@ function makeUser() {
}
*/!*
};
-};
+}
let user = makeUser();
diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md
index 4784b082c..c6f8f9658 100644
--- a/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md
+++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/task.md
@@ -14,7 +14,7 @@ function makeUser() {
name: "John",
ref: this
};
-};
+}
let user = makeUser();
diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js
index 1f71eda4c..4decb76dc 100644
--- a/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js
+++ b/1-js/04-object-basics/04-object-methods/7-calculator/_js.view/test.js
@@ -15,6 +15,11 @@ describe("calculator", function() {
afterEach(function() {
prompt.restore();
});
+
+ it('the read get two values and saves them as object properties', function () {
+ assert.equal(calculator.a, 2);
+ assert.equal(calculator.b, 3);
+ });
it("the sum is 5", function() {
assert.equal(calculator.sum(), 5);
diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/task.md b/1-js/04-object-basics/04-object-methods/7-calculator/task.md
index aa22608ec..82d0da030 100644
--- a/1-js/04-object-basics/04-object-methods/7-calculator/task.md
+++ b/1-js/04-object-basics/04-object-methods/7-calculator/task.md
@@ -6,7 +6,7 @@ importance: 5
Create an object `calculator` with three methods:
-- `read()` prompts for two values and saves them as object properties.
+- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively.
- `sum()` returns the sum of saved values.
- `mul()` multiplies saved values and returns the result.
@@ -21,4 +21,3 @@ alert( calculator.mul() );
```
[demo]
-
diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js
index e98fe6410..a35c009cc 100644
--- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js
+++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js
@@ -11,5 +11,6 @@ let ladder = {
},
showStep: function() {
alert(this.step);
+ return this;
}
};
\ No newline at end of file
diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js
index a2b17fcc4..b4f2459b7 100644
--- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js
+++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js
@@ -32,6 +32,14 @@ describe('Ladder', function() {
it('down().up().up().up() ', function() {
assert.equal(ladder.down().up().up().up().step, 2);
});
+
+ it('showStep() should return this', function() {
+ assert.equal(ladder.showStep(), ladder);
+ });
+
+ it('up().up().down().showStep().down().showStep()', function () {
+ assert.equal(ladder.up().up().down().showStep().down().showStep().step, 0)
+ });
after(function() {
ladder.step = 0;
diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md
index 2b47873fc..f215461dd 100644
--- a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md
+++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md
@@ -21,9 +21,9 @@ let ladder = {
return this;
*/!*
}
-}
+};
-ladder.up().up().down().up().down().showStep(); // 1
+ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0
```
We also can write a single call per line. For long chains it's more readable:
@@ -33,7 +33,7 @@ ladder
.up()
.up()
.down()
- .up()
+ .showStep() // 1
.down()
- .showStep(); // 1
+ .showStep(); // 0
```
diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md
index eca9f4e92..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,19 +21,21 @@ 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();
ladder.up();
ladder.down();
ladder.showStep(); // 1
+ladder.down();
+ladder.showStep(); // 0
```
-Modify the code of `up`, `down` and `showStep` to make the calls chainable, like this:
+Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this:
```js
-ladder.up().up().down().showStep(); // 1
+ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0
```
-Such approach is widely used across JavaScript libraries.
+Such an approach is widely used across JavaScript libraries.
diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md
index 75bd1856a..cea2b6a70 100644
--- a/1-js/04-object-basics/04-object-methods/article.md
+++ b/1-js/04-object-basics/04-object-methods/article.md
@@ -32,11 +32,11 @@ user.sayHi = function() {
user.sayHi(); // Hello!
```
-Here we've just used a Function Expression to create the function and assign it to the property `user.sayHi` of the object.
+Here we've just used a Function Expression to create a function and assign it to the property `user.sayHi` of the object.
-Then we can call it. The user can now speak!
+Then we can call it as `user.sayHi()`. The user can now speak!
-A function that is the property of an object is called its *method*.
+A function that is a property of an object is called its *method*.
So, here we've got a method `sayHi` of the object `user`.
@@ -51,7 +51,7 @@ let user = {
// first, declare
function sayHi() {
alert("Hello!");
-};
+}
// then add as a method
user.sayHi = sayHi;
@@ -81,7 +81,7 @@ user = {
// method shorthand looks better, right?
user = {
*!*
- sayHi() { // same as "sayHi: function()"
+ sayHi() { // same as "sayHi: function(){...}"
*/!*
alert("Hello");
}
@@ -90,7 +90,7 @@ user = {
As demonstrated, we can omit `"function"` and just write `sayHi()`.
-To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases the shorter syntax is preferred.
+To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases, the shorter syntax is preferred.
## "this" in methods
@@ -160,14 +160,16 @@ let user = {
let admin = user;
user = null; // overwrite to make things obvious
-admin.sayHi(); // Whoops! inside sayHi(), the old name is used! error!
+*!*
+admin.sayHi(); // TypeError: Cannot read property 'name' of null
+*/!*
```
If we used `this.name` instead of `user.name` inside the `alert`, then the code would work.
## "this" is not bound
-In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function.
+In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function, even if it's not a method of an object.
There's no syntax error in the following example:
diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md
index 8c1fea8eb..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
@@ -4,14 +4,14 @@ importance: 2
# Two functions â one object
-Is it possible to create functions `A` and `B` such as `new A()==new B()`?
+Is it possible to create functions `A` and `B` so that `new A() == new B()`?
```js no-beautify
function A() { ... }
function B() { ... }
-let a = new A;
-let b = new B;
+let a = new A();
+let b = new B();
alert( a == b ); // true
```
diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js
index 036053927..bba80e5c2 100644
--- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js
+++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/_js.view/test.js
@@ -10,6 +10,11 @@ describe("calculator", function() {
calculator = new Calculator();
calculator.read();
});
+
+ it("the read method asks for two values using prompt and remembers them in object properties", function() {
+ assert.equal(calculator.a, 2);
+ assert.equal(calculator.b, 3);
+ });
it("when 2 and 3 are entered, the sum is 5", function() {
assert.equal(calculator.sum(), 5);
diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md
index 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 a885e35ff..a335464f1 100644
--- a/1-js/04-object-basics/06-constructor-new/article.md
+++ b/1-js/04-object-basics/06-constructor-new/article.md
@@ -1,6 +1,6 @@
# Constructor, operator "new"
-The regular `{...}` syntax allows to create one object. But often we need to create many similar objects, like multiple users or menu items and so on.
+The regular `{...}` syntax allows us to create one object. But often we need to create many similar objects, like multiple users or menu items and so on.
That can be done using constructor functions and the `"new"` operator.
@@ -64,13 +64,14 @@ Now if we want to create other users, we can call `new User("Ann")`, `new User("
That's the main purpose of constructors -- to implement reusable object creation code.
-Let's note once again -- technically, any function can be used as a constructor. That is: any function can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`.
+Let's note once again -- technically, any function (except arrow functions, as they don't have `this`) can be used as a constructor. It can be run with `new`, and it will execute the algorithm above. The "capital letter first" is a common agreement, to make it clear that a function is to be run with `new`.
````smart header="new function() { ... }"
-If we have many lines of code all about creation of a single complex object, we can wrap them in constructor function, like this:
+If we have many lines of code all about creation of a single complex object, we can wrap them in an immediately called constructor function, like this:
```js
-let user = new function() {
+// create a function and immediately call it with new
+let user = new function() {
this.name = "John";
this.isAdmin = false;
@@ -80,7 +81,7 @@ let user = new function() {
};
```
-The constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse.
+This constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse.
````
## Constructor mode test: new.target
@@ -91,7 +92,7 @@ The syntax from this section is rarely used, skip it unless you want to know eve
Inside a function, we can check whether it was called with `new` or without it, using a special `new.target` property.
-It is empty for regular calls and equals the function if called with `new`:
+It is undefined for regular calls and equals the function if called with `new`:
```js run
function User() {
@@ -170,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/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md
index 974689020..4c6029423 100644
--- a/1-js/04-object-basics/07-optional-chaining/article.md
+++ b/1-js/04-object-basics/07-optional-chaining/article.md
@@ -3,46 +3,94 @@
[recent browser="new"]
-The optional chaining `?.` is an error-proof way to access nested object properties, even if an intermediate property doesn't exist.
+The optional chaining `?.` is a safe way to access nested object properties, even if an intermediate property doesn't exist.
-## The problem
+## The "non-existing property" problem
If you've just started to read the tutorial and learn JavaScript, maybe the problem hasn't touched you yet, but it's quite common.
-For example, some of our users have addresses, but few did not provide them. Then we can't safely read `user.address.street`:
+As an example, let's say we have `user` objects that hold the information about our users.
+
+Most of our users have addresses in `user.address` property, with the street `user.address.street`, but some did not provide them.
+
+In such case, when we attempt to get `user.address.street`, and the user happens to be without an address, we get an error:
```js run
-let user = {}; // the user happens to be without address
+let user = {}; // a user without "address" property
alert(user.address.street); // Error!
```
-Or, in the web development, we'd like to get an information about an element on the page, but it may not exist:
+That's the expected result. JavaScript works like this. As `user.address` is `undefined`, an attempt to get `user.address.street` fails with an error.
+
+In many practical cases we'd prefer to get `undefined` instead of an error here (meaning "no street").
+
+...and another example. In Web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element.
```js run
-// Error if the result of querySelector(...) is null
-let html = document.querySelector('.my-element').innerHTML;
+// document.querySelector('.elem') is null if there's no element
+let html = document.querySelector('.elem').innerHTML; // error if it's null
```
-Before `?.` appeared in the language, the `&&` operator was used to work around that.
+Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` property of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result.
-For example:
+How can we do this?
+
+The obvious solution would be to check the value using `if` or the conditional operator `?`, before accessing its property, like this:
+
+```js
+let user = {};
+
+alert(user.address ? user.address.street : undefined);
+```
+
+It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code.
+
+Here's how the same would look for `document.querySelector`:
+
+```js run
+let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null;
+```
+
+We can see that the element search `document.querySelector('.elem')` is actually called twice here. Not good.
+
+For more deeply nested properties, it becomes even uglier, as more repetitions are required.
+
+E.g. let's get `user.address.street.name` in a similar fashion.
+
+```js
+let user = {}; // user has no address
+
+alert(user.address ? user.address.street ? user.address.street.name : null : null);
+```
+
+That's just awful, one may even have problems understanding such code.
+
+There's a little better way to write it, using the `&&` operator:
```js run
let user = {}; // user has no address
-alert( user && user.address && user.address.street ); // undefined (no error)
+alert( user.address && user.address.street && user.address.street.name ); // undefined (no error)
```
-AND'ing the whole path to the property ensures that all components exist, but is cumbersome to write.
+AND'ing the whole path to the property ensures that all components exist (if not, the evaluation stops), but also isn't ideal.
+
+As you can see, property names are still duplicated in the code. E.g. in the code above, `user.address` appears three times.
+
+That's why the optional chaining `?.` was added to the language. To solve this problem once and for all!
## Optional chaining
-The optional chaining `?.` stops the evaluation and returns `undefined` if the part before `?.` is `undefined` or `null`.
+The optional chaining `?.` stops the evaluation if the value before `?.` is `undefined` or `null` and returns `undefined`.
**Further in this article, for brevity, we'll be saying that something "exists" if it's not `null` and not `undefined`.**
-Here's the safe way to access `user.address.street`:
+In other words, `value?.prop`:
+- works as `value.prop`, if `value` exists,
+- otherwise (when `value` is `undefined/null`) it returns `undefined`.
+
+Here's the safe way to access `user.address.street` using `?.`:
```js run
let user = {}; // user has no address
@@ -50,6 +98,14 @@ let user = {}; // user has no address
alert( user?.address?.street ); // undefined (no error)
```
+The code is short and clean, there's no duplication at all.
+
+Here's an example with `document.querySelector`:
+
+```js run
+let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element
+```
+
Reading the address with `user?.address` works even if `user` object doesn't exist:
```js run
@@ -61,16 +117,14 @@ alert( user?.address.street ); // undefined
Please note: the `?.` syntax makes optional the value before it, but not any further.
-In the example above, `user?.` allows only `user` to be `null/undefined`.
-
-On the other hand, if `user` does exist, then it must have `user.address` property, otherwise `user?.address.street` gives an error at the second dot.
+E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/undefined` (and returns `undefined` in that case), but that's only for `user`. Further properties are accessed in a regular way. If we want some of them to be optional, then we'll need to replace more `.` with `?.`.
```warn header="Don't overuse the optional chaining"
We should use `?.` only where it's ok that something doesn't exist.
-For example, if according to our coding logic `user` object must be there, but `address` is optional, then `user.address?.street` would be better.
+For example, if according to our code logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`.
-So, if `user` happens to be undefined due to a mistake, we'll know about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug.
+Then, if `user` happens to be undefined, we'll see a programming error about it and fix it. Otherwise, if we overuse `?.`, coding errors can be silenced where not appropriate, and become more difficult to debug.
```
````warn header="The variable before `?.` must be declared"
@@ -80,25 +134,27 @@ If there's no variable `user` at all, then `user?.anything` triggers an error:
// ReferenceError: user is not defined
user?.address;
```
-There must be `let/const/var user`. The optional chaining works only for declared variables.
+The variable must be declared (e.g. `let/const/var user` or as a function parameter). The optional chaining works only for declared variables.
````
## Short-circuiting
As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist.
-So, if there are any further function calls or side effects, they don't occur:
+So, if there are any further function calls or operations to the right of `?.`, they won't be made.
+
+For instance:
```js run
let user = null;
let x = 0;
-user?.sayHi(x++); // nothing happens
+user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++
alert(x); // 0, value not incremented
```
-## Other cases: ?.(), ?.[]
+## Other variants: ?.(), ?.[]
The optional chaining `?.` is not an operator, but a special syntax construct, that also works with functions and square brackets.
@@ -107,39 +163,40 @@ For example, `?.()` is used to call a function that may not exist.
In the code below, some of our users have `admin` method, and some don't:
```js run
-let user1 = {
+let userAdmin = {
admin() {
alert("I am admin");
}
-}
+};
+
+let userGuest = {};
-let user2 = {};
+*!*
+userAdmin.admin?.(); // I am admin
+*/!*
*!*
-user1.admin?.(); // I am admin
-user2.admin?.();
+userGuest.admin?.(); // nothing happens (no such method)
*/!*
```
-Here, in both lines we first use the dot `.` to get `admin` property, because the user object must exist, so it's safe read from it.
+Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the `user` object exists, so it's safe read from it.
-Then `?.()` checks the left part: if the admin function exists, then it runs (for `user1`). Otherwise (for `user2`) the evaluation stops without errors.
+Then `?.()` checks the left part: if the `admin` function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors.
The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist.
```js run
+let key = "firstName";
+
let user1 = {
firstName: "John"
};
-let user2 = null; // Imagine, we couldn't authorize the user
-
-let key = "firstName";
+let user2 = null;
alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined
-
-alert( user1?.[key]?.something?.not?.existing); // undefined
```
Also we can use `?.` with `delete`:
@@ -148,28 +205,29 @@ Also we can use `?.` with `delete`:
delete user?.name; // delete user.name if user exists
```
-```warn header="We can use `?.` for safe reading and deleting, but not writing"
-The optional chaining `?.` has no use at the left side of an assignment:
+````warn header="We can use `?.` for safe reading and deleting, but not writing"
+The optional chaining `?.` has no use on the left side of an assignment.
+For example:
```js run
-// the idea of the code below is to write user.name, if user exists
+let user = null;
user?.name = "John"; // Error, doesn't work
-// because it evaluates to undefined = "John"
+// because it evaluates to: undefined = "John"
```
+````
+
## Summary
-The `?.` syntax has three forms:
+The optional chaining `?.` syntax has three forms:
1. `obj?.prop` -- returns `obj.prop` if `obj` exists, otherwise `undefined`.
2. `obj?.[prop]` -- returns `obj[prop]` if `obj` exists, otherwise `undefined`.
-3. `obj?.method()` -- calls `obj.method()` if `obj` exists, otherwise returns `undefined`.
+3. `obj.method?.()` -- calls `obj.method()` if `obj.method` exists, otherwise returns `undefined`.
As we can see, all of them are straightforward and simple to use. The `?.` checks the left part for `null/undefined` and allows the evaluation to proceed if it's not so.
A chain of `?.` allows to safely access nested properties.
-Still, we should apply `?.` carefully, only where it's ok that the left part doesn't to exist.
-
-So that it won't hide programming errors from us, if they occur.
+Still, we should apply `?.` carefully, only where it's acceptable, according to our code logic, that the left part doesn't exist. So that it won't hide programming errors from us, if they occur.
diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md
index e469bb0ba..10a98af0a 100644
--- a/1-js/04-object-basics/08-symbol/article.md
+++ b/1-js/04-object-basics/08-symbol/article.md
@@ -1,9 +1,16 @@
# Symbol type
-By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types.
+By specification, only two primitive types may serve as object property keys:
-Till now we've been using only strings. Now let's see the benefits that symbols can give us.
+- string type, or
+- symbol type.
+
+Otherwise, if one uses another type, such as number, it's autoconverted to string. So that `obj[1]` is the same as `obj["1"]`, and `obj[true]` is the same as `obj["true"]`.
+
+Until now we've been using only strings.
+
+Now let's explore symbols, see what they can do for us.
## Symbols
@@ -12,18 +19,17 @@ A "symbol" represents a unique identifier.
A value of this type can be created using `Symbol()`:
```js
-// id is a new symbol
let id = Symbol();
```
-Upon creation, we can give symbol a description (also called a symbol name), mostly useful for debugging purposes:
+Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes:
```js
// id is a symbol with the description "id"
let id = Symbol("id");
```
-Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn't affect anything.
+Symbols are guaranteed to be unique. Even if we create many symbols with exactly the same description, they are different values. The description is just a label that doesn't affect anything.
For instance, here are two symbols with the same description -- they are not equal:
@@ -38,6 +44,8 @@ alert(id1 == id2); // false
If you are familiar with Ruby or another language that also has some sort of "symbols" -- please don't be misguided. JavaScript symbols are different.
+So, to summarize, a symbol is a "primitive unique value" with an optional description. Let's see where we can use them.
+
````warn header="Symbols don't auto-convert to a string"
Most values in JavaScript support implicit conversion to a string. For instance, we can `alert` almost any value, and it will work. Symbols are special. They don't auto-convert.
@@ -53,6 +61,7 @@ alert(id); // TypeError: Cannot convert a Symbol value to a string
That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not accidentally convert one into another.
If we really want to show a symbol, we need to explicitly call `.toString()` on it, like here:
+
```js run
let id = Symbol("id");
*!*
@@ -61,6 +70,7 @@ alert(id.toString()); // Symbol(id), now it works
```
Or get `symbol.description` property to show the description only:
+
```js run
let id = Symbol("id");
*!*
@@ -72,6 +82,7 @@ alert(id.description); // id
## "Hidden" properties
+
Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite.
For instance, if we're working with `user` objects, that belong to a third-party code. We'd like to add identifiers to them.
@@ -92,9 +103,9 @@ alert( user[id] ); // we can access the data using the symbol as the key
What's the benefit of using `Symbol("id")` over a string `"id"`?
-As `user` objects belongs to another code, and that code also works with them, we shouldn't just add any fields to it. That's unsafe. But a symbol cannot be accessed accidentally, the third-party code probably won't even see it, so it's probably all right to do.
+As `user` objects belong to another codebase, it's unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code won't be aware of newly defined symbols, so it's safe to add symbols to the `user` objects.
-Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. That may be another JavaScript library, so that the scripts are completely unaware of each other.
+Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes.
Then that script can create its own `Symbol("id")`, like this:
@@ -109,7 +120,7 @@ There will be no conflict between our and their identifiers, because symbols are
...But if we used a string `"id"` instead of a symbol for the same purpose, then there *would* be a conflict:
-```js run
+```js
let user = { name: "John" };
// Our script uses "id" property
@@ -158,10 +169,10 @@ for (let key in user) alert(key); // name, age (no symbols)
*/!*
// the direct access by the symbol works
-alert( "Direct: " + user[id] );
+alert( "Direct: " + user[id] ); // Direct: 123
```
-`Object.keys(user)` also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property.
+[Object.keys(user)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property.
In contrast, [Object.assign](mdn:js/Object/assign) copies both string and symbol properties:
@@ -206,12 +217,12 @@ Symbols inside the registry are called *global symbols*. If we want an applicati
```smart header="That sounds like Ruby"
In some programming languages, like Ruby, there's a single symbol per name.
-In JavaScript, as we can see, that's right for global symbols.
+In JavaScript, as we can see, that's true for global symbols.
```
### Symbol.keyFor
-For global symbols, not only `Symbol.for(key)` returns a symbol by name, but there's a reverse call: `Symbol.keyFor(sym)`, that does the reverse: returns a name by a global symbol.
+We have seen that for global symbols, `Symbol.for(key)` returns a symbol by name. To do the opposite -- return a name by global symbol -- we can use: `Symbol.keyFor(sym)`:
For instance:
@@ -227,7 +238,7 @@ alert( Symbol.keyFor(sym2) ); // id
The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and returns `undefined`.
-That said, any symbols have `description` property.
+That said, all symbols have the `description` property.
For instance:
@@ -268,10 +279,11 @@ Symbols are always different values, even if they have the same name. If we want
Symbols have two main use cases:
1. "Hidden" object properties.
+
If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite.
So we can "covertly" hide something into objects that we need, but others should not see, using symbolic properties.
2. There are many system symbols used by JavaScript which are accessible as `Symbol.*`. We can use them to alter some built-in behaviors. For instance, later in the tutorial we'll use `Symbol.iterator` for [iterables](info:iterable), `Symbol.toPrimitive` to setup [object-to-primitive conversion](info:object-toprimitive) and so on.
-Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in functions and syntax constructs don't use these methods.
+Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. But most libraries, built-in functions and syntax constructs don't use these methods.
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 36b6c6460..fa68da583 100644
--- a/1-js/04-object-basics/09-object-toprimitive/article.md
+++ b/1-js/04-object-basics/09-object-toprimitive/article.md
@@ -3,19 +3,40 @@
What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`?
-In that case, objects are auto-converted to primitives, and then the operation is carried out.
+JavaScript doesn't allow you to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle addition (or other operators).
+
+In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value.
+
+That's an important limitation: the result of `obj1 + obj2` (or another math operation) can't be another object!
+
+E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board".
+
+So, because we can't technically do much here, there's no maths with objects in real projects. When it happens, with rare exceptions, it's because of a coding mistake.
+
+In this chapter we'll cover how an object converts to primitive and how to customize it.
+
+We have two purposes:
+
+1. It will allow us to understand what's going on in case of coding mistakes, when such an operation happened accidentally.
+2. There are exceptions, where such operations are possible and look good. E.g. subtracting or comparing dates (`Date` objects). We'll come across them later.
+
+## Conversion rules
In the chapter we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it.
-1. All objects are `true` in a boolean context. There are only numeric and string conversions.
+1. There's no conversion to boolean. All objects are `true` in a boolean context, as simple as that. There exist only numeric and string conversions.
2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates.
-3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts.
+3. As for the string conversion -- it usually happens when we output an object with `alert(obj)` and in similar contexts.
+
+We can implement string and numeric conversion by ourselves, using special object methods.
-## ToPrimitive
+Now let's get into technical details, because it's the only way to cover the topic in-depth.
-We can fine-tune string and numeric conversion, using special object methods.
+## Hints
-There are three variants of type conversion, so-called "hints", described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive):
+How does JavaScript decide which conversion to apply?
+
+There are three variants of type conversion, that happen in various situations. They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive):
`"string"`
: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`:
@@ -43,10 +64,12 @@ There are three variants of type conversion, so-called "hints", described in the
let greater = user1 > user2;
```
+ Most built-in mathematical functions also include such conversion.
+
`"default"`
: Occurs in rare cases when the operator is "not sure" what type to expect.
- For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it.
+ For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them). So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it.
Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used.
@@ -60,21 +83,19 @@ There are three variants of type conversion, so-called "hints", described in the
The greater and less comparison operators, such as `<` `>`, can work with both strings and numbers too. Still, they use the `"number"` hint, not `"default"`. That's for historical reasons.
- In practice though, we don't need to remember these peculiar details, because all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we can do the same.
+In practice though, things are a bit simpler.
-```smart header="No `\"boolean\"` hint"
-Please note -- there are only three hints. It's that simple.
+All built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we probably should do the same.
-There is no "boolean" hint (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions.
-```
+Still, it's important to know about all 3 hints, soon we'll see why.
**To do the conversion, JavaScript tries to find and call three object methods:**
1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists,
2. Otherwise if hint is `"string"`
- - try `obj.toString()` and `obj.valueOf()`, whatever exists.
+ - try calling `obj.toString()` or `obj.valueOf()`, whatever exists.
3. Otherwise if hint is `"number"` or `"default"`
- - try `obj.valueOf()` and `obj.toString()`, whatever exists.
+ - try calling `obj.valueOf()` or `obj.toString()`, whatever exists.
## Symbol.toPrimitive
@@ -82,11 +103,14 @@ Let's start from the first method. There's a built-in symbol named `Symbol.toPri
```js
obj[Symbol.toPrimitive] = function(hint) {
- // must return a primitive value
+ // here goes the code to convert this object to a primitive
+ // it must return a primitive value
// hint = one of "string", "number", "default"
};
```
+If the method `Symbol.toPrimitive` exists, it's used for all hints, and no more methods are needed.
+
For instance, here `user` object implements it:
```js run
@@ -106,17 +130,16 @@ alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
```
-As we can see from the code, `user` becomes a self-descriptive string or a money amount depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases.
-
+As we can see from the code, `user` becomes a self-descriptive string or a money amount, depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases.
## toString/valueOf
-Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion.
+If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`:
-If there's no `Symbol.toPrimitive` then JavaScript tries to find them and try in the order:
+- For the `"string"` hint: 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).
-- `toString -> valueOf` for "string" hint.
-- `valueOf -> toString` otherwise.
+Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion.
These methods must return a primitive value. If `toString` or `valueOf` returns an object, then it's ignored (same as if there were no method).
@@ -136,9 +159,9 @@ alert(user.valueOf() === user); // true
So if we try to use an object as a string, like in an `alert` or so, then by default we see `[object Object]`.
-And the default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist.
+The default `valueOf` is mentioned here only for the sake of completeness, to avoid any confusion. As you can see, it returns the object itself, and so is ignored. Don't ask me why, that's for historical reasons. So we can assume it doesn't exist.
-Let's implement these methods.
+Let's implement these methods to customize the conversion.
For instance, here `user` does the same as above using a combination of `toString` and `valueOf` instead of `Symbol.toPrimitive`:
@@ -183,27 +206,27 @@ alert(user + 500); // toString -> John500
In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions.
-## Return types
+### A conversion can return any primitive type
The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive.
-There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint `"number"`.
+There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for the hint `"number"`.
The only mandatory thing: these methods must return a primitive, not an object.
```smart header="Historical notes"
For historical reasons, if `toString` or `valueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript.
-In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there will be an error.
+In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error.
```
## Further conversions
As we know already, many operators and functions perform type conversions, e.g. multiplication `*` converts operands to numbers.
-If we pass an object as an argument, then there are two stages:
+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 resulting primitive isn't of the right type, it's converted.
+2. If necessary for further calculations, the resulting primitive is also converted.
For instance:
@@ -230,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
@@ -240,16 +263,18 @@ The object-to-primitive conversion is called automatically by many built-in func
There are 3 types (hints) of it:
- `"string"` (for `alert` and other operations that need a string)
- `"number"` (for maths)
-- `"default"` (few operators)
+- `"default"` (few operators, usually objects implement it the same way as `"number"`)
-The specification describes explicitly which operator uses which hint. There are very few operators that "don't know what to expect" and use the `"default"` hint. Usually for built-in objects `"default"` hint is handled the same way as `"number"`, so in practice the last two are often merged together.
+The specification describes explicitly which operator uses which hint.
The conversion algorithm is:
1. Call `obj[Symbol.toPrimitive](hint)` if the method exists,
2. Otherwise if hint is `"string"`
- - try `obj.toString()` and `obj.valueOf()`, whatever exists.
+ - try calling `obj.toString()` or `obj.valueOf()`, whatever exists.
3. Otherwise if hint is `"number"` or `"default"`
- - try `obj.valueOf()` and `obj.toString()`, whatever exists.
+ - try calling `obj.valueOf()` or `obj.toString()`, whatever exists.
+
+All these methods must return a primitive to work (if defined).
-In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for all conversions that return a "human-readable" representation of an object, for logging or debugging purposes.
+In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes.
diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md
index 50c781ea5..208f84cc7 100644
--- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md
+++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md
@@ -15,4 +15,4 @@ str.test = 5;
alert(str.test);
```
-How do you think, will it work? What will be shown?
+What do you think, will it work? What will be shown?
diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md
index 6c13acda6..69e7196e9 100644
--- a/1-js/05-data-types/01-primitives-methods/article.md
+++ b/1-js/05-data-types/01-primitives-methods/article.md
@@ -39,7 +39,7 @@ Objects are "heavier" than primitives. They require additional resources to supp
Here's the paradox faced by the creator of JavaScript:
-- There are many things one would want to do with a primitive like a string or a number. It would be great to access them as methods.
+- There are many things one would want to do with a primitive, like a string or a number. It would be great to access them using methods.
- Primitives must be as fast and lightweight as possible.
The solution looks a little bit awkward, but here it is:
@@ -48,7 +48,7 @@ The solution looks a little bit awkward, but here it is:
2. The language allows access to methods and properties of strings, numbers, booleans and symbols.
3. In order for that to work, a special "object wrapper" that provides the extra functionality is created, and then is destroyed.
-The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean` and `Symbol`. Thus, they provide different sets of methods.
+The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. Thus, they provide different sets of methods.
For instance, there exists a string method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns a capitalized `str`.
@@ -104,9 +104,10 @@ if (zero) { // zero is true, because it's an object
}
```
-On the other hand, using the same functions `String/Number/Boolean` without `new` is a totally sane and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive).
+On the other hand, using the same functions `String/Number/Boolean` without `new` is totally fine and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive).
For example, this is entirely valid:
+
```js
let num = Number("123"); // convert a string to number
```
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 e768f4d47..8e41f673d 100644
--- a/1-js/05-data-types/02-number/article.md
+++ b/1-js/05-data-types/02-number/article.md
@@ -2,9 +2,9 @@
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-2008_revision), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter.
+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, to represent integers of arbitrary length. They are sometimes needed, because a regular number can't exceed 253 or be less than -253 . 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.
@@ -16,45 +16,56 @@ Imagine we need to write 1 billion. The obvious way is:
let billion = 1000000000;
```
-But in real life, we usually avoid writing a long string of zeroes as it's easy to mistype. Also, we are lazy. We will usually write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers.
+We also can use underscore `_` as the separator:
-In JavaScript, we shorten a number by appending the letter `"e"` to the number and specifying the zeroes count:
+```js
+let billion = 1_000_000_000;
+```
+
+Here the underscore `_` plays the role of the "[syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)", it makes the number more readable. The JavaScript engine simply ignores `_` between digits, so it's exactly the same one billion as above.
+
+In real life though, we try to avoid writing long sequences of zeroes. We're too lazy for that. We'll try to write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers.
+
+In JavaScript, we can shorten a number by appending the letter `"e"` to it and specifying the zeroes count:
```js run
let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes
-alert( 7.3e9 ); // 7.3 billions (7,300,000,000)
+alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)
```
-In other words, `"e"` multiplies the number by `1` with the given zeroes count.
+In other words, `e` multiplies the number by `1` with the given zeroes count.
```js
-1e3 = 1 * 1000
-1.23e6 = 1.23 * 1000000
+1e3 === 1 * 1000; // e3 means *1000
+1.23e6 === 1.23 * 1000000; // e6 means *1000000
```
-Now let's write something very small. Say, 1 microsecond (one millionth of a second):
+Now let's write something very small. Say, 1 microsecond (one-millionth of a second):
```js
-let ms = 0.000001;
+let mÑs = 0.000001;
```
-Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say the same as:
+Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could write the same as:
```js
-let ms = 1e-6; // six zeroes to the left from 1
+let mcs = 1e-6; // five zeroes to the left from 1
```
-If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`.
+If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`.
In other words, a negative number after `"e"` means a division by 1 with the given number of zeroes:
```js
// -3 divides by 1 with 3 zeroes
-1e-3 = 1 / 1000 (=0.001)
+1e-3 === 1 / 1000; // 0.001
// -6 divides by 1 with 6 zeroes
-1.23e-6 = 1.23 / 1000000 (=0.00000123)
+1.23e-6 === 1.23 / 1000000; // 0.00000123
+
+// an example with a bigger number
+1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times
```
### Hex, binary and octal numbers
@@ -92,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
@@ -107,9 +118,10 @@ Common use cases for this are:
```warn header="Two dots to call a method"
Please note that two dots in `123456..toString(36)` is not a typo. If we want to call a method directly on a number, like `toString` in the example above, then we need to place two dots `..` after it.
-If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now goes the method.
+If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now uses the method.
Also could write `(123456).toString(36)`.
+
```
## Rounding
@@ -125,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` and `-1.1` becomes `-1`.
+: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`.
`Math.trunc` (not supported by Internet Explorer)
: Removes anything after the decimal point without rounding: `3.1` becomes `3`, `-1.1` becomes `-1`.
@@ -135,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` |
@@ -148,11 +162,11 @@ There are two ways to do so:
1. Multiply-and-divide.
- For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100` (or a bigger power of 10), call the rounding function and then divide it back.
+ For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100`, call the rounding function and then divide it back.
```js run
let num = 1.23456;
- alert( Math.floor(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
+ alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
```
2. The method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rounds the number to `n` digits after the point and returns a string representation of the result.
@@ -169,20 +183,20 @@ There are two ways to do so:
alert( num.toFixed(1) ); // "12.4"
```
- Please note that result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end:
+ Please note that the result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end:
```js run
let num = 12.34;
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: `+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
-Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers), and 1 bit is for the sign.
+Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point, and 1 bit is for the sign.
-If a number is too big, it would overflow the 64-bit storage, potentially giving an infinity:
+If a number is really huge, it may overflow the 64-bit storage and become a special numeric value `Infinity`:
```js run
alert( 1e500 ); // Infinity
@@ -190,7 +204,7 @@ alert( 1e500 ); // Infinity
What may be a little less obvious, but happens quite often, is the loss of precision.
-Consider this (falsy!) test:
+Consider this (falsy!) equality test:
```js run
alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!*
@@ -204,13 +218,19 @@ Strange! What is it then if not `0.3`?
alert( 0.1 + 0.2 ); // 0.30000000000000004
```
-Ouch! There are more consequences than an incorrect comparison here. Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone.
+Ouch! Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone.
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.
-In other words, 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.
@@ -230,14 +250,14 @@ 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):
```js run
let sum = 0.1 + 0.2;
-alert( sum.toFixed(2) ); // 0.30
+alert( sum.toFixed(2) ); // "0.30"
```
Please note that `toFixed` always returns a string. It ensures that it has 2 digits after the decimal point. That's actually convenient if we have an e-shopping and need to show `$0.30`. For other cases, we can use the unary plus to coerce it into a number:
@@ -254,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.
@@ -276,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
@@ -296,7 +316,7 @@ They belong to the type `number`, but are not "normal" numbers, so there are spe
alert( isNaN("str") ); // true
```
- But do we need this function? Can't we just use the comparison `=== NaN`? Sorry, but the answer is no. The value `NaN` is unique in that it does not equal anything, including itself:
+ But do we need this function? Can't we just use the comparison `=== NaN`? Unfortunately not. The value `NaN` is unique in that it does not equal anything, including itself:
```js run
alert( NaN === NaN ); // false
@@ -320,18 +340,46 @@ let num = +prompt("Enter a number", '');
alert( isFinite(num) );
```
-Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`.
+Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`.
+
+````smart header="`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`.
-```smart header="Compare with `Object.is`"
+ ```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
-There is a special built-in method [`Object.is`](mdn:js/Object/is) that compares values like `===`, but is more reliable for two edge cases:
+ // 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)).
```
@@ -345,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.
@@ -383,7 +431,7 @@ JavaScript has a built-in [Math](https://developer.mozilla.org/en/docs/Web/JavaS
A few examples:
`Math.random()`
-: Returns a random number from 0 to 1 (not including 1)
+: Returns a random number from 0 to 1 (not including 1).
```js run
alert( Math.random() ); // 0.1234567894322
@@ -391,8 +439,8 @@ A few examples:
alert( Math.random() ); // ... (any random numbers)
```
-`Math.max(a, b, c...)` / `Math.min(a, b, c...)`
-: Returns the greatest/smallest from the arbitrary number of arguments.
+`Math.max(a, b, c...)` and `Math.min(a, b, c...)`
+: Returns the greatest and smallest from the arbitrary number of arguments.
```js run
alert( Math.max(3, 5, -10, 0, 1) ); // 5
@@ -400,13 +448,13 @@ A few examples:
```
`Math.pow(n, power)`
-: Returns `n` raised the given power
+: Returns `n` raised to the given power.
```js run
alert( Math.pow(2, 10) ); // 2 in power 10 = 1024
```
-There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object.
+There are more functions and constants in `Math` object, including trigonometry, which you can find in the [docs for the Math object](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math).
## Summary
@@ -421,6 +469,13 @@ For different numeral systems:
- `parseInt(str, base)` parses the string `str` into an integer in numeral system with given `base`, `2 ⤠base ⤠36`.
- `num.toString(base)` converts a number to a string in the numeral system with the given `base`.
+For regular number tests:
+
+- `isNaN(value)` converts its argument to a number and then tests it for being `NaN`
+- `Number.isNaN(value)` checks whether its argument belongs to the `number` type, and if so, tests it for being `NaN`
+- `isFinite(value)` converts its argument to a number and then tests it for not being `NaN/Infinity/-Infinity`
+- `Number.isFinite(value)` checks whether its argument belongs to the `number` type, and if so, tests it for not being `NaN/Infinity/-Infinity`
+
For converting values like `12pt` and `100px` to a number:
- Use `parseInt/parseFloat` for the "soft" conversion, which reads a number from a string and then returns the value they could read before the error.
@@ -432,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/solution.md b/1-js/05-data-types/03-string/3-truncate/solution.md
index 5546c47ee..d51672ae6 100644
--- a/1-js/05-data-types/03-string/3-truncate/solution.md
+++ b/1-js/05-data-types/03-string/3-truncate/solution.md
@@ -1,6 +1,6 @@
The maximal length must be `maxlength`, so we need to cut it a little shorter, to give space for the ellipsis.
-Note that there is actually a single unicode character for an ellipsis. That's not three dots.
+Note that there is actually a single Unicode character for an ellipsis. That's not three dots.
```js run demo
function truncate(str, maxlength) {
diff --git a/1-js/05-data-types/03-string/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 765823d7c..60ce2b6f0 100644
--- a/1-js/05-data-types/03-string/article.md
+++ b/1-js/05-data-types/03-string/article.md
@@ -48,9 +48,9 @@ let guestList = "Guests: // Error: Unexpected token ILLEGAL
* John";
```
-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.
+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`|Carriage return: not used alone. Windows text files use a combination of two characters `\r\n` to represent a line break. |
-|`\'`, `\"`|Quotes|
+|`\r`|In Windows text files a combination of two characters `\r\n` represents a new break, while on non-Windows OS it's just `\n`. That's for historical reasons, most Windows software also understands `\n`. |
+|`\'`, `\"`, \\`|Quotes|
|`\\`|Backslash|
|`\t`|Tab|
-|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- kept for compatibility, not used nowadays. |
-|`\xXX`|Unicode character with the given hexadecimal unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.|
-|`\uXXXX`|A unicode symbol with the hex code `XXXX` in UTF-16 encoding, for instance `\u00A9` -- is a unicode for the copyright symbol `©`. It must be exactly 4 hex digits. |
-|`\u{Xâ¦XXXXXX}` (1 to 6 hex characters)|A unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two unicode symbols, taking 4 bytes. This way we can insert long codes. |
+|`\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:
@@ -110,21 +103,13 @@ alert( 'I*!*\'*/!*m the Walrus!' ); // *!*I'm*/!* the Walrus!
As you can see, we have to prepend the inner quote by the backslash `\'`, because otherwise it would indicate the string end.
-Of course, only to the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead:
+Of course, only the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead:
```js run
-alert( `I'm the Walrus!` ); // I'm the Walrus!
+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`:
@@ -214,7 +202,7 @@ alert( 'Interface'.toLowerCase() ); // interface
Or, if we want a single character lowercased:
-```js
+```js run
alert( 'Interface'[0].toLowerCase() ); // 'i'
```
@@ -239,7 +227,7 @@ alert( str.indexOf('widget') ); // -1, not found, the search is case-sensitive
alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id)
```
-The optional second parameter allows us to search starting from the given position.
+The optional second parameter allows us to start searching from a given position.
For instance, the first occurrence of `"id"` is at position `1`. To look for the next occurrence, let's start the search from position `2`:
@@ -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_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 is 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.
@@ -371,8 +320,8 @@ alert( "Widget".includes("id", 3) ); // false, from position 3 there is no "id"
The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say:
```js run
-alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
-alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
+alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Widget" starts with "Wid"
+alert( "Wid*!*get*/!*".endsWith("get") ); // true, "Widget" ends with "get"
```
## Getting a substring
@@ -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{|}~ÂÂÂÂÂ
// ¡¢£¤¥¦§¨©ª«¬Â®¯°±²³´µ¶·¸¹º»¼½¾¿ÃÃÃÃÃÃ
ÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃÃ
```
@@ -526,15 +475,15 @@ Now it becomes obvious why `a > Z`.
The characters are compared by their numeric code. The greater code means that the character is greater. The code for `a` (97) is greater than the code for `Z` (90).
- All lowercase letters go after uppercase letters because their codes are greater.
-- Some letters like `Ã` stand apart from the main alphabet. Here, it's code is greater than anything from `a` to `z`.
+- Some letters like `Ã` stand apart from the main alphabet. Here, its code is greater than anything from `a` to `z`.
-### Correct comparisons
+### Correct comparisons [#correct-comparisons]
The "right" algorithm to do string comparisons is more complex than it may seem, because alphabets are different for different languages.
So, the browser needs to know the language to compare.
-Luckily, all modern browsers (IE10- requires the additional library [Intl.js](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf).
+Luckily, 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 `"aÌ"` 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 SÌ.
-
-```js run
-alert( 'S\u0307' ); // SÌ
-```
-
-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": `SÌÌ£`.
-
-For example:
-
-```js run
-alert( 'S\u0307\u0323' ); // SÌÌ£
-```
-
-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ÌÌ£, S + dot above + dot below
-let s2 = 'S\u0323\u0307'; // SÌ£Ì, 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 daadf494b..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
@@ -57,9 +57,9 @@ alert( getMaxSubSum([1, 2, 3]) ); // 6
alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100
```
-The solution has a time complexety of [O(n2 )](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer.
+The solution has a time complexity of [O(n2 )](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer.
-For big arrays (1000, 10000 or more items) such algorithms can lead to a serious sluggishness.
+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/3-call-array-this/task.md b/1-js/05-data-types/04-array/3-call-array-this/task.md
index 340c5feef..f1e13499c 100644
--- a/1-js/05-data-types/04-array/3-call-array-this/task.md
+++ b/1-js/05-data-types/04-array/3-call-array-this/task.md
@@ -11,7 +11,7 @@ let arr = ["a", "b"];
arr.push(function() {
alert( this );
-})
+});
arr[2](); // ?
```
diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md
index 33498f40a..629a7c77f 100644
--- a/1-js/05-data-types/04-array/article.md
+++ b/1-js/05-data-types/04-array/article.md
@@ -92,6 +92,38 @@ let fruits = [
The "trailing comma" style makes it easier to insert/remove items, because all lines become alike.
````
+## Get last elements with "at"
+
+[recent browser="new"]
+
+Let's say we want the last element of the array.
+
+Some programming languages allow the use of negative indexes for the same purpose, like `fruits[-1]`.
+
+However, in JavaScript it won't work. The result will be `undefined`, because the index in square brackets is treated literally.
+
+We can explicitly calculate the last element index and then access it: `fruits[fruits.length - 1]`.
+
+```js run
+let fruits = ["Apple", "Orange", "Plum"];
+
+alert( fruits[fruits.length-1] ); // Plum
+```
+
+A bit cumbersome, isn't it? We need to write the variable name twice.
+
+Luckily, there's a shorter syntax: `fruits.at(-1)`:
+
+```js run
+let fruits = ["Apple", "Orange", "Plum"];
+
+// same as fruits[fruits.length-1]
+alert( fruits.at(-1) ); // Plum
+```
+
+In other words, `arr.at(i)`:
+- is exactly the same as `arr[i]`, if `i >= 0`.
+- for negative values of `i`, it steps back from the end of the array.
## Methods pop/push, shift/unshift
@@ -121,9 +153,9 @@ A stack is usually illustrated as a pack of cards: new cards are added to the to
For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out).
-Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end.
+Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements, both to/from the beginning or the end.
-In computer science the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
+In computer science, the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
**Methods that work with the end of the array:**
@@ -138,6 +170,8 @@ In computer science the data structure that allows this, is called [deque](https
alert( fruits ); // Apple, Orange
```
+ Both `fruits.pop()` and `fruits.at(-1)` return the last element of the array, but `fruits.pop()` also modifies the array by removing it.
+
`push`
: Append the element to the end of the array:
@@ -193,7 +227,7 @@ An array is a special kind of object. The square brackets used to access a prope
They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object.
-Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object.
+Remember, there are only eight basic data types in JavaScript (see the [Data types](info:types) chapter for more info). Array is an object and thus behaves like an object.
For instance, it is copied by reference:
@@ -209,7 +243,7 @@ arr.push("Pear"); // modify the array by reference
alert( fruits ); // Banana, Pear - 2 items now
```
-...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast.
+...But what makes arrays really special is their internal representation. The engine tries to store its elements in the contiguous memory area, one after another, just as depicted on the illustrations in this chapter, and there are other optimizations as well, to make arrays work really fast.
But they all break if we quit working with an array as with an "ordered collection" and start working with it as if it were a regular object.
@@ -247,7 +281,7 @@ Why is it faster to work with the end of an array than with its beginning? Let's
fruits.shift(); // take 1 element from the start
```
-It's not enough to take and remove the element with the number `0`. Other elements need to be renumbered as well.
+It's not enough to take and remove the element with the index `0`. Other elements need to be renumbered as well.
The `shift` operation must do 3 things:
@@ -365,11 +399,11 @@ There is one more syntax to create an array:
let arr = *!*new Array*/!*("Apple", "Pear", "etc");
```
-It's rarely used, because square brackets `[]` are shorter. Also there's a tricky feature with it.
+It's rarely used, because square brackets `[]` are shorter. Also, there's a tricky feature with it.
If `new Array` is called with a single argument which is a number, then it creates an array *without items, but with the given length*.
-Let's see how one can shoot themself in the foot:
+Let's see how one can shoot themselves in the foot:
```js run
let arr = new Array(2); // will it create an array of [2] ?
@@ -379,9 +413,7 @@ alert( arr[0] ); // undefined! no elements.
alert( arr.length ); // length 2
```
-In the code above, `new Array(number)` has all elements `undefined`.
-
-To evade such surprises, we usually use square brackets, unless we really know what we're doing.
+To avoid such surprises, we usually use square brackets, unless we really know what we're doing.
## Multidimensional arrays
@@ -394,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
@@ -429,25 +461,77 @@ alert( "1" + 1 ); // "11"
alert( "1,2" + 1 ); // "1,21"
```
+## Don't compare arrays with ==
+
+Arrays in JavaScript, unlike some other programming languages, shouldn't be compared with operator `==`.
+
+This operator has no special treatment for arrays, it works with them as with any objects.
+
+Let's recall the rules:
+
+- Two objects are equal `==` only if they're references to the same object.
+- If one of the arguments of `==` is an object, and the other one is a primitive, then the object gets converted to primitive, as explained in the chapter .
+- ...With an exception of `null` and `undefined` that equal `==` each other and nothing else.
+
+The strict comparison `===` is even simpler, as it doesn't convert types.
+
+So, if we compare arrays with `==`, they are never the same, unless we compare two variables that reference exactly the same array.
+
+For example:
+```js run
+alert( [] == [] ); // false
+alert( [0] == [0] ); // false
+```
+
+These arrays are technically different objects. So they aren't equal. The `==` operator doesn't do item-by-item comparison.
+
+Comparison with primitives may give seemingly strange results as well:
+
+```js run
+alert( 0 == [] ); // true
+
+alert('0' == [] ); // false
+```
+
+Here, in both cases, we compare a primitive with an array object. So the array `[]` gets converted to primitive for the purpose of comparison and becomes an empty string `''`.
+
+Then the comparison process goes on with the primitives, as described in the chapter :
+
+```js run
+// after [] was converted to ''
+alert( 0 == '' ); // true, as '' becomes converted to number 0
+
+alert('0' == '' ); // false, no type conversion, different strings
+```
+
+So, how to compare arrays?
+
+That's simple: don't use the `==` operator. Instead, compare them item-by-item in a loop or using iteration methods explained in the next chapter.
+
## Summary
Array is a special kind of object, suited to storing and managing ordered data items.
-- The declaration:
+The declaration:
- ```js
- // square brackets (usual)
- let arr = [item1, item2...];
+```js
+// square brackets (usual)
+let arr = [item1, item2...];
- // new Array (exceptionally rare)
- let arr = new Array(item1, item2...);
- ```
+// new Array (exceptionally rare)
+let arr = new Array(item1, item2...);
+```
- The call to `new Array(number)` creates an array with the given length, but without elements.
+The call to `new Array(number)` creates an array with the given length, but without elements.
- The `length` property is the array length or, to be precise, its last numeric index plus one. It is auto-adjusted by array methods.
- If we shorten `length` manually, the array is truncated.
+Getting the elements:
+
+- we can get element by its index, like `arr[0]`
+- also we can use `at(i)` method that allows negative indexes. For negative values of `i`, it steps back from the end of the array. If `i >= 0`, it works same as `arr[i]`.
+
We can use an array as a deque with the following operations:
- `push(...items)` adds `items` to the end.
@@ -460,4 +544,8 @@ To loop over the elements of the array:
- `for (let item of arr)` -- the modern syntax for items only,
- `for (let i in arr)` -- never use.
-We will return to arrays and study more methods to add, remove, extract elements and sort arrays in the chapter .
+To compare arrays, don't use the `==` operator (as well as `>`, `<` and others), as they have no special treatment for arrays. They handle them as any objects, and it's not what we usually want.
+
+Instead you can use `for..of` loop to compare arrays item-by-item.
+
+We will continue with arrays and study more methods to add, remove, extract elements and sort arrays in the next chapter .
diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md
index d3c8f8eb1..7f0082357 100644
--- a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md
+++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md
@@ -4,7 +4,7 @@ importance: 4
# Create keyed object from array
-Let's say we received an array of users in the form `{id:..., name:..., age... }`.
+Let's say we received an array of users in the form `{id:..., name:..., age:... }`.
Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values.
diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/task.md b/1-js/05-data-types/05-array-methods/2-filter-range/task.md
index 18b2c1d9b..46e47c93d 100644
--- a/1-js/05-data-types/05-array-methods/2-filter-range/task.md
+++ b/1-js/05-data-types/05-array-methods/2-filter-range/task.md
@@ -4,7 +4,7 @@ importance: 4
# Filter range
-Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements between `a` and `b` in it and returns an array of them.
+Write a function `filterRange(arr, a, b)` that gets an array `arr`, looks for elements with values higher or equal to `a` and lower or equal to `b` and return a result as an array.
The function should not modify the array. It should return the new array.
diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js
index db32d9a11..241b74c6e 100644
--- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js
+++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js
@@ -4,13 +4,13 @@ describe("filterRangeInPlace", function() {
let arr = [5, 3, 8, 1];
- filterRangeInPlace(arr, 1, 4);
+ filterRangeInPlace(arr, 2, 5);
- assert.deepEqual(arr, [3, 1]);
+ assert.deepEqual(arr, [5, 3]);
});
it("doesn't return anything", function() {
assert.isUndefined(filterRangeInPlace([1,2,3], 1, 4));
});
-});
\ No newline at end of file
+});
diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js
index 45ef1619d..f62452a5f 100644
--- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js
+++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js
@@ -10,14 +10,14 @@ function Calculator() {
let split = str.split(' '),
a = +split[0],
op = split[1],
- b = +split[2]
+ b = +split[2];
if (!this.methods[op] || isNaN(a) || isNaN(b)) {
return NaN;
}
return this.methods[op](a, b);
- }
+ };
this.addMethod = function(name, func) {
this.methods[name] = func;
diff --git a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md
index 5d8bf4a13..2d8d4fb0e 100644
--- a/1-js/05-data-types/05-array-methods/7-map-objects/solution.md
+++ b/1-js/05-data-types/05-array-methods/7-map-objects/solution.md
@@ -25,7 +25,7 @@ alert( usersMapped[0].id ); // 1
alert( usersMapped[0].fullName ); // John Smith
```
-Please note that in for the arrow functions we need to use additional brackets.
+Please note that in the arrow functions we need to use additional brackets.
We can't write like this:
```js
diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md
index 9f1ade707..cfaf9761a 100644
--- a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md
+++ b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md
@@ -1,6 +1,6 @@
```js run no-beautify
function sortByAge(arr) {
- arr.sort((a, b) => a.age > b.age ? 1 : -1);
+ arr.sort((a, b) => a.age - b.age);
}
let john = { name: "John", age: 25 };
diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md
index 301696440..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,19 +32,19 @@ alert( arr.length ); // 3
The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`.
-That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now.
+That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now.
So, special methods should be used.
-The [arr.splice(start)](mdn:js/Array/splice) method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements.
+The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements.
The syntax is:
```js
-arr.splice(index[, deleteCount, elem1, ..., elemN])
+arr.splice(start[, deleteCount, elem1, ..., elemN])
```
-It starts from the position `index`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements.
+It modifies `arr` starting from the index `start`: removes `deleteCount` elements and then inserts `elem1, ..., elemN` at their place. Returns the array of removed elements.
This method is easy to grasp by examples.
@@ -62,7 +62,7 @@ alert( arr ); // ["I", "JavaScript"]
Easy, right? Starting from the index `1` it removed `1` element.
-In the next example we remove 3 elements and replace them with the other two:
+In the next example, we remove 3 elements and replace them with the other two:
```js run
let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"];
@@ -84,7 +84,7 @@ let removed = arr.splice(0, 2);
alert( removed ); // "I", "study" <-- array of removed elements
```
-The `splice` method is also able to insert the elements without any removals. For that we need to set `deleteCount` to `0`:
+The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`:
```js run
let arr = ["I", "study", "JavaScript"];
@@ -114,7 +114,7 @@ alert( arr ); // 1,2,3,4,5
### slice
-The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking `arr.splice`.
+The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`.
The syntax is:
@@ -124,7 +124,7 @@ arr.slice([start], [end])
It returns a new array copying to it all items from index `start` to `end` (not including `end`). Both `start` and `end` can be negative, in that case position from array end is assumed.
-It's similar to a string method `str.slice`, but instead of substrings it makes subarrays.
+It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays.
For instance:
@@ -206,7 +206,7 @@ The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for ever
The syntax:
```js
arr.forEach(function(item, index, array) {
- // ... do something with item
+ // ... do something with an item
});
```
@@ -234,12 +234,13 @@ Now let's cover methods that search in an array.
### indexOf/lastIndexOf and includes
-The methods [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) and [arr.includes](mdn:js/Array/includes) have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters:
+The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/includes) have the similar syntax and do essentially the same as their string counterparts, but operate on items instead of characters:
- `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`.
-- `arr.lastIndexOf(item, from)` -- same, but looks for from right to left.
- `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found.
+Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning.
+
For instance:
```js run
@@ -252,21 +253,33 @@ alert( arr.indexOf(null) ); // -1
alert( arr.includes(1) ); // true
```
-Note that the methods use `===` comparison. So, if we look for `false`, it finds exactly `false` and not the zero.
+Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero.
-If we want to check for inclusion, and don't want to know the exact index, then `arr.includes` is preferred.
+If we want to check if `item` exists in the array and don't need the index, then `arr.includes` is preferred.
-Also, a very minor difference of `includes` is that it correctly handles `NaN`, unlike `indexOf/lastIndexOf`:
+The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left.
+
+```js run
+let fruits = ['Apple', 'Orange', 'Apple']
+
+alert( fruits.indexOf('Apple') ); // 0 (first Apple)
+alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple)
+```
+
+````smart header="The `includes` method handles `NaN` correctly"
+A minor, but noteworthy feature of `includes` is that it correctly handles `NaN`, unlike `indexOf`:
```js run
const arr = [NaN];
-alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN)
+alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0)
alert( arr.includes(NaN) );// true (correct)
```
+That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally.
+````
-### find and findIndex
+### find and findIndex/findLastIndex
-Imagine we have an array of objects. How do we find an object with the specific condition?
+Imagine we have an array of objects. How do we find an object with a specific condition?
Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy.
@@ -284,7 +297,7 @@ The function is called for elements of the array, one after another:
- `index` is its index.
- `array` is the array itself.
-If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned.
+If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned.
For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`:
@@ -300,11 +313,30 @@ let user = users.find(item => item.id == 1);
alert(user.name); // John
```
-In real life arrays of objects is a common thing, so the `find` method is very useful.
+In real life, arrays of objects are a common thing, so the `find` method is very useful.
Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. That's typical, other arguments of this function are rarely used.
-The [arr.findIndex](mdn:js/Array/findIndex) method is essentially the same, but it returns the index where the element was found instead of the element itself and `-1` is returned when nothing is found.
+The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found.
+
+The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`.
+
+Here's an example:
+
+```js run
+let users = [
+ {id: 1, name: "John"},
+ {id: 2, name: "Pete"},
+ {id: 3, name: "Mary"},
+ {id: 4, name: "John"}
+];
+
+// Find the index of the first John
+alert(users.findIndex(user => user.name == 'John')); // 0
+
+// Find the index of the last John
+alert(users.findLastIndex(user => user.name == 'John')); // 3
+```
### filter
@@ -389,6 +421,7 @@ Literally, all elements are converted to strings for comparisons. For strings, l
To use our own sorting order, we need to supply a function as the argument of `arr.sort()`.
The function should compare two arbitrary values and return:
+
```js
function compare(a, b) {
if (a > b) return 1; // if the first value is greater than the second
@@ -417,15 +450,16 @@ alert(arr); // *!*1, 2, 15*/!*
Now it works as intended.
-Let's step aside and think what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order.
+Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order.
-The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison.
+The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison.
-By the way, if we ever want to know which elements are compared -- nothing prevents from alerting them:
+By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them:
```js run
[1, -2, 15, 2, 0, 8].sort(function(a, b) {
alert( a + " <> " + b );
+ return a - b;
});
```
@@ -492,7 +526,7 @@ Here's the situation from real life. We are writing a messaging app, and the per
The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`.
-In the example below, we split by a comma followed by space:
+In the example below, we split by a comma followed by a space:
```js run
let names = 'Bilbo, Gandalf, Nazgul';
@@ -559,9 +593,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?
@@ -630,8 +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
@@ -641,7 +674,7 @@ So `typeof` does not help to distinguish a plain object from an array:
```js run
alert(typeof {}); // object
-alert(typeof []); // same
+alert(typeof []); // object (same)
```
...But arrays are used so often that there's a special method for that: [Array.isArray(value)](mdn:js/Array/isArray). It returns `true` if the `value` is an array, and `false` otherwise.
@@ -656,7 +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:
@@ -700,7 +733,7 @@ alert(soldiers[1].age); // 23
If in the example above we used `users.filter(army.canJoin)`, then `army.canJoin` would be called as a standalone function, with `this=undefined`, thus leading to an instant error.
-A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The former is used more often, as it's a bit easier to understand for most people.
+A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The latter is used more often, as it's a bit easier to understand for most people.
## Summary
@@ -711,12 +744,12 @@ A cheat sheet of array methods:
- `pop()` -- extracts an item from the end,
- `shift()` -- extracts an item from the beginning,
- `unshift(...items)` -- adds items to the beginning.
- - `splice(pos, deleteCount, ...items)` -- at index `pos` delete `deleteCount` elements and insert `items`.
- - `slice(start, end)` -- creates a new array, copies elements from position `start` till `end` (not inclusive) into it.
+ - `splice(pos, deleteCount, ...items)` -- at index `pos` deletes `deleteCount` elements and inserts `items`.
+ - `slice(start, end)` -- creates a new array, copies elements from index `start` till `end` (not inclusive) into it.
- `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken.
- To search among elements:
- - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, return the index or `-1` if not found.
+ - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found.
- `includes(value)` -- returns `true` if the array has `value`, otherwise `false`.
- `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`.
- `findIndex` is like `find`, but returns the index instead of a value.
@@ -729,26 +762,40 @@ A cheat sheet of array methods:
- `sort(func)` -- sorts the array in-place, then returns it.
- `reverse()` -- reverses the array in-place, then returns it.
- `split/join` -- convert a string to array and back.
- - `reduce(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls.
+ - `reduce/reduceRight(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls.
- Additionally:
- - `Array.isArray(arr)` checks `arr` for being an array.
+ - `Array.isArray(value)` checks `value` for being an array, if so returns `true`, otherwise `false`.
Please note that methods `sort`, `reverse` and `splice` modify the array itself.
These methods are the most used ones, they cover 99% of use cases. But there are few others:
-- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) checks the array.
+- [arr.some(fn)](mdn:js/Array/some)/[arr.every(fn)](mdn:js/Array/every) check the array.
The function `fn` is called on each element of the array similar to `map`. If any/all results are `true`, returns `true`, otherwise `false`.
+ These methods behave sort of like `||` and `&&` operators: if `fn` returns a truthy value, `arr.some()` immediately returns `true` and stops iterating over the rest of items; if `fn` returns a falsy value, `arr.every()` immediately returns `false` and stops iterating over the rest of items as well.
+
+ We can use `every` to compare arrays:
+
+ ```js run
+ function arraysEqual(arr1, arr2) {
+ return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
+ }
+
+ alert( arraysEqual([1, 2], [1, 2])); // true
+ ```
+
- [arr.fill(value, start, end)](mdn:js/Array/fill) -- fills the array with repeating `value` from index `start` to `end`.
- [arr.copyWithin(target, start, end)](mdn:js/Array/copyWithin) -- copies its elements from position `start` till position `end` into *itself*, at position `target` (overwrites existing).
+- [arr.flat(depth)](mdn:js/Array/flat)/[arr.flatMap(fn)](mdn:js/Array/flatMap) create a new flat array from a multidimensional array.
+
For the full list, see the [manual](mdn:js/Array).
-From the first sight it may seem that there are so many methods, quite difficult to remember. But actually that's much easier.
+At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier.
Look through the cheat sheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods.
diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md
index 8a38516e1..e2c0d4f97 100644
--- a/1-js/05-data-types/06-iterable/article.md
+++ b/1-js/05-data-types/06-iterable/article.md
@@ -1,7 +1,7 @@
# Iterables
-*Iterable* objects is a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop.
+*Iterable* objects are a generalization of arrays. That's a concept that allows us to make any object useable in a `for..of` loop.
Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable.
@@ -26,12 +26,12 @@ let range = {
// for(let num of range) ... num=1,2,3,4,5
```
-To make the `range` iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that).
+To make the `range` object iterable (and thus let `for..of` work) we need to add a method to the object named `Symbol.iterator` (a special built-in symbol just for that).
1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`.
2. Onward, `for..of` works *only with that returned object*.
3. When `for..of` wants the next value, it calls `next()` on that object.
-4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` is the next value.
+4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the loop is finished, otherwise `value` is the next value.
Here's the full implementation for `range` with remarks:
@@ -45,10 +45,10 @@ let range = {
range[Symbol.iterator] = function() {
// ...it returns the iterator object:
- // 2. Onward, for..of works only with this iterator, asking it for next values
+ // 2. Onward, for..of works only with the iterator object below, asking it for next values
return {
current: this.from,
- last: this.to,
+ last: this.to,
// 3. next() is called on each iteration by the for..of loop
next() {
@@ -140,7 +140,7 @@ for (let char of str) {
## Calling an iterator explicitly
-For deeper understanding let's see how to use an iterator explicitly.
+For deeper understanding, let's see how to use an iterator explicitly.
We'll iterate over a string in exactly the same way as `for..of`, but with direct calls. This code creates a string iterator and gets values from it "manually":
@@ -165,16 +165,16 @@ That is rarely needed, but gives us more control over the process than `for..of`
## Iterables and array-likes [#array-like]
-There are two official terms that look similar, but are very different. Please make sure you understand them well to avoid the confusion.
+Two official terms look similar, but are very different. Please make sure you understand them well to avoid the confusion.
- *Iterables* are objects that implement the `Symbol.iterator` method, as described above.
- *Array-likes* are objects that have indexes and `length`, so they look like arrays.
-When we use JavaScript for practical tasks in browser or other environments, we may meet objects that are iterables or array-likes, or both.
+When we use JavaScript for practical tasks in a browser or any other environment, we may meet objects that are iterables or array-likes, or both.
For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`).
-But an iterable may be not array-like. And vice versa an array-like may be not iterable.
+But an iterable may not be array-like. And vice versa an array-like may not be iterable.
For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`.
@@ -218,7 +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
@@ -270,7 +270,7 @@ for (let char of str) {
alert(chars);
```
-...But it is shorter.
+...But it is shorter.
We can even build surrogate-aware `slice` on it:
@@ -293,7 +293,7 @@ alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs)
Objects that can be used in `for..of` are called *iterable*.
- Technically, iterables must implement the method named `Symbol.iterator`.
- - The result of `obj[Symbol.iterator]` is called an *iterator*. It handles the further iteration process.
+ - The result of `obj[Symbol.iterator]()` is called an *iterator*. It handles further iteration process.
- An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value.
- The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly.
- Built-in iterables like strings or arrays, also implement `Symbol.iterator`.
@@ -304,4 +304,4 @@ Objects that have indexed properties and `length` are called *array-like*. Such
If we look inside the specification -- we'll see that most built-in methods assume that they work with iterables or array-likes instead of "real" arrays, because that's more abstract.
-`Array.from(obj[, mapFn, thisArg])` makes a real `Array` of an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item.
+`Array.from(obj[, mapFn, thisArg])` makes a real `Array` from an iterable or array-like `obj`, and we can then use array methods on it. The optional arguments `mapFn` and `thisArg` allow us to apply a function to each item.
diff --git a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md
index 25c74bfc2..81507647f 100644
--- a/1-js/05-data-types/07-map-set/03-iterable-keys/task.md
+++ b/1-js/05-data-types/07-map-set/03-iterable-keys/task.md
@@ -4,7 +4,7 @@ importance: 5
# Iterable keys
-We'd like to get an array of `map.keys()` in a variable and then do apply array-specific methods to it, e.g. `.push`.
+We'd like to get an array of `map.keys()` in a variable and then apply array-specific methods to it, e.g. `.push`.
But that doesn't work:
diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md
index e08c84084..37f5e48c2 100644
--- a/1-js/05-data-types/07-map-set/article.md
+++ b/1-js/05-data-types/07-map-set/article.md
@@ -1,26 +1,26 @@
# Map and Set
-Now we've learned about the following complex data structures:
+Till now, we've learned about the following complex data structures:
-- Objects for storing keyed collections.
-- Arrays for storing ordered collections.
+- Objects are used for storing keyed collections.
+- Arrays are used for storing ordered collections.
But that's not enough for real life. That's why `Map` and `Set` also exist.
## Map
-[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type.
+[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type.
Methods and properties are:
-- `new Map()` -- creates the map.
-- `map.set(key, value)` -- stores the value by the key.
-- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map.
-- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise.
-- `map.delete(key)` -- removes the value by the key.
-- `map.clear()` -- removes everything from the map.
-- `map.size` -- returns the current element count.
+- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map.
+- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key.
+- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map.
+- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise.
+- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element (the key/value pair) by the key.
+- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map.
+- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count.
For instance:
@@ -42,7 +42,7 @@ alert( map.size ); // 3
As we can see, unlike objects, keys are not converted to strings. Any type of key is possible.
```smart header="`map[key]` isn't the right way to use a `Map`"
-Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (no object keys and so on).
+Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (only string/symbol keys and so on).
So we should use `map` methods: `set`, `get` and so on.
```
@@ -63,24 +63,26 @@ visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
```
-Using objects as keys is one of most notable and important `Map` features. For string keys, `Object` can be fine, but not for object keys.
+Using objects as keys is one of the most notable and important `Map` features. The same does not count for `Object`. String as a key in `Object` is fine, but we can't use another `Object` as a key in `Object`.
Let's try:
```js run
let john = { name: "John" };
+let ben = { name: "Ben" };
let visitsCountObj = {}; // try to use an object
-visitsCountObj[john] = 123; // try to use john object as the key
+visitsCountObj[ben] = 234; // try to use ben object as the key
+visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced
*!*
// That's what got written!
-alert( visitsCountObj["[object Object]"] ); // 123
+alert( visitsCountObj["[object Object]"] ); // 123
*/!*
```
-As `visitsCountObj` is an object, it converts all keys, such as `john` to strings, so we've got the string key `"[object Object]"`. Definitely not what we want.
+As `visitsCountObj` is an object, it converts all `Object` keys, such as `john` and `ben` above, to same string `"[object Object]"`. Definitely not what we want.
```smart header="How `Map` compares keys"
To test keys for equivalence, `Map` uses the algorithm [SameValueZero](https://tc39.github.io/ecma262/#sec-samevaluezero). It is roughly the same as strict equality `===`, but the difference is that `NaN` is considered equal to `NaN`. So `NaN` can be used as the key as well.
@@ -98,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:
@@ -160,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:
@@ -231,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.
@@ -270,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
@@ -289,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.
-- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map.
-- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise.
-- `map.delete(key)` -- removes the value by the key.
-- `map.clear()` -- removes everything from the map.
-- `map.size` -- returns the current element count.
+- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization.
+- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key, returns the map itself.
+- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map.
+- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise.
+- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`.
+- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map.
+- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count.
The differences from a regular `Object`:
- Any keys, objects can be keys.
- Additional convenient methods, the `size` property.
-`Set` -- is a collection of unique values.
+[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- is a collection of unique values.
Methods and properties:
-- `new Set([iterable])` -- creates the set, with optional `iterable` (e.g. array) of values for initialization.
-- `set.add(value)` -- adds a value (does nothing if `value` exists), returns the set itself.
-- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`.
-- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`.
-- `set.clear()` -- removes everything from the set.
-- `set.size` -- is the elements count.
+- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, with optional `iterable` (e.g. array) of values for initialization.
+- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value (does nothing if `value` exists), returns the set itself.
+- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`.
+- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`.
+- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set.
+- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count.
Iteration over `Map` and `Set` is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number.
diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md
index 6a4c20baf..e2147ccfa 100644
--- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md
+++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md
@@ -25,7 +25,7 @@ messages.shift();
// now readMessages has 1 element (technically memory may be cleaned later)
```
-The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it.
+The `WeakSet` allows to store a set of messages and easily check for the existence of a message in it.
It cleans up itself automatically. The tradeoff is that we can't iterate over it, can't get "all read messages" from it directly. But we can do it by iterating over all messages and filtering those that are in the set.
diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md
index bcc5e5e6f..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 stores a value in memory while it is reachable (and can potentially be used).
+As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used.
For instance:
+
```js
let john = { name: "John" };
@@ -30,7 +32,8 @@ let array = [ john ];
john = null; // overwrite the reference
*!*
-// john is stored inside the array, so it won't be garbage-collected
+// the object previously referenced by john is stored inside the array
+// therefore it won't be garbage-collected
// we can get it as array[0]
*/!*
```
@@ -53,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 from `Map` is that `WeakMap` 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();
@@ -93,16 +96,16 @@ 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*.
-The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported.
+The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically, the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported.
-Now where do we need such data structure?
+Now, where do we need such a data structure?
## Use case: additional data
@@ -146,7 +149,7 @@ countUser(john); // count his visits
john = null;
```
-Now `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`.
+Now, `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`.
We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures.
@@ -163,13 +166,13 @@ function countUser(user) {
}
```
-Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`.
+Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable, by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`.
## Use case: caching
-Another common example is caching: when a function result should be remembered ("cached"), so that future calls on the same object reuse it.
+Another common example is caching. We can store ("cache") results from a function, so that future calls on the same object can reuse it.
-We can use `Map` to store results, like this:
+To achieve that, we can use `Map` (not optimal scenario):
```js run
// ð cache.js
@@ -181,6 +184,7 @@ function process(obj) {
let result = /* calculations of the result for */ obj;
cache.set(obj, result);
+ return result;
}
return cache.get(obj);
@@ -206,7 +210,7 @@ alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!)
For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more.
-If we replace `Map` with `WeakMap`, then this problem disappears: the cached result will be removed from memory automatically after the object gets garbage collected.
+If we replace `Map` with `WeakMap`, then this problem disappears. The cached result will be removed from memory automatically after the object gets garbage collected.
```js run
// ð cache.js
@@ -220,6 +224,7 @@ function process(obj) {
let result = /* calculate the result for */ obj;
cache.set(obj, result);
+ return result;
}
return cache.get(obj);
@@ -241,13 +246,13 @@ 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 an additional storage. But not for an arbitrary data, but rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object.
+Being "weak", it also serves as additional storage. But not for arbitrary data, rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object.
For instance, we can add users to `WeakSet` to keep track of those who visited our site:
@@ -275,14 +280,16 @@ john = null;
// visitedSet will be cleaned automatically
```
-The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place.
+The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and the inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place.
## Summary
-`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`](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.
-`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.
-Both of them do not support methods and properties that refer to all keys or their count. Only individual operations are allowed.
+That comes at the cost of not having support for `clear`, `size`, `keys`, `values`...
-`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically.
+`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "primary" object storage. Once the object is removed from the primary storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically.
diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md
index b633dc274..bef678f53 100644
--- a/1-js/05-data-types/09-keys-values-entries/article.md
+++ b/1-js/05-data-types/09-keys-values-entries/article.md
@@ -77,7 +77,7 @@ Objects lack many methods that exist for arrays, e.g. `map`, `filter` and others
If we'd like to apply them, then we can use `Object.entries` followed by `Object.fromEntries`:
1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`.
-2. Use array methods on that array, e.g. `map`.
+2. Use array methods on that array, e.g. `map`, to transform these key/value pairs.
3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object.
For example, we have an object with prices, and would like to double them:
@@ -91,12 +91,13 @@ let prices = {
*!*
let doublePrices = Object.fromEntries(
- // convert to array, map, and then fromEntries gives back the object
- Object.entries(prices).map(([key, value]) => [key, value * 2])
+ // convert prices to array, map each key/value pair into another pair
+ // and then fromEntries gives back the object
+ Object.entries(prices).map(entry => [entry[0], entry[1] * 2])
);
*/!*
alert(doublePrices.meat); // 8
-```
+```
-It may look difficult from the first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way.
+It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way.
diff --git a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js
index f4bd5c761..6538af42b 100644
--- a/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js
+++ b/1-js/05-data-types/10-destructuring-assignment/6-max-salary/_js.view/solution.js
@@ -1,16 +1,14 @@
function topSalary(salaries) {
- let max = 0;
+ let maxSalary = 0;
let maxName = null;
for(const [name, salary] of Object.entries(salaries)) {
- if (max < salary) {
- max = salary;
+ if (maxSalary < salary) {
+ maxSalary = salary;
maxName = name;
}
}
return maxName;
-}
-
-
+}
\ No newline at end of file
diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md
index 46aa760a9..c8693b2c4 100644
--- a/1-js/05-data-types/10-destructuring-assignment/article.md
+++ b/1-js/05-data-types/10-destructuring-assignment/article.md
@@ -2,19 +2,22 @@
The two most used data structures in JavaScript are `Object` and `Array`.
-Objects allow us to create a single entity that stores data items by key, and arrays allow us to gather data items into an ordered collection.
+- Objects allow us to create a single entity that stores data items by key.
+- Arrays allow us to gather data items into an ordered list.
-But when we pass those to a function, it may need not an object/array as a whole, but rather individual pieces.
+However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties.
-*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. Destructuring also works great with complex functions that have a lot of parameters, default values, and so on.
+*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient.
+
+Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that.
## Array destructuring
-An example of how the array is destructured into variables:
+Here's an example of how an array is destructured into variables:
```js
-// we have an array with the name and surname
-let arr = ["Ilya", "Kantor"]
+// we have an array with a name and surname
+let arr = ["John", "Smith"]
*!*
// destructuring assignment
@@ -23,20 +26,24 @@ let arr = ["Ilya", "Kantor"]
let [firstName, surname] = arr;
*/!*
-alert(firstName); // Ilya
-alert(surname); // Kantor
+alert(firstName); // John
+alert(surname); // Smith
```
Now we can work with variables instead of array members.
It looks great when combined with `split` or other array-returning methods:
-```js
-let [firstName, surname] = "Ilya Kantor".split(' ');
+```js run
+let [firstName, surname] = "John Smith".split(' ');
+alert(firstName); // John
+alert(surname); // Smith
```
+As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better.
+
````smart header="\"Destructuring\" does not mean \"destructive\"."
-It's called "destructuring assignment," because it "destructurizes" by copying items into variables. But the array itself is not modified.
+It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified.
It's just a shorter way to write:
```js
@@ -58,7 +65,7 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic
alert( title ); // Consul
```
-In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items is also skipped (as there are no variables for them).
+In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them).
````
````smart header="Works with any iterable on the right-side"
@@ -69,29 +76,28 @@ In the code above, the second element of the array is skipped, the third one is
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
```
-
+That works, because internally a destructuring assignment works by iterating over the right value. It's a kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values.
````
-````smart header="Assign to anything at the left-side"
-
-We can use any "assignables" at the left side.
+````smart header="Assign to anything on the left-side"
+We can use any "assignables" on the left side.
For instance, an object property:
```js run
let user = {};
-[user.name, user.surname] = "Ilya Kantor".split(' ');
+[user.name, user.surname] = "John Smith".split(' ');
-alert(user.name); // Ilya
+alert(user.name); // John
+alert(user.surname); // Smith
```
````
````smart header="Looping with .entries()"
+In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method.
-In the previous chapter we saw the [Object.entries(obj)](mdn:js/Object/entries) method.
-
-We can use it with destructuring to loop over keys-and-values of an object:
+We can use it with destructuring to loop over the keys-and-values of an object:
```js run
let user = {
@@ -99,7 +105,7 @@ let user = {
age: 30
};
-// loop over keys-and-values
+// loop over the keys-and-values
*!*
for (let [key, value] of Object.entries(user)) {
*/!*
@@ -107,7 +113,7 @@ for (let [key, value] of Object.entries(user)) {
}
```
-...And the same for a map:
+The similar code for a `Map` is simpler, as it's iterable:
```js run
let user = new Map();
@@ -115,6 +121,7 @@ user.set("name", "John");
user.set("age", "30");
*!*
+// Map iterates as [key, value] pairs, very convenient for destructuring
for (let [key, value] of user) {
*/!*
alert(`${key}:${value}`); // name:John, then age:30
@@ -122,15 +129,17 @@ for (let [key, value] of user) {
```
````
-```smart header="Swap variables trick"
-A well-known trick for swapping values of two variables:
+````smart header="Swap variables trick"
+There's a well-known trick for swapping values of two variables using a destructuring assignment:
```js run
let guest = "Jane";
let admin = "Pete";
-// Swap values: make guest=Pete, admin=Jane
+// Let's swap the values: make guest=Pete, admin=Jane
+*!*
[guest, admin] = [admin, guest];
+*/!*
alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!)
```
@@ -138,31 +147,47 @@ alert(`${guest} ${admin}`); // Pete Jane (successfully swapped!)
Here we create a temporary array of two variables and immediately destructure it in swapped order.
We can swap more than two variables this way.
-
+````
### The rest '...'
-If we want not just to get first values, but also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`:
+Usually, if the array is longer than the list at the left, the "extra" items are omitted.
+
+For example, here only two items are taken, and the rest is just ignored:
```js run
-let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*];
+let [name1, name2] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
+// Further items aren't assigned anywhere
+```
+
+If we'd like also to gather all that follows -- we can add one more parameter that gets "the rest" using three dots `"..."`:
+
+```js run
+let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*];
*!*
-// Note that type of `rest` is Array.
+// rest is an array of items, starting from the 3rd one
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
*/!*
```
-The value of `rest` is the array of the remaining array elements. We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment.
+The value of `rest` is the array of the remaining array elements.
+
+We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment.
+
+```js run
+let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
+// now titles = ["Consul", "of the Roman Republic"]
+```
### Default values
-If there are fewer values in the array than variables in the assignment, there will be no error. Absent values are considered undefined:
+If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined:
```js run
*!*
@@ -187,7 +212,7 @@ alert(surname); // Anonymous (default used)
Default values can be more complex expressions or even function calls. They are evaluated only if the value is not provided.
-For instance, here we use the `prompt` function for two defaults. But it will run only for the missing one:
+For instance, here we use the `prompt` function for two defaults:
```js run
// runs only prompt for surname
@@ -197,7 +222,7 @@ alert(name); // Julius (from array)
alert(surname); // whatever prompt gets
```
-
+Please note: the `prompt` will run only for the missing value (`surname`).
## Object destructuring
@@ -209,7 +234,7 @@ The basic syntax is:
let {var1, var2} = {var1:â¦, var2:â¦}
```
-We have an existing object at the right side, that we want to split into variables. The left side contains a "pattern" for corresponding properties. In the simple case, that's a list of variable names in `{...}`.
+We should have an existing object on the right side, that we want to split into variables. The left side contains an object-like "pattern" for corresponding properties. In the simplest case, that's a list of variable names in `{...}`.
For instance:
@@ -229,7 +254,9 @@ alert(width); // 100
alert(height); // 200
```
-Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables. The order does not matter. This works too:
+Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables.
+
+The order does not matter. This works too:
```js
// changed the order in let {...}
@@ -238,7 +265,7 @@ let {height, width, title} = { title: "Menu", height: 200, width: 100 }
The pattern on the left side may be more complex and specify the mapping between properties and variables.
-If we want to assign a property to a variable with another name, for instance, `options.width` to go into the variable named `w`, then we can set it using a colon:
+If we want to assign a property to a variable with another name, for instance, make `options.width` go into the variable named `w`, then we can set the variable name using a colon:
```js run
let options = {
@@ -391,9 +418,9 @@ alert( title ); // Menu
## Nested destructuring
-If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions.
+If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions.
-In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure to extract values from them:
+In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern on the left side of the assignment has the same structure to extract values from them:
```js run
let options = {
@@ -402,7 +429,7 @@ let options = {
height: 200
},
items: ["Cake", "Donut"],
- extra: true
+ extra: true
};
// destructuring assignment split in multiple lines for clarity
@@ -422,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:

@@ -432,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 = []) {
@@ -442,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?
@@ -507,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:
@@ -534,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.
@@ -544,9 +571,9 @@ In the code above, the whole arguments object is `{}` by default, so there's alw
- The full array syntax:
```js
- let [item1 = default, item2, ...rest] = array
+ let [item1 = defaultValue, item2, ...rest] = array
```
- The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`.
+ The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`.
- It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one.
diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md
index 9bb1d749c..18286c336 100644
--- a/1-js/05-data-types/11-date/1-new-date/solution.md
+++ b/1-js/05-data-types/11-date/1-new-date/solution.md
@@ -2,7 +2,17 @@ The `new Date` constructor uses the local time zone. So the only important thing
So February has number 1.
+Here's an example with numbers as date components:
+
+```js run
+//new Date(year, month, date, hour, minute, second, millisecond)
+let d1 = new Date(2012, 1, 20, 3, 12);
+alert( d1 );
+```
+We could also create a date from a string, like this:
+
```js run
-let d = new Date(2012, 1, 20, 3, 12);
-alert( d );
+//new Date(datastring)
+let d2 = new Date("2012-02-20T03:12");
+alert( d2 );
```
diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md
index a2de63ae4..6958a3a97 100644
--- a/1-js/05-data-types/11-date/article.md
+++ b/1-js/05-data-types/11-date/article.md
@@ -57,7 +57,7 @@ To create a new `Date` object call `new Date()` with one of the following argume
`new Date(year, month, date, hours, minutes, seconds, ms)`
: Create the date with the given components in the local time zone. Only the first two arguments are obligatory.
- - The `year` must have 4 digits: `2013` is okay, `98` is not.
+ - The `year` should have 4 digits. For compatibility, 2 digits are also accepted and considered `19xx`, e.g. `98` is the same as `1998` here, but always using 4 digits is strongly encouraged.
- The `month` count starts with `0` (Jan), up to `11` (Dec).
- The `date` parameter is actually the day of month, if absent then `1` is assumed.
- If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`.
@@ -69,7 +69,7 @@ To create a new `Date` object call `new Date()` with one of the following argume
new Date(2011, 0, 1); // the same, hours etc are 0 by default
```
- The minimal precision is 1 ms (1/1000 sec):
+ The maximal precision is 1 ms (1/1000 sec):
```js run
let date = new Date(2011, 0, 1, 2, 3, 4, 567);
@@ -348,7 +348,7 @@ let time1 = 0;
let time2 = 0;
*!*
-// run bench(upperSlice) and bench(upperLoop) each 10 times alternating
+// run bench(diffSubtract) and bench(diffGetTime) each 10 times alternating
for (let i = 0; i < 10; i++) {
time1 += bench(diffSubtract);
time2 += bench(diffGetTime);
@@ -376,7 +376,7 @@ for (let i = 0; i < 10; i++) {
```warn header="Be careful doing microbenchmarking"
Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all.
-The great pack of articles about V8 can be found at .
+The great pack of articles about V8 can be found at .
```
## Date.parse from a string
@@ -388,7 +388,7 @@ The string format should be: `YYYY-MM-DDTHH:mm:ss.sssZ`, where:
- `YYYY-MM-DD` -- is the date: year-month-day.
- The character `"T"` is used as the delimiter.
- `HH:mm:ss.sss` -- is the time: hours, minutes, seconds and milliseconds.
-- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` that would mean UTC+0.
+- The optional `'Z'` part denotes the time zone in the format `+-hh:mm`. A single letter `Z` would mean UTC+0.
Shorter variants are also possible, like `YYYY-MM-DD` or `YYYY-MM` or even `YYYY`.
@@ -407,7 +407,7 @@ We can instantly create a `new Date` object from the timestamp:
```js run
let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );
-alert(date);
+alert(date);
```
## Summary
@@ -427,7 +427,7 @@ Sometimes we need more precise time measurements. JavaScript itself does not hav
alert(`Loading started ${performance.now()}ms ago`);
// Something like: "Loading started 34731.26000000001ms ago"
// .26 is microseconds (260 microseconds)
-// more than 3 digits after the decimal point are precision errors, but only the first 3 are correct
+// more than 3 digits after the decimal point are precision errors, only the first 3 are correct
```
Node.js has `microtime` module and other ways. Technically, almost any device and environment allows to get more precision, it's just not in `Date`.
diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md
index a5f2974af..133ffb353 100644
--- a/1-js/05-data-types/12-json/article.md
+++ b/1-js/05-data-types/12-json/article.md
@@ -27,7 +27,7 @@ Luckily, there's no need to write the code to handle all this. The task has been
## JSON.stringify
-The [JSON](http://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](http://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever.
+The [JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](https://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever.
JavaScript provides methods:
@@ -41,7 +41,7 @@ let student = {
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
- wife: null
+ spouse: null
};
*!*
@@ -58,7 +58,7 @@ alert(json);
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
- "wife": null
+ "spouse": null
}
*/
*/!*
@@ -105,7 +105,7 @@ JSON is data-only language-independent specification, so some JavaScript-specifi
Namely:
- Function properties (methods).
-- Symbolic properties.
+- Symbolic keys and values.
- Properties that store `undefined`.
```js run
@@ -276,6 +276,7 @@ name: John
name: Alice
place: [object Object]
number: 23
+occupiedBy: [object Object]
*/
```
@@ -328,6 +329,8 @@ alert(JSON.stringify(user, null, 2));
*/
```
+The third argument can also be a string. In this case, the string is used for indentation instead of a number of spaces.
+
The `space` parameter is used solely for logging and nice-output purposes.
## Custom "toJSON"
@@ -402,7 +405,7 @@ To decode a JSON-string, we need another method named [JSON.parse](mdn:js/JSON/p
The syntax:
```js
-let value = JSON.parse(str, [reviver]);
+let value = JSON.parse(str[, reviver]);
```
str
@@ -448,7 +451,7 @@ let json = `{
Besides, JSON does not support comments. Adding a comment to JSON makes it invalid.
-There's another format named [JSON5](http://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language.
+There's another format named [JSON5](https://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language.
The regular JSON is that strict not because its developers are lazy, but to allow easy, reliable and very fast implementations of the parsing algorithm.
diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md
index 3a281ef3f..11667f940 100644
--- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md
+++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md
@@ -37,4 +37,4 @@ P.S. Naturally, the formula is the fastest solution. It uses only 3 operations f
The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower.
-P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory, so counting `sumTo(100000)` becomes possible. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size.
+P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function, with no other calculations performed, then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size.
diff --git a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md
index 59040a2b7..09e511db5 100644
--- a/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md
+++ b/1-js/06-advanced-functions/01-recursion/02-factorial/solution.md
@@ -1,4 +1,4 @@
-By definition, a factorial is `n!` can be written as `n * (n-1)!`.
+By definition, a factorial `n!` can be written as `n * (n-1)!`.
In other words, the result of `factorial(n)` can be calculated as `n` multiplied by the result of `factorial(n-1)`. And the call for `n-1` can recursively descend lower, and lower, till `1`.
diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md
index 4357ff208..0eb76ea1c 100644
--- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md
+++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md
@@ -33,7 +33,7 @@ printReverseList(list);
# Using a loop
-The loop variant is also a little bit more complicated then the direct output.
+The loop variant is also a little bit more complicated than the direct output.
There is no way to get the last value in our `list`. We also can't "go back".
diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md
index 320de62f0..5ae894474 100644
--- a/1-js/06-advanced-functions/01-recursion/article.md
+++ b/1-js/06-advanced-functions/01-recursion/article.md
@@ -61,7 +61,7 @@ When `pow(x, n)` is called, the execution splits into two branches:
if n==1 = x
/
pow(x, n) =
- \
+ \
else = x * pow(x, n - 1)
```
@@ -132,7 +132,7 @@ We can sketch it as:
-That's when the function starts to execute. The condition `n == 1` is false, so the flow continues into the second branch of `if`:
+That's when the function starts to execute. The condition `n == 1` is falsy, so the flow continues into the second branch of `if`:
```js run
function pow(x, n) {
@@ -188,7 +188,7 @@ The new current execution context is on top (and bold), and previous remembered
When we finish the subcall -- it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped.
```smart
-Here in the picture we use the word "line", as our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(â¦) + pow(â¦) + somethingElse(â¦)`.
+Here in the picture we use the word "line", as in our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(â¦) + pow(â¦) + somethingElse(â¦)`.
So it would be more precise to say that the execution resumes "immediately after the subcall".
```
@@ -285,7 +285,7 @@ The iterative `pow` uses a single context changing `i` and `result` in the proce
**Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.**
-...But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts.
+...But sometimes the rewrite is non-trivial, especially when a function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts.
Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used.
@@ -535,7 +535,7 @@ Terms:
list = { value, next -> list }
```
- Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they branch and every branch can have other branches.
+ Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they have branches and every branch can have other branches.
Recursive functions can be used to walk them as we've seen in the `sumSalary` example.
diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md
index 1f139d7a4..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".
@@ -225,7 +225,7 @@ But there's a subtle difference between `Array.from(obj)` and `[...obj]`:
So, for the task of turning something into an array, `Array.from` tends to be more universal.
-## Get a new copy of an array/object
+## Copy an array/object
Remember when we talked about `Object.assign()` [in the past](info:object-copy#cloning-and-merging-object-assign)?
@@ -233,8 +233,11 @@ It is possible to do the same thing with the spread syntax.
```js run
let arr = [1, 2, 3];
+
+*!*
let arrCopy = [...arr]; // spread the array into a list of parameters
// then put the result into a new array
+*/!*
// do the arrays have the same contents?
alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true
@@ -252,8 +255,11 @@ Note that it is possible to do the same thing to make a copy of an object:
```js run
let obj = { a: 1, b: 2, c: 3 };
+
+*!*
let objCopy = { ...obj }; // spread the object into a list of parameters
// then return the result in a new object
+*/!*
// do the objects have the same contents?
alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true
@@ -267,7 +273,7 @@ alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}
```
-This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj);` or for an array `let arrCopy = Object.assign([], arr);` so we prefer to use it whenever we can.
+This way of copying an object is much shorter than `let objCopy = Object.assign({}, obj)` or for an array `let arrCopy = Object.assign([], arr)` so we prefer to use it whenever we can.
## Summary
diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg
new file mode 100644
index 000000000..f8c7bd6ac
--- /dev/null
+++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-empty.svg
@@ -0,0 +1 @@
+outer <empty> makeArmy() LexicalEnvironment while iteration LexicalEnvironment <empty> <empty> <empty> i: 10
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg
new file mode 100644
index 000000000..7611d0ef8
--- /dev/null
+++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-for-fixed.svg
@@ -0,0 +1 @@
+outer makeArmy() LexicalEnvironment for iteration LexicalEnvironment i: 0 i: 1 i: 2 i: 10 ...
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg
new file mode 100644
index 000000000..d83ecbe76
--- /dev/null
+++ b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy-while-fixed.svg
@@ -0,0 +1 @@
+outer j: 0 j: 1 j: 2 j: 10 ... makeArmy() LexicalEnvironment while iteration LexicalEnvironment
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg
deleted file mode 100644
index c0a312ec7..000000000
--- a/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg
+++ /dev/null
@@ -1 +0,0 @@
-outer i: 0 i: 1 i: 2 i: 10 ... makeArmy() LexicalEnvironment for block LexicalEnvironment
\ No newline at end of file
diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md
index 0fb0b4a49..9d99aa717 100644
--- a/1-js/06-advanced-functions/03-closure/10-make-army/solution.md
+++ b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md
@@ -1,12 +1,12 @@
-Let's examine what's done inside `makeArmy`, and the solution will become obvious.
+Let's examine what exactly happens inside `makeArmy`, and the solution will become obvious.
1. It creates an empty array `shooters`:
```js
let shooters = [];
```
-2. Fills it in the loop via `shooters.push(function...)`.
+2. Fills it with functions via `shooters.push(function)` in the loop.
Every element is a function, so the resulting array looks like this:
@@ -26,95 +26,104 @@ Let's examine what's done inside `makeArmy`, and the solution will become obviou
```
3. The array is returned from the function.
+
+ Then, later, the call to any member, e.g. `army[5]()` will get the element `army[5]` from the array (which is a function) and calls it.
+
+ Now why do all such functions show the same value, `10`?
+
+ That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment.
+
+ Then, what will be the value of `i`?
+
+ If we look at the source:
+
+ ```js
+ function makeArmy() {
+ ...
+ let i = 0;
+ while (i < 10) {
+ let shooter = function() { // shooter function
+ alert( i ); // should show its number
+ };
+ shooters.push(shooter); // add function to the array
+ i++;
+ }
+ ...
+ }
+ ```
+
+ We can see that all `shooter` functions are created in the lexical environment of `makeArmy()` function. But when `army[5]()` is called, `makeArmy` has already finished its job, and the final value of `i` is `10` (`while` stops at `i=10`).
+
+ As the result, all `shooter` functions get the same value from the outer lexical environment and that is, the last value, `i=10`.
+
+ 
+
+ As you can see above, on each iteration of a `while {...}` block, a new lexical environment is created. So, to fix this, we can copy the value of `i` into a variable within the `while {...}` block, like this:
+
+ ```js run
+ function makeArmy() {
+ let shooters = [];
+
+ let i = 0;
+ while (i < 10) {
+ *!*
+ let j = i;
+ */!*
+ let shooter = function() { // shooter function
+ alert( *!*j*/!* ); // should show its number
+ };
+ shooters.push(shooter);
+ i++;
+ }
+
+ return shooters;
+ }
+
+ let army = makeArmy();
+
+ // Now the code works correctly
+ army[0](); // 0
+ army[5](); // 5
+ ```
+
+ Here `let j = i` declares an "iteration-local" variable `j` and copies `i` into it. Primitives are copied "by value", so we actually get an independent copy of `i`, belonging to the current loop iteration.
+
+ The shooters work correctly, because the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds to the current loop iteration:
+
+ 
+
+ Such a problem could also be avoided if we used `for` in the beginning, like this:
+
+ ```js run demo
+ function makeArmy() {
+
+ let shooters = [];
+
+ *!*
+ for(let i = 0; i < 10; i++) {
+ */!*
+ let shooter = function() { // shooter function
+ alert( i ); // should show its number
+ };
+ shooters.push(shooter);
+ }
+
+ return shooters;
+ }
+
+ let army = makeArmy();
+
+ army[0](); // 0
+ army[5](); // 5
+ ```
+
+ That's essentially the same, because `for` on each iteration generates a new lexical environment, with its own variable `i`. So `shooter` generated in every iteration references its own `i`, from that very iteration.
+
+ 
-Then, later, the call to `army[5]()` will get the element `army[5]` from the array (it will be a function) and call it.
-
-Now why all such functions show the same?
-
-That's because there's no local variable `i` inside `shooter` functions. When such a function is called, it takes `i` from its outer lexical environment.
-
-What will be the value of `i`?
-
-If we look at the source:
-
-```js
-function makeArmy() {
- ...
- let i = 0;
- while (i < 10) {
- let shooter = function() { // shooter function
- alert( i ); // should show its number
- };
- ...
- }
- ...
-}
-```
-
-...We can see that it lives in the lexical environment associated with the current `makeArmy()` run. But when `army[5]()` is called, `makeArmy` has already finished its job, and `i` has the last value: `10` (the end of `while`).
-
-As a result, all `shooter` functions get from the outer lexical envrironment the same, last value `i=10`.
-
-We can fix it by moving the variable definition into the loop:
-
-```js run demo
-function makeArmy() {
-
- let shooters = [];
-
-*!*
- for(let i = 0; i < 10; i++) {
-*/!*
- let shooter = function() { // shooter function
- alert( i ); // should show its number
- };
- shooters.push(shooter);
- }
-
- return shooters;
-}
-
-let army = makeArmy();
-
-army[0](); // 0
-army[5](); // 5
-```
-
-Now it works correctly, because every time the code block in `for (let i=0...) {...}` is executed, a new Lexical Environment is created for it, with the corresponding variable `i`.
-
-So, the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration. That's why now it works.
-
-
-
-Here we rewrote `while` into `for`.
-
-Another trick could be possible, let's see it for better understanding of the subject:
-
-```js run
-function makeArmy() {
- let shooters = [];
-
- let i = 0;
- while (i < 10) {
-*!*
- let j = i;
-*/!*
- let shooter = function() { // shooter function
- alert( *!*j*/!* ); // should show its number
- };
- shooters.push(shooter);
- i++;
- }
-
- return shooters;
-}
-
-let army = makeArmy();
+Now, as you've put so much effort into reading this, and the final recipe is so simple - just use `for`, you may wonder -- was it worth that?
-army[0](); // 0
-army[5](); // 5
-```
+Well, if you could easily answer the question, you wouldn't read the solution. So, hopefully this task must have helped you to understand things a bit better.
-The `while` loop, just like `for`, makes a new Lexical Environment for each run. So here we make sure that it gets the right value for a `shooter`.
+Besides, there are indeed cases when one prefers `while` to `for`, and other scenarios, where such problems are real.
-We copy `let j = i`. This makes a loop body local `j` and copies the value of `i` to it. Primitives are copied "by value", so we actually get a complete independent copy of `i`, belonging to the current loop iteration.
diff --git a/1-js/06-advanced-functions/03-closure/10-make-army/task.md b/1-js/06-advanced-functions/03-closure/10-make-army/task.md
index 93e64f2d0..f50c7dc20 100644
--- a/1-js/06-advanced-functions/03-closure/10-make-army/task.md
+++ b/1-js/06-advanced-functions/03-closure/10-make-army/task.md
@@ -14,22 +14,28 @@ function makeArmy() {
let i = 0;
while (i < 10) {
- let shooter = function() { // shooter function
- alert( i ); // should show its number
+ let shooter = function() { // create a shooter function,
+ alert( i ); // that should show its number
};
- shooters.push(shooter);
+ shooters.push(shooter); // and add it to the array
i++;
}
+ // ...and return the array of shooters
return shooters;
}
let army = makeArmy();
-army[0](); // the shooter number 0 shows 10
-army[5](); // and number 5 also outputs 10...
-// ... all shooters show 10 instead of their 0, 1, 2, 3...
+*!*
+// all shooters show 10 instead of their numbers 0, 1, 2, 3...
+army[0](); // 10 from the shooter number 0
+army[1](); // 10 from the shooter number 1
+army[2](); // 10 ...and so on.
+*/!*
```
-Why do all of the shooters show the same value? Fix the code so that they work as intended.
+Why do all of the shooters show the same value?
+
+Fix the code so that they work as intended.
diff --git a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md
index d02c53b99..4e386eec5 100644
--- a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md
+++ b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md
@@ -1,4 +1,6 @@
+importance: 5
+---
# Function in if
Look at the code. What will be the result of the call at the last line?
diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md
index 346e4060a..b16b35290 100644
--- a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md
+++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md
@@ -27,7 +27,7 @@ The code above demonstrates it.
function func() {
*!*
// the local variable x is known to the engine from the beginning of the function,
- // but "unitialized" (unusable) until let ("dead zone")
+ // but "uninitialized" (unusable) until let ("dead zone")
// hence the error
*/!*
diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js
index e3c335e03..802f28c4d 100644
--- a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js
+++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js
@@ -23,7 +23,7 @@ describe("byField", function(){
{ name: "John", age: 20, surname: "Johnson"},
];
let ageSortedAnswer = users.sort(byField("age"));
- assert.deepEqual(ageSortedKey, ageSortedKey);
+ assert.deepEqual(ageSortedKey, ageSortedAnswer);
});
it("sorts users by surname", function(){
diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md
index 4d06c9db7..cb43a7968 100644
--- a/1-js/06-advanced-functions/03-closure/article.md
+++ b/1-js/06-advanced-functions/03-closure/article.md
@@ -7,7 +7,7 @@ We already know that a function can access variables outside of it ("outer" vari
But what happens if outer variables change since a function is created? Will the function get newer values or the old ones?
-And what if a function is passed along as a parameter and called from another place of code, will it get access to outer variables at the new place?
+And what if a function is passed along as an argument and called from another place of code, will it get access to outer variables at the new place?
Let's expand our knowledge to understand these scenarios and more complex ones.
@@ -146,7 +146,7 @@ Despite being simple, slightly modified variants of that code have practical use
How does this work? If we create multiple counters, will they be independent? What's going on with the variables here?
-Undestanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth.
+Understanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth.
## Lexical Environment
@@ -314,7 +314,7 @@ When on an interview, a frontend developer gets a question about "what's a closu
Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That's because there are no references to it. As any JavaScript object, it's only kept in memory while it's reachable.
-...But if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment.
+However, if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment.
In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive.
@@ -333,7 +333,7 @@ let g = f(); // g.[[Environment]] stores a reference to the Lexical Environment
// of the corresponding f() call
```
-Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below:
+Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. In the code below, all 3 of them:
```js
function f() {
@@ -371,7 +371,7 @@ As we've seen, in theory while a function is alive, all outer variables are also
But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it's obvious from the code that an outer variable is not used -- it is removed.
-**An important side effect in V8 (Chrome, Opera) is that such variable will become unavailable in debugging.**
+**An important side effect in V8 (Chrome, Edge, Opera) is that such variable will become unavailable in debugging.**
Try running the example below in Chrome with the Developer Tools open.
@@ -413,6 +413,6 @@ let g = f();
g();
```
-This feature of V8 is good to know. If you are debugging with Chrome/Opera, sooner or later you will meet it.
+This feature of V8 is good to know. If you are debugging with Chrome/Edge/Opera, sooner or later you will meet it.
-That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You always can check for it by running the examples on this page.
+That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You can always check for it by running the examples on this page.
diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md
index 1e8bb5bae..28d7a76ec 100644
--- a/1-js/06-advanced-functions/04-var/article.md
+++ b/1-js/06-advanced-functions/04-var/article.md
@@ -4,7 +4,7 @@
```smart header="This article is for understanding old scripts"
The information in this article is useful for understanding old scripts.
-That's not how we write a new code.
+That's not how we write new code.
```
In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration:
@@ -28,7 +28,7 @@ On the other hand, it's important to understand differences when migrating old s
## "var" has no block scope
-Variables, declared with `var`, are either function-wide or global. They are visible through blocks.
+Variables, declared with `var`, are either function-scoped or global-scoped. They are visible through blocks.
For instance:
@@ -52,19 +52,21 @@ if (true) {
}
*!*
-alert(test); // Error: test is not defined
+alert(test); // ReferenceError: test is not defined
*/!*
```
The same thing for loops: `var` cannot be block- or loop-local:
-```js
+```js run
for (var i = 0; i < 10; i++) {
+ var one = 1;
// ...
}
*!*
-alert(i); // 10, "i" is visible after loop, it's a global variable
+alert(i); // 10, "i" is visible after loop, it's a global variable
+alert(one); // 1, "one" is visible after loop, it's a global variable
*/!*
```
@@ -80,10 +82,10 @@ function sayHi() {
}
sayHi();
-alert(phrase); // Error: phrase is not defined (Check the Developer Console)
+alert(phrase); // ReferenceError: phrase is not defined
```
-As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript blocks had no Lexical Environments. And `var` is a remnant of that.
+As we can see, `var` pierces through `if`, `for` or other code blocks. That's because a long time ago in JavaScript, blocks had no Lexical Environments, and `var` is a remnant of that.
## "var" tolerates redeclarations
@@ -168,7 +170,7 @@ That's best demonstrated with an example:
```js run
function sayHi() {
- alert(phrase);
+ alert(phrase);
*!*
var phrase = "Hello";
@@ -203,11 +205,11 @@ sayHi();
Because all `var` declarations are processed at the function start, we can reference them at any place. But variables are undefined until the assignments.
-In both examples above `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`.
+In both examples above, `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`.
-### IIFE
+## IIFE
-As in the past there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE).
+In the past, as there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE).
That's not something we should use nowadays, but you can find them in old scripts.
@@ -216,22 +218,22 @@ An IIFE looks like this:
```js run
(function() {
- let message = "Hello";
+ var message = "Hello";
alert(message); // Hello
})();
```
-Here a Function Expression is created and immediately called. So the code executes right away and has its own private variables.
+Here, a Function Expression is created and immediately called. So the code executes right away and has its own private variables.
-The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error:
+The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript engine encounters `"function"` in the main code, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error:
```js run
-// Try to declare and immediately call a function
-function() { // <-- Error: Function statements require a function name
+// Tries to declare and immediately call a function
+function() { // <-- SyntaxError: Function statements require a function name
- let message = "Hello";
+ var message = "Hello";
alert(message); // Hello
@@ -254,11 +256,11 @@ There exist other ways besides parentheses to tell JavaScript that we mean a Fun
```js run
// Ways to create IIFE
-(function() {
+*!*(*/!*function() {
alert("Parentheses around the function");
}*!*)*/!*();
-(function() {
+*!*(*/!*function() {
alert("Parentheses around the whole thing");
}()*!*)*/!*;
@@ -277,7 +279,7 @@ In all the above cases we declare a Function Expression and run it immediately.
There are two main differences of `var` compared to `let/const`:
-1. `var` variables have no block scope, they are visible minimum at the function level.
+1. `var` variables have no block scope, their visibility is scoped to current function, or global, if declared outside function.
2. `var` declarations are processed at function start (script start for globals).
There's one more very minor difference related to the global object, that we'll cover in the next chapter.
diff --git a/1-js/06-advanced-functions/05-global-object/article.md b/1-js/06-advanced-functions/05-global-object/article.md
index 3d195a978..cf4839d94 100644
--- a/1-js/06-advanced-functions/05-global-object/article.md
+++ b/1-js/06-advanced-functions/05-global-object/article.md
@@ -5,7 +5,7 @@ The global object provides variables and functions that are available anywhere.
In a browser it is named `window`, for Node.js it is `global`, for other environments it may have another name.
-Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. In some browsers, namely non-Chromium Edge, `globalThis` is not yet supported, but can be easily polyfilled.
+Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. It's supported in all major browsers.
We'll use `window` here, assuming that our environment is a browser. If your script may run in other environments, it's better to use `globalThis` instead.
@@ -25,7 +25,9 @@ var gVar = 5;
alert(window.gVar); // 5 (became a property of the global object)
```
-Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such thing doesn't happen.
+Function declarations have the same effect (statements with `function` keyword in the main code flow, not function expressions).
+
+Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such a thing doesn't happen.
If we used `let` instead, such thing wouldn't happen:
@@ -81,7 +83,7 @@ if (!window.Promise) {
That includes JavaScript built-ins, such as `Array` and environment-specific values, such as `window.innerHeight` -- the window height in the browser.
- The global object has a universal name `globalThis`.
- ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js). As `globalThis` is a recent proposal, it's not supported in non-Chromium Edge (but can be polyfilled).
+ ...But more often is referred by "old-school" environment-specific names, such as `window` (browser) and `global` (Node.js).
- We should store values in the global object only if they're truly global for our project. And keep their number at minimum.
- In-browser, unless we're using [modules](info:modules), global functions and variables declared with `var` become a property of the global object.
- To make our code future-proof and easier to understand, we should access properties of the global object directly, as `window.x`.
diff --git a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md
index 5c9326912..e97039f72 100644
--- a/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md
+++ b/1-js/06-advanced-functions/06-function-object/5-sum-many-brackets/solution.md
@@ -5,7 +5,7 @@
Now the code:
-```js run
+```js demo run
function sum(a) {
let currentSum = a;
@@ -52,4 +52,4 @@ function f(b) {
}
```
-This `f` will be used in the next call, again return itself, so many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion.
+This `f` will be used in the next call, again return itself, as many times as needed. Then, when used as a number or a string -- the `toString` returns the `currentSum`. We could also use `Symbol.toPrimitive` or `valueOf` here for the conversion.
diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md
index ed848c0c5..8419ae763 100644
--- a/1-js/06-advanced-functions/06-function-object/article.md
+++ b/1-js/06-advanced-functions/06-function-object/article.md
@@ -326,7 +326,7 @@ welcome(); // Hello, Guest (nested call works)
Now it works, because the name `"func"` is function-local. It is not taken from outside (and not visible there). The specification guarantees that it will always reference the current function.
-The outer code still has it's variable `sayHi` or `welcome`. And `func` is an "internal function name", how the function can call itself internally.
+The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to call itself reliably.
```smart header="There's no such thing for Function Declaration"
The "internal name" feature described here is only available for Function Expressions, not for Function Declarations. For Function Declarations, there is no syntax for adding an "internal" name.
@@ -347,7 +347,7 @@ If the function is declared as a Function Expression (not in the main code flow)
Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature.
-They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts.
+They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want to learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts.
So, a function can do a useful job by itself and also carry a bunch of other functionality in properties.
diff --git a/1-js/06-advanced-functions/07-new-function/article.md b/1-js/06-advanced-functions/07-new-function/article.md
index 3214ba376..ffe264a4e 100644
--- a/1-js/06-advanced-functions/07-new-function/article.md
+++ b/1-js/06-advanced-functions/07-new-function/article.md
@@ -92,7 +92,7 @@ What if it could access the outer variables?
The problem is that before JavaScript is published to production, it's compressed using a *minifier* -- a special program that shrinks code by removing extra comments, spaces and -- what's important, renames local variables into shorter ones.
-For instance, if a function has `let userName`, minifier replaces it `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace.
+For instance, if a function has `let userName`, minifier replaces it with `let a` (or another letter if this one is occupied), and does it everywhere. That's usually a safe thing to do, because the variable is local, nothing outside the function can access it. And inside the function, minifier replaces every mention of it. Minifiers are smart, they analyze the code structure, so they don't break anything. They're not just a dumb find-and-replace.
So if `new Function` had access to outer variables, it would be unable to find renamed `userName`.
diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md
index 95fddea65..fc033c5e2 100644
--- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md
+++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md
@@ -27,7 +27,7 @@ Usually, that's a function. For historical reasons, a string of code can be pass
: The delay before run, in milliseconds (1000 ms = 1 second), by default 0.
`arg1`, `arg2`...
-: Arguments for the function (not supported in IE9-)
+: Arguments for the function
For instance, this code calls `sayHi()` after one second:
@@ -102,7 +102,7 @@ As we can see from `alert` output, in a browser the timer identifier is a number
Again, there is no universal specification for these methods, so that's fine.
-For browsers, timers are described in the [timers section](https://www.w3.org/TR/html5/webappapis.html#timers) of HTML5 standard.
+For browsers, timers are described in the [timers section](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) of HTML Living Standard.
## setInterval
@@ -129,7 +129,7 @@ setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
```smart header="Time goes on while `alert` is shown"
In most browsers, including Chrome and Firefox the internal timer continues "ticking" while showing `alert/confirm/prompt`.
-So if you run the code above and don't dismiss the `alert` window for some time, then in the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds.
+So if you run the code above and don't dismiss the `alert` window for some time, then the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds.
```
## Nested setTimeout
@@ -218,7 +218,7 @@ And here is the picture for the nested `setTimeout`:

-**The nested `setTimeout` guarantees the fixed delay (here 100ms).**
+**The nested `setTimeout` ensures a minimum delay (100ms here) between the end of one call and the beginning of the subsequent one.**
That's because a new call is planned at the end of the previous one.
@@ -232,7 +232,7 @@ setTimeout(function() {...}, 100);
For `setInterval` the function stays in memory until `clearInterval` is called.
-There's a side-effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small.
+There's a side effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small.
````
## Zero delay setTimeout
@@ -256,7 +256,7 @@ The first line "puts the call into calendar after 0ms". But the scheduler will o
There are also advanced browser-related use cases of zero-delay timeout, that we'll discuss in the chapter .
````smart header="Zero delay is in fact not zero (in a browser)"
-In the browser, there's a limitation of how often nested timers can run. The [HTML5 standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.".
+In the browser, there's a limitation of how often nested timers can run. The [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.".
Let's demonstrate what it means with the example below. The `setTimeout` call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the `times` array. What do the real delays look like? Let's see:
@@ -281,7 +281,7 @@ The similar thing happens if we use `setInterval` instead of `setTimeout`: `setI
That limitation comes from ancient times and many scripts rely on it, so it exists for historical reasons.
-For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html) for Node.js. So this note is browser-specific.
+For server-side JavaScript, that limitation does not exist, and there exist other ways to schedule an immediate asynchronous job, like [setImmediate](https://nodejs.org/api/timers.html#timers_setimmediate_callback_args) for Node.js. So this note is browser-specific.
````
## Summary
@@ -290,13 +290,13 @@ For server-side JavaScript, that limitation does not exist, and there exist othe
- To cancel the execution, we should call `clearTimeout/clearInterval` with the value returned by `setTimeout/setInterval`.
- Nested `setTimeout` calls are a more flexible alternative to `setInterval`, allowing us to set the time *between* executions more precisely.
- Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current script is complete".
-- The browser limits the minimal delay for five or more nested call of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons.
+- The browser limits the minimal delay for five or more nested calls of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons.
Please note that all scheduling methods do not *guarantee* the exact delay.
For example, the in-browser timer may slow down for a lot of reasons:
- The CPU is overloaded.
- The browser tab is in the background mode.
-- The laptop is on battery.
+- The laptop is on battery saving mode.
All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and OS-level performance settings.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
index fa52871a0..5b0fcc5f8 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md
@@ -16,14 +16,14 @@ Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there a
...And it will get the arguments of the very last call, other calls are ignored.
-Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce):
+Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)):
```js
let f = _.debounce(alert, 1000);
-f("a");
+f("a");
setTimeout( () => f("b"), 200);
-setTimeout( () => f("c"), 500);
+setTimeout( () => f("c"), 500);
// debounced function waits 1000ms after the last call and then runs: alert("c")
```
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
index cf851f771..6950664be 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md
@@ -12,11 +12,10 @@ function throttle(func, ms) {
savedThis = this;
return;
}
+ isThrottled = true;
func.apply(this, arguments); // (1)
- isThrottled = true;
-
setTimeout(function() {
isThrottled = false; // (3)
if (savedArgs) {
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
index 6df7af132..cbd473196 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
@@ -8,7 +8,7 @@ Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper.
When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds.
-The difference with debounce is that it's completely different decorator:
+Compared to the debounce decorator, the behavior is completely different:
- `debounce` runs the function once after the "cooldown" period. Good for processing the final result.
- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md
index d0dda4df1..c5d785493 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md
@@ -36,11 +36,11 @@ function cachingDecorator(func) {
slow = cachingDecorator(slow);
-alert( slow(1) ); // slow(1) is cached
-alert( "Again: " + slow(1) ); // the same
+alert( slow(1) ); // slow(1) is cached and the result returned
+alert( "Again: " + slow(1) ); // slow(1) result returned from cache
-alert( slow(2) ); // slow(2) is cached
-alert( "Again: " + slow(2) ); // the same as the previous line
+alert( slow(2) ); // slow(2) is cached and the result returned
+alert( "Again: " + slow(2) ); // slow(2) result returned from cache
```
In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior.
@@ -301,18 +301,18 @@ The only syntax difference between `call` and `apply` is that `call` expects a l
So these two calls are almost equivalent:
```js
-func.call(context, ...args); // pass an array as list with spread syntax
-func.apply(context, args); // is same as using call
+func.call(context, ...args);
+func.apply(context, args);
```
-There's only a subtle difference:
+They perform the same call of `func` with given context and arguments.
+
+There's only a subtle difference regarding `args`:
- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`.
- The `apply` accepts only *array-like* `args`.
-So, where we expect an iterable, `call` works, and where we expect an array-like, `apply` works.
-
-And for objects that are both iterable and array-like, like a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better.
+...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better.
Passing all arguments along with the context to another function is called *call forwarding*.
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 8de8e6fd1..7a6e47b90 100644
--- a/1-js/06-advanced-functions/10-bind/article.md
+++ b/1-js/06-advanced-functions/10-bind/article.md
@@ -125,7 +125,7 @@ funcUser(); // John
*/!*
```
-Here `func.bind(user)` as a "bound variant" of `func`, with fixed `this=user`.
+Here `func.bind(user)` is a "bound variant" of `func`, with fixed `this=user`.
All arguments are passed to the original `func` "as is", for instance:
@@ -187,8 +187,8 @@ let user = {
let say = user.say.bind(user);
-say("Hello"); // Hello, John ("Hello" argument is passed to say)
-say("Bye"); // Bye, John ("Bye" is passed to say)
+say("Hello"); // Hello, John! ("Hello" argument is passed to say)
+say("Bye"); // Bye, John! ("Bye" is passed to say)
```
````smart header="Convenience method: `bindAll`"
@@ -202,7 +202,7 @@ for (let key in user) {
}
```
-JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(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
@@ -247,7 +247,7 @@ The call to `mul.bind(null, 2)` creates a new function `double` that passes call
That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one.
-Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`.
+Please note that we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`.
The function `triple` in the code below triples the value:
diff --git a/1-js/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md
index f5caeaece..8730277ad 100644
--- a/1-js/06-advanced-functions/12-arrow-functions/article.md
+++ b/1-js/06-advanced-functions/12-arrow-functions/article.md
@@ -52,7 +52,7 @@ let group = {
*!*
this.students.forEach(function(student) {
// Error: Cannot read property 'title' of undefined
- alert(this.title + ': ' + student)
+ alert(this.title + ': ' + student);
});
*/!*
}
@@ -87,7 +87,7 @@ For instance, `defer(f, ms)` gets a function and returns a wrapper around it tha
```js run
function defer(f, ms) {
return function() {
- setTimeout(() => f.apply(this, arguments), ms)
+ setTimeout(() => f.apply(this, arguments), ms);
};
}
diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md
index 3593bffae..0a945b377 100644
--- a/1-js/07-object-properties/01-property-descriptors/article.md
+++ b/1-js/07-object-properties/01-property-descriptors/article.md
@@ -19,7 +19,7 @@ We didn't see them yet, because generally they do not show up. When we create a
First, let's see how to get those flags.
-The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
+The method [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
The syntax is:
```js
@@ -54,7 +54,7 @@ alert( JSON.stringify(descriptor, null, 2 ) );
*/
```
-To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty).
+To change the flags, we can use [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
The syntax is:
@@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name'
Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours.
```smart header="Errors appear only in strict mode"
-In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
+In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
```
Here's the same example, but the property is created from scratch:
@@ -194,7 +194,7 @@ alert(Object.keys(user)); // name
The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties.
-A non-configurable property can not be deleted.
+A non-configurable property can't be deleted, its attributes can't be modified.
For instance, `Math.PI` is non-writable, non-enumerable and non-configurable:
@@ -214,49 +214,67 @@ alert( JSON.stringify(descriptor, null, 2 ) );
So, a programmer is unable to change the value of `Math.PI` or overwrite it.
```js run
-Math.PI = 3; // Error
+Math.PI = 3; // Error, because it has writable: false
// delete Math.PI won't work either
```
+We also can't change `Math.PI` to be `writable` again:
+
+```js run
+// Error, because of configurable: false
+Object.defineProperty(Math, "PI", { writable: true });
+```
+
+There's absolutely nothing we can do with `Math.PI`.
+
Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`.
-To be precise, non-configurability imposes several restrictions on `defineProperty`:
-1. Can't change `configurable` flag.
-2. Can't change `enumerable` flag.
-3. Can't change `writable: false` to `true` (the other way round works).
-4. Can't change `get/set` for an accessor property (but can assign them if absent).
+**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.**
-Here we are making `user.name` a "forever sealed" constant:
+Here `user.name` is non-configurable, but we can still change it (as it's writable):
```js run
-let user = { };
+let user = {
+ name: "John"
+};
+
+Object.defineProperty(user, "name", {
+ configurable: false
+});
+
+user.name = "Pete"; // works fine
+delete user.name; // Error
+```
+
+And here we make `user.name` a "forever sealed" constant, just like the built-in `Math.PI`:
+
+```js run
+let user = {
+ name: "John"
+};
Object.defineProperty(user, "name", {
- value: "John",
writable: false,
configurable: false
});
-*!*
// won't be able to change user.name or its flags
// all this won't work:
-// user.name = "Pete"
-// delete user.name
-// defineProperty(user, "name", { value: "Pete" })
-Object.defineProperty(user, "name", {writable: true}); // Error
-*/!*
+user.name = "Pete";
+delete user.name;
+Object.defineProperty(user, "name", { value: "Pete" });
```
-```smart header="\"Non-configurable\" doesn't mean \"non-writable\""
-Notable exception: a value of non-configurable, but writable property can be changed.
+```smart header="The only attribute change possible: writable true -> false"
+There's a minor exception about changing flags.
-The idea of `configurable: false` is to prevent changes to property flags and its deletion, not changes to its value.
+We can change `writable: true` to `false` for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though.
```
## Object.defineProperties
-There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once.
+There's a method [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) that allows to define many properties at once.
The syntax is:
@@ -282,7 +300,7 @@ So, we can set many properties at once.
## Object.getOwnPropertyDescriptors
-To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors).
+To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors).
Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object:
@@ -300,7 +318,7 @@ for (let key in user) {
...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred.
-Another difference is that `for..in` ignores symbolic properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic ones.
+Another difference is that `for..in` ignores symbolic and non-enumerable properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic and non-enumerable ones.
## Sealing an object globally
@@ -308,24 +326,24 @@ Property descriptors work at the level of individual properties.
There are also methods that limit access to the *whole* object:
-[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions)
+[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions)
: Forbids the addition of new properties to the object.
-[Object.seal(obj)](mdn:js/Object/seal)
+[Object.seal(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal)
: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties.
-[Object.freeze(obj)](mdn:js/Object/freeze)
+[Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
: Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties.
And also there are tests for them:
-[Object.isExtensible(obj)](mdn:js/Object/isExtensible)
+[Object.isExtensible(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible)
: Returns `false` if adding properties is forbidden, otherwise `true`.
-[Object.isSealed(obj)](mdn:js/Object/isSealed)
+[Object.isSealed(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed)
: Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`.
-[Object.isFrozen(obj)](mdn:js/Object/isFrozen)
+[Object.isFrozen(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen)
: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`.
These methods are rarely used in practice.
diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md
index 45b9e70ed..c2aa35d53 100644
--- a/1-js/07-object-properties/02-property-accessors/article.md
+++ b/1-js/07-object-properties/02-property-accessors/article.md
@@ -5,7 +5,7 @@ There are two kinds of object properties.
The first kind is *data properties*. We already know how to work with them. All properties that we've been using until now were data properties.
-The second type of properties is something new. It's *accessor properties*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code.
+The second type of property is something new. It's an *accessor property*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code.
## Getters and setters
diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md
index 710390f15..ef6c7ffeb 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/article.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/article.md
@@ -12,7 +12,7 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named

-The prototype is a little bit "magical". When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it.
+When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it.
The property `[[Prototype]]` is internal and hidden, but there are many ways to set it.
@@ -27,19 +27,11 @@ let rabbit = {
};
*!*
-rabbit.__proto__ = animal;
+rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal
*/!*
```
-```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
-Please note that `__proto__` is *not the same* as `[[Prototype]]`. It's a getter/setter for it.
-
-It exists for historical reasons. In modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later.
-
-By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples.
-```
-
-If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`.
+Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`.
For instance:
@@ -62,7 +54,7 @@ alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true
```
-Here the line `(*)` sets `animal` to be a prototype of `rabbit`.
+Here the line `(*)` sets `animal` to be the prototype of `rabbit`.
Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up):
@@ -130,6 +122,8 @@ alert(longEar.jumps); // true (from rabbit)

+Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`.
+
There are only two limitations:
1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle.
@@ -137,6 +131,18 @@ There are only two limitations:
Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others.
+```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
+It's a common mistake of novice developers not to know the difference between these two.
+
+Please note that `__proto__` is *not the same* as the internal `[[Prototype]]` property. It's a getter/setter for `[[Prototype]]`. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language.
+
+The `__proto__` property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use `Object.getPrototypeOf/Object.setPrototypeOf` functions instead that get/set the prototype. We'll also cover these functions later.
+
+By the specification, `__proto__` must only be supported by browsers. In fact though, all environments including server-side support `__proto__`, so we're quite safe using it.
+
+As the `__proto__` notation is a bit more intuitively obvious, we use it in the examples.
+```
+
## Writing doesn't use prototype
The prototype is only used for reading properties.
@@ -197,6 +203,9 @@ alert(admin.fullName); // John Smith (*)
// setter triggers!
admin.fullName = "Alice Cooper"; // (**)
+
+alert(admin.fullName); // Alice Cooper, state of admin modified
+alert(user.fullName); // John Smith, state of user protected
```
Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called.
@@ -277,7 +286,7 @@ for(let prop in rabbit) alert(prop); // jumps, then eats
*/!*
```
-If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
+If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
So we can filter out inherited properties (or do something else with them):
diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
index 0073e252e..372d50dd6 100644
--- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
+++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md
@@ -38,7 +38,12 @@ Why `user2.name` is `undefined`?
Here's how `new user.constructor('Pete')` works:
1. First, it looks for `constructor` in `user`. Nothing.
-2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing.
-3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used.
+2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!).
+3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`.
+4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used.
-At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object, similar to `let user2 = {}`, that's what we have in `user2` after all.
+Finally, at the end, we have `let user2 = new Object('Pete')`.
+
+Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`.
+
+(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all).
\ No newline at end of file
diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md
index 378936c9a..bdfc86dd8 100644
--- a/1-js/08-prototypes/03-native-prototypes/article.md
+++ b/1-js/08-prototypes/03-native-prototypes/article.md
@@ -2,7 +2,7 @@
The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it.
-First we'll see at the details, and then how to use it for adding new capabilities to built-in objects.
+First we'll look at the details, and then how to use it for adding new capabilities to built-in objects.
## Object.prototype
@@ -33,7 +33,9 @@ We can check it like this:
let obj = {};
alert(obj.__proto__ === Object.prototype); // true
-// obj.toString === obj.__proto__.toString == Object.prototype.toString
+
+alert(obj.toString === obj.__proto__.toString); //true
+alert(obj.toString === Object.prototype.toString); //true
```
Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`:
diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
index a92e17900..f3c9cf0e5 100644
--- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
+++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
@@ -28,4 +28,4 @@ alert(dictionary); // "apple,__proto__"
When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable.
-See the the chapter [](info:property-descriptors) for review.
+See the chapter [](info:property-descriptors) for review.
diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md
index e460ef016..9c5f1eb3d 100644
--- a/1-js/08-prototypes/04-prototype-methods/article.md
+++ b/1-js/08-prototypes/04-prototype-methods/article.md
@@ -3,15 +3,18 @@
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
-The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard).
+Setting or reading the prototype with `obj.__proto__` is considered outdated and somewhat deprecated (moved to the so-called "Annex B" of the JavaScript standard, meant for browsers only).
-The modern methods are:
+The modern methods to get/set a prototype are:
-- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`.
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`.
-These should be used instead of `__proto__`.
+The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`.
+
+Although, there's a special method for this too:
+
+- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
For instance:
@@ -22,7 +25,7 @@ let animal = {
// create a new object with animal as a prototype
*!*
-let rabbit = Object.create(animal);
+let rabbit = Object.create(animal); // same as {__proto__: animal}
*/!*
alert(rabbit.eats); // true
@@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
*/!*
```
-`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:
+The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors.
+
+We can provide additional properties to the new object there, like this:
```js run
let animal = {
@@ -57,26 +62,34 @@ The descriptors are in the same format as described in the chapter , arrow functions do not have `super`.
If accessed, it's taken from the outer function. For instance:
+
```js
class Rabbit extends Animal {
stop() {
@@ -176,7 +177,6 @@ setTimeout(function() { super.stop() }, 1000);
```
````
-
## Overriding constructor
With constructors it gets a little bit tricky.
@@ -280,8 +280,6 @@ alert(rabbit.earLength); // 10
*/!*
```
-
-
### Overriding class fields: a tricky note
```warn header="Advanced note"
@@ -300,7 +298,7 @@ Consider this example:
```js run
class Animal {
- name = 'animal'
+ name = 'animal';
constructor() {
alert(this.name); // (*)
@@ -317,13 +315,13 @@ new Rabbit(); // animal
*/!*
```
-Here, class `Rabbit` extends `Animal` and overrides `name` field with its own value.
+Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value.
There's no own constructor in `Rabbit`, so `Animal` constructor is called.
What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`.
-**In other words, parent constructor always uses its own field value, not the overridden one.**
+**In other words, the parent constructor always uses its own field value, not the overridden one.**
What's odd about it?
@@ -360,23 +358,22 @@ And that's what we naturally expect. When the parent constructor is called in th
...But for class fields it's not so. As said, the parent constructor always uses the parent field.
-Why is there the difference?
+Why is there a difference?
-Well, the reason is in the field initialization order. The class field is initialized:
+Well, the reason is the field initialization order. The class field is initialized:
- Before constructor for the base class (that doesn't extend anything),
-- Imediately after `super()` for the derived class.
+- Immediately after `super()` for the derived class.
In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`.
So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used.
-This subtle difference between fields and methods is specific to JavaScript
+This subtle difference between fields and methods is specific to JavaScript.
Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here.
If it becomes a problem, one can fix it by using methods or getters/setters instead of fields.
-
## Super: internals, [[HomeObject]]
```warn header="Advanced information"
@@ -545,7 +542,7 @@ Here's the demo of a wrong `super` result after copying:
```js run
let animal = {
sayHi() {
- console.log(`I'm an animal`);
+ alert(`I'm an animal`);
}
};
@@ -559,7 +556,7 @@ let rabbit = {
let plant = {
sayHi() {
- console.log("I'm a plant");
+ alert("I'm a plant");
}
};
diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md
index ca9e80601..cb9829ce0 100644
--- a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md
+++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md
@@ -21,14 +21,14 @@ alert( rabbit.hasOwnProperty('name') ); // true
But that's not all yet.
-Even after the fix, there's still important difference in `"class Rabbit extends Object"` versus `class Rabbit`.
+Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`.
As we know, the "extends" syntax sets up two prototypes:
1. Between `"prototype"` of the constructor functions (for methods).
2. Between the constructor functions themselves (for static methods).
-In our case, for `class Rabbit extends Object` it means:
+In the case of `class Rabbit extends Object` it means:
```js run
class Rabbit extends Object {}
@@ -37,7 +37,7 @@ alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) true
```
-So `Rabbit` now provides access to static methods of `Object` via `Rabbit`, like this:
+So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this:
```js run
class Rabbit extends Object {}
@@ -67,7 +67,7 @@ alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
So `Rabbit` doesn't provide access to static methods of `Object` in that case.
-By the way, `Function.prototype` has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`.
+By the way, `Function.prototype` also has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`.
Here's the picture:
diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md
index ab08f2ded..4b493a5e8 100644
--- a/1-js/09-classes/03-static-properties-methods/article.md
+++ b/1-js/09-classes/03-static-properties-methods/article.md
@@ -1,9 +1,9 @@
# Static properties and methods
-We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*.
+We can also assign a method to the class as a whole. Such methods are called *static*.
-In a class, they are prepended by `static` keyword, like this:
+In a class declaration, they are prepended by `static` keyword, like this:
```js run
class User {
@@ -31,9 +31,11 @@ User.staticMethod(); // true
The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule).
-Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
+Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it.
-For instance, we have `Article` objects and need a function to compare them. A natural solution would be to add `Article.compare` method, like this:
+For instance, we have `Article` objects and need a function to compare them.
+
+A natural solution would be to add `Article.compare` static method:
```js run
class Article {
@@ -63,9 +65,11 @@ articles.sort(Article.compare);
alert( articles[0].title ); // CSS
```
-Here `Article.compare` stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
+Here `Article.compare` method stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
+
+Another example would be a so-called "factory" method.
-Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
+Let's say, we need multiple ways to create an article:
1. Create by given parameters (`title`, `date` etc).
2. Create an empty article with today's date.
@@ -73,7 +77,7 @@ Another example would be a so-called "factory" method. Imagine, we need few ways
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
-Like `Article.createTodays()` here:
+Such as `Article.createTodays()` here:
```js run
class Article {
@@ -101,10 +105,21 @@ Static methods are also used in database-related classes to search/save/remove e
```js
// assuming Article is a special class for managing articles
-// static method to remove the article:
+// static method to remove the article by id:
Article.remove({id: 12345});
```
+````warn header="Static methods aren't available for individual objects"
+Static methods are callable on classes, not on individual objects.
+
+E.g. such code won't work:
+
+```js
+// ...
+article.createTodays(); /// Error: article.createTodays is not a function
+```
+````
+
## Static properties
[recent browser=Chrome]
@@ -125,7 +140,7 @@ That is the same as a direct assignment to `Article`:
Article.publisher = "Ilya Kantor";
```
-## Inheritance of static properties and methods
+## Inheritance of static properties and methods [#statics-and-inheritance]
Static properties and methods are inherited.
diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md
index 60ed0ef1b..91efb89ee 100644
--- a/1-js/09-classes/04-private-protected-properties-methods/article.md
+++ b/1-js/09-classes/04-private-protected-properties-methods/article.md
@@ -96,7 +96,9 @@ class CoffeeMachine {
_waterAmount = 0;
set waterAmount(value) {
- if (value < 0) throw new Error("Negative water");
+ if (value < 0) {
+ value = 0;
+ }
this._waterAmount = value;
}
@@ -114,10 +116,10 @@ class CoffeeMachine {
let coffeeMachine = new CoffeeMachine(100);
// add water
-coffeeMachine.waterAmount = -10; // Error: Negative water
+coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10
```
-Now the access is under control, so setting the water below zero fails.
+Now the access is under control, so setting the water amount below zero becomes impossible.
## Read-only "power"
@@ -159,7 +161,7 @@ class CoffeeMachine {
_waterAmount = 0;
*!*setWaterAmount(value)*/!* {
- if (value < 0) throw new Error("Negative water");
+ if (value < 0) value = 0;
this._waterAmount = value;
}
@@ -190,7 +192,7 @@ There's a finished JavaScript proposal, almost in the standard, that provides la
Privates should start with `#`. They are only accessible from inside the class.
-For instance, here's a private `#waterLimit` property and the water-checking private method `#checkWater`:
+For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`:
```js run
class CoffeeMachine {
@@ -199,19 +201,23 @@ class CoffeeMachine {
*/!*
*!*
- #checkWater(value) {
- if (value < 0) throw new Error("Negative water");
- if (value > this.#waterLimit) throw new Error("Too much water");
+ #fixWaterAmount(value) {
+ if (value < 0) return 0;
+ if (value > this.#waterLimit) return this.#waterLimit;
}
*/!*
+ setWaterAmount(value) {
+ this.#waterLimit = this.#fixWaterAmount(value);
+ }
+
}
let coffeeMachine = new CoffeeMachine();
*!*
// can't access privates from outside of the class
-coffeeMachine.#checkWater(); // Error
+coffeeMachine.#fixWaterAmount(123); // Error
coffeeMachine.#waterLimit = 1000; // Error
*/!*
```
@@ -232,7 +238,7 @@ class CoffeeMachine {
}
set waterAmount(value) {
- if (value < 0) throw new Error("Negative water");
+ if (value < 0) value = 0;
this.#waterAmount = value;
}
}
diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md
index dd3d61ca6..00aee3376 100644
--- a/1-js/09-classes/06-instanceof/article.md
+++ b/1-js/09-classes/06-instanceof/article.md
@@ -2,7 +2,7 @@
The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account.
-Such a check may be necessary in many cases. Here we'll use it for building a *polymorphic* function, the one that treats arguments differently depending on their type.
+Such a check may be necessary in many cases. For example, it can be used for building a *polymorphic* function, the one that treats arguments differently depending on their type.
## The instanceof operator [#ref-instanceof]
@@ -55,7 +55,7 @@ The algorithm of `obj instanceof Class` works roughly as follows:
For example:
```js run
- // setup instanceOf check that assumes that
+ // set up instanceof check that assumes that
// anything with canEat property is an animal
class Animal {
static [Symbol.hasInstance](obj) {
@@ -68,7 +68,7 @@ The algorithm of `obj instanceof Class` works roughly as follows:
alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called
```
-2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` is equal to one of the prototypes in the `obj` prototype chain.
+2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceof Class` checks whether `Class.prototype` is equal to one of the prototypes in the `obj` prototype chain.
In other words, compare one after another:
```js
@@ -93,7 +93,7 @@ The algorithm of `obj instanceof Class` works roughly as follows:
alert(rabbit instanceof Animal); // true
*/!*
- // rabbit.__proto__ === Rabbit.prototype
+ // rabbit.__proto__ === Animal.prototype (no match)
*!*
// rabbit.__proto__.__proto__ === Animal.prototype (match!)
*/!*
diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md
index d43b96c96..526b832ef 100644
--- a/1-js/09-classes/07-mixins/article.md
+++ b/1-js/09-classes/07-mixins/article.md
@@ -69,7 +69,7 @@ let sayMixin = {
};
let sayHiMixin = {
- __proto__: sayMixin, // (or we could use Object.create to set the prototype here)
+ __proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here)
sayHi() {
*!*
@@ -103,7 +103,7 @@ Here's the diagram (see the right part):
That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above.
-As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`.
+As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`.
## EventMixin
diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md
index 303431d6d..ec0dabc9a 100644
--- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md
+++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md
@@ -1,8 +1,8 @@
The difference becomes obvious when we look at the code inside a function.
-The behavior is different if there's a "jump out" of `try..catch`.
+The behavior is different if there's a "jump out" of `try...catch`.
-For instance, when there's a `return` inside `try..catch`. The `finally` clause works in case of *any* exit from `try..catch`, even via the `return` statement: right after `try..catch` is done, but before the calling code gets the control.
+For instance, when there's a `return` inside `try...catch`. The `finally` clause works in case of *any* exit from `try...catch`, even via the `return` statement: right after `try...catch` is done, but before the calling code gets the control.
```js run
function f() {
@@ -11,7 +11,7 @@ function f() {
*!*
return "result";
*/!*
- } catch (e) {
+ } catch (err) {
/// ...
} finally {
alert('cleanup!');
@@ -28,11 +28,11 @@ function f() {
try {
alert('start');
throw new Error("an error");
- } catch (e) {
+ } catch (err) {
// ...
if("can't handle the error") {
*!*
- throw e;
+ throw err;
*/!*
}
diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
index c573cc232..b6dc81326 100644
--- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
+++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md
@@ -6,12 +6,12 @@ importance: 5
Compare the two code fragments.
-1. The first one uses `finally` to execute the code after `try..catch`:
+1. The first one uses `finally` to execute the code after `try...catch`:
```js
try {
work work
- } catch (e) {
+ } catch (err) {
handle errors
} finally {
*!*
@@ -19,12 +19,12 @@ Compare the two code fragments.
*/!*
}
```
-2. The second fragment puts the cleaning right after `try..catch`:
+2. The second fragment puts the cleaning right after `try...catch`:
```js
try {
work work
- } catch (e) {
+ } catch (err) {
handle errors
}
diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md
index 3a2dc4ed4..cad2e1a3e 100644
--- a/1-js/10-error-handling/1-try-catch/article.md
+++ b/1-js/10-error-handling/1-try-catch/article.md
@@ -1,14 +1,14 @@
-# Error handling, "try..catch"
+# Error handling, "try...catch"
No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons.
Usually, a script "dies" (immediately stops) in case of an error, printing it to console.
-But there's a syntax construct `try..catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable.
+But there's a syntax construct `try...catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable.
-## The "try..catch" syntax
+## The "try...catch" syntax
-The `try..catch` construct has two main blocks: `try`, and then `catch`:
+The `try...catch` construct has two main blocks: `try`, and then `catch`:
```js
try {
@@ -25,12 +25,12 @@ try {
It works like this:
1. First, the code in `try {...}` is executed.
-2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`.
-3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch(err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened.
+2. If there were no errors, then `catch (err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`.
+3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch (err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened.

-So, an error inside the `try {â¦}` block does not kill the script -- we have a chance to handle it in `catch`.
+So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`.
Let's look at some examples.
@@ -45,7 +45,7 @@ Let's look at some examples.
alert('End of try runs'); // *!*(2) <--*/!*
- } catch(err) {
+ } catch (err) {
alert('Catch is ignored, because there are no errors'); // (3)
@@ -64,7 +64,7 @@ Let's look at some examples.
alert('End of try (never reached)'); // (2)
- } catch(err) {
+ } catch (err) {
alert(`Error has occurred!`); // *!*(3) <--*/!*
@@ -72,45 +72,45 @@ Let's look at some examples.
```
-````warn header="`try..catch` only works for runtime errors"
-For `try..catch` to work, the code must be runnable. In other words, it should be valid JavaScript.
+````warn header="`try...catch` only works for runtime errors"
+For `try...catch` to work, the code must be runnable. In other words, it should be valid JavaScript.
It won't work if the code is syntactically wrong, for instance it has unmatched curly braces:
```js run
try {
{{{{{{{{{{{{
-} catch(e) {
+} catch (err) {
alert("The engine can't understand this code, it's invalid");
}
```
The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code.
-So, `try..catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions".
+So, `try...catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions".
````
-````warn header="`try..catch` works synchronously"
-If an exception happens in "scheduled" code, like in `setTimeout`, then `try..catch` won't catch it:
+````warn header="`try...catch` works synchronously"
+If an exception happens in "scheduled" code, like in `setTimeout`, then `try...catch` won't catch it:
```js run
try {
setTimeout(function() {
noSuchVariable; // script will die here
}, 1000);
-} catch (e) {
+} catch (err) {
alert( "won't work" );
}
```
-That's because the function itself is executed later, when the engine has already left the `try..catch` construct.
+That's because the function itself is executed later, when the engine has already left the `try...catch` construct.
-To catch an exception inside a scheduled function, `try..catch` must be inside that function:
+To catch an exception inside a scheduled function, `try...catch` must be inside that function:
```js run
setTimeout(function() {
try {
- noSuchVariable; // try..catch handles the error!
+ noSuchVariable; // try...catch handles the error!
} catch {
alert( "error is caught here!" );
}
@@ -125,7 +125,7 @@ When an error occurs, JavaScript generates an object containing the details abou
```js
try {
// ...
-} catch(err) { // <-- the "error object", could use another word instead of err
+} catch (err) { // <-- the "error object", could use another word instead of err
// ...
}
```
@@ -150,7 +150,7 @@ try {
*!*
lalala; // error, variable is not defined!
*/!*
-} catch(err) {
+} catch (err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)
@@ -175,9 +175,9 @@ try {
}
```
-## Using "try..catch"
+## Using "try...catch"
-Let's explore a real-life use case of `try..catch`.
+Let's explore a real-life use case of `try...catch`.
As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) method to read JSON-encoded values.
@@ -205,7 +205,7 @@ Should we be satisfied with that? Of course not!
This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message.
-Let's use `try..catch` to handle the error:
+Let's use `try...catch` to handle the error:
```js run
let json = "{ bad json }";
@@ -217,12 +217,12 @@ try {
*/!*
alert( user.name ); // doesn't work
-} catch (e) {
+} catch (err) {
*!*
// ...the execution jumps here
alert( "Our apologies, the data has errors, we'll try to request it one more time." );
- alert( e.name );
- alert( e.message );
+ alert( err.name );
+ alert( err.message );
*/!*
}
```
@@ -245,7 +245,7 @@ try {
alert( user.name ); // no name!
*/!*
-} catch (e) {
+} catch (err) {
alert( "doesn't execute" );
}
```
@@ -294,11 +294,11 @@ Let's see what kind of error `JSON.parse` generates:
```js run
try {
JSON.parse("{ bad json o_O }");
-} catch(e) {
+} catch (err) {
*!*
- alert(e.name); // SyntaxError
+ alert(err.name); // SyntaxError
*/!*
- alert(e.message); // Unexpected token b in JSON at position 2
+ alert(err.message); // Unexpected token b in JSON at position 2
}
```
@@ -323,8 +323,8 @@ try {
alert( user.name );
-} catch(e) {
- alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name
+} catch (err) {
+ alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name
}
```
@@ -334,7 +334,7 @@ Now `catch` became a single place for all error handling: both for `JSON.parse`
## Rethrowing
-In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing.
+In the example above we use `try...catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing.
For example:
@@ -345,7 +345,7 @@ try {
user = JSON.parse(json); // <-- forgot to put "let" before user
// ...
-} catch(err) {
+} catch (err) {
alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
// (no JSON Error actually)
}
@@ -353,7 +353,7 @@ try {
Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a bug may be discovered that leads to terrible hacks.
-In our case, `try..catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug.
+In our case, `try...catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug.
To avoid such problems, we can employ the "rethrowing" technique. The rule is simple:
@@ -362,7 +362,7 @@ To avoid such problems, we can employ the "rethrowing" technique. The rule is si
The "rethrowing" technique can be explained in more detail as:
1. Catch gets all errors.
-2. In the `catch(err) {...}` block we analyze the error object `err`.
+2. In the `catch (err) {...}` block we analyze the error object `err`.
3. If we don't know how to handle it, we do `throw err`.
Usually, we can check the error type using the `instanceof` operator:
@@ -370,7 +370,7 @@ Usually, we can check the error type using the `instanceof` operator:
```js run
try {
user = { /*...*/ };
-} catch(err) {
+} catch (err) {
*!*
if (err instanceof ReferenceError) {
*/!*
@@ -399,24 +399,24 @@ try {
alert( user.name );
-} catch(e) {
+} catch (err) {
*!*
- if (e instanceof SyntaxError) {
- alert( "JSON Error: " + e.message );
+ if (err instanceof SyntaxError) {
+ alert( "JSON Error: " + err.message );
} else {
- throw e; // rethrow (*)
+ throw err; // rethrow (*)
}
*/!*
}
```
-The error throwing on line `(*)` from inside `catch` block "falls out" of `try..catch` and can be either caught by an outer `try..catch` construct (if it exists), or it kills the script.
+The error throwing on line `(*)` from inside `catch` block "falls out" of `try...catch` and can be either caught by an outer `try...catch` construct (if it exists), or it kills the script.
So the `catch` block actually handles only errors that it knows how to deal with and "skips" all others.
-The example below demonstrates how such errors can be caught by one more level of `try..catch`:
+The example below demonstrates how such errors can be caught by one more level of `try...catch`:
```js run
function readData() {
@@ -427,11 +427,11 @@ function readData() {
*!*
blabla(); // error!
*/!*
- } catch (e) {
+ } catch (err) {
// ...
- if (!(e instanceof SyntaxError)) {
+ if (!(err instanceof SyntaxError)) {
*!*
- throw e; // rethrow (don't know how to deal with it)
+ throw err; // rethrow (don't know how to deal with it)
*/!*
}
}
@@ -439,20 +439,20 @@ function readData() {
try {
readData();
-} catch (e) {
+} catch (err) {
*!*
- alert( "External catch got: " + e ); // caught it!
+ alert( "External catch got: " + err ); // caught it!
*/!*
}
```
-Here `readData` only knows how to handle `SyntaxError`, while the outer `try..catch` knows how to handle everything.
+Here `readData` only knows how to handle `SyntaxError`, while the outer `try...catch` knows how to handle everything.
-## try..catch..finally
+## try...catch...finally
Wait, that's not all.
-The `try..catch` construct may have one more code clause: `finally`.
+The `try...catch` construct may have one more code clause: `finally`.
If it exists, it runs in all cases:
@@ -464,7 +464,7 @@ The extended syntax looks like this:
```js
*!*try*/!* {
... try to execute the code ...
-} *!*catch*/!*(e) {
+} *!*catch*/!* (err) {
... handle errors ...
} *!*finally*/!* {
... execute always ...
@@ -477,7 +477,7 @@ Try running this code:
try {
alert( 'try' );
if (confirm('Make an error?')) BAD_CODE();
-} catch (e) {
+} catch (err) {
alert( 'catch' );
} finally {
alert( 'finally' );
@@ -513,7 +513,7 @@ let start = Date.now();
try {
result = fib(num);
-} catch (e) {
+} catch (err) {
result = 0;
*!*
} finally {
@@ -531,14 +531,14 @@ You can check by running the code with entering `35` into `prompt` -- it execute
In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases.
-```smart header="Variables are local inside `try..catch..finally`"
-Please note that `result` and `diff` variables in the code above are declared *before* `try..catch`.
+```smart header="Variables are local inside `try...catch...finally`"
+Please note that `result` and `diff` variables in the code above are declared *before* `try...catch`.
Otherwise, if we declared `let` in `try` block, it would only be visible inside of it.
```
````smart header="`finally` and `return`"
-The `finally` clause works for *any* exit from `try..catch`. That includes an explicit `return`.
+The `finally` clause works for *any* exit from `try...catch`. That includes an explicit `return`.
In the example below, there's a `return` in `try`. In this case, `finally` is executed just before the control returns to the outer code.
@@ -550,7 +550,7 @@ function func() {
return 1;
*/!*
- } catch (e) {
+ } catch (err) {
/* ... */
} finally {
*!*
@@ -563,9 +563,9 @@ alert( func() ); // first works alert from finally, and then this one
```
````
-````smart header="`try..finally`"
+````smart header="`try...finally`"
-The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized.
+The `try...finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized.
```js
function func() {
@@ -586,7 +586,7 @@ In the code above, an error inside `try` always falls out, because there's no `c
The information from this section is not a part of the core JavaScript.
```
-Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error or some other terrible thing.
+Let's imagine we've got a fatal error outside of `try...catch`, and the script died. Like a programming error or some other terrible thing.
Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages), etc.
@@ -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:
@@ -643,14 +643,14 @@ They work like this:
## Summary
-The `try..catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it.
+The `try...catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it.
The syntax is:
```js
try {
// run this code
-} catch(err) {
+} catch (err) {
// if an error happened, then jump here
// err is the error object
} finally {
@@ -658,7 +658,7 @@ try {
}
```
-There may be no `catch` section or no `finally`, so shorter constructs `try..catch` and `try..finally` are also valid.
+There may be no `catch` section or no `finally`, so shorter constructs `try...catch` and `try...finally` are also valid.
Error objects have following properties:
@@ -666,10 +666,10 @@ Error objects have following properties:
- `name` -- the string with error name (error constructor name).
- `stack` (non-standard, but well-supported) -- the stack at the moment of error creation.
-If an error object is not needed, we can omit it by using `catch {` instead of `catch(err) {`.
+If an error object is not needed, we can omit it by using `catch {` instead of `catch (err) {`.
We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter.
*Rethrowing* is a very important pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know.
-Even if we don't have `try..catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`.
+Even if we don't have `try...catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`.
diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md
index ff2e4c529..d28b07439 100644
--- a/1-js/10-error-handling/2-custom-errors/article.md
+++ b/1-js/10-error-handling/2-custom-errors/article.md
@@ -21,9 +21,9 @@ Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it thr
Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field.
-Our `ValidationError` class should inherit from the built-in `Error` class.
+Our `ValidationError` class should inherit from the `Error` class.
-That class is built-in, but here's its approximate code so we can understand what we're extending:
+The `Error` class is built-in, but here's its approximate code so we can understand what we're extending:
```js
// The "pseudocode" for the built-in Error class defined by JavaScript itself
@@ -38,7 +38,7 @@ class Error {
Now let's inherit `ValidationError` from it and try it in action:
-```js run untrusted
+```js run
*!*
class ValidationError extends Error {
*/!*
@@ -117,15 +117,15 @@ We could also look at `err.name`, like this:
// instead of (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...
-```
+```
The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof.
-Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or other unknown ones) should fall through.
+Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (caused by a typo in the code or other unknown reasons) should fall through.
## Further inheritance
-The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age`). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing.
+The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age` instead of a number). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing.
```js run
class ValidationError extends Error {
diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md
index 9d1a260d5..57115a909 100644
--- a/1-js/11-async/01-callbacks/article.md
+++ b/1-js/11-async/01-callbacks/article.md
@@ -28,7 +28,7 @@ function loadScript(src) {
}
```
-It appends to the document the new, dynamically created, tag `
```
-If we really need to make a window-level global variable, we can explicitly assign it to `window` and access as `window.user`. But that's an exception requiring a good reason.
+```smart
+In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`.
+
+Then all scripts will see it, both with `type="module"` and without it.
+
+That said, making such global variables is frowned upon. Please try to avoid them.
+```
### A module code is evaluated only the first time when imported
-If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers.
+If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers.
+
+The one-time evaluation has important consequences, that we should be aware of.
-That has important consequences. Let's look at them using examples:
+Let's see a couple of examples.
First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time:
@@ -133,9 +146,11 @@ import `./alert.js`; // Module is evaluated!
import `./alert.js`; // (shows nothing)
```
-In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable -- export it.
+The second import shows nothing, because the module has already been evaluated.
-Now, a more advanced example.
+There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above.
+
+Now, let's consider a deeper example.
Let's say, a module exports an object:
@@ -160,54 +175,67 @@ import {admin} from './admin.js';
alert(admin.name); // Pete
*!*
-// Both 1.js and 2.js imported the same object
+// Both 1.js and 2.js reference the same admin object
// Changes made in 1.js are visible in 2.js
*/!*
```
-So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that.
+As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`.
+
+That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other importers will see that.
-Such behavior allows us to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready.
+**Such behavior is actually very convenient, because it allows us to *configure* modules.**
-For instance, the `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside:
+In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it.
+
+Here's the classical pattern:
+1. A module exports some means of configuration, e.g. a configuration object.
+2. On the first import we initialize it, write to its properties. The top-level application script may do that.
+3. Further imports use the module.
+
+For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside:
```js
// ð admin.js
-export let admin = { };
+export let config = { };
export function sayHi() {
- alert(`Ready to serve, ${admin.name}!`);
+ alert(`Ready to serve, ${config.user}!`);
}
```
-In `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself:
+Here, `admin.js` exports the `config` object (initially empty, but may have default properties too).
+
+Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`:
```js
// ð init.js
-import {admin} from './admin.js';
-admin.name = "Pete";
+import {config} from './admin.js';
+config.user = "Pete";
```
-Another module can also see `admin.name`:
+...Now the module `admin.js` is configured.
-```js
-// ð other.js
-import {admin, sayHi} from './admin.js';
+Further importers can call it, and it correctly shows the current user:
-alert(admin.name); // *!*Pete*/!*
+```js
+// ð another.js
+import {sayHi} from './admin.js';
sayHi(); // Ready to serve, *!*Pete*/!*!
```
+
### import.meta
The object `import.meta` contains the information about the current module.
-Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML:
+Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML:
```html run height=0
```
@@ -233,7 +261,7 @@ Compare it to non-module scripts, where `this` is a global object:
There are also several browser-specific differences of scripts with `type="module"` compared to regular ones.
-You may want skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser.
+You may want to skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser.
### Module scripts are deferred
@@ -244,7 +272,7 @@ In other words:
- module scripts wait until the HTML document is fully ready (even if they are tiny and load faster than HTML), and then run.
- relative order of scripts is maintained: scripts that go first in the document, execute first.
-As a side-effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them.
+As a side effect, module scripts always "see" the fully loaded HTML-page, including HTML elements below them.
For instance:
@@ -260,7 +288,7 @@ Compare to regular script below:
diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md
index 4bd41a168..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](http://webpack.github.io) and others) bundle modules together and optimize them to speedup loading and remove unused stuff.
-
- Let's say, we added a 3rd-party library `say.js` to our project with many functions:
- ```js
- // ð say.js
- export function sayHi() { ... }
- export function sayBye() { ... }
- export function becomeSilent() { ... }
- ```
+1. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`.
+2. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier.
- Now if we only use one of `say.js` functions in our project:
- ```js
- // ð main.js
- import {sayHi} from './say.js';
- ```
- ...Then the optimizer will see that and remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking".
+```smart header="Don't be afraid to import too much"
+Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also remove unused imports.
-2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`.
-3. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier.
+For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimized bundle.
+```
## Import "as"
@@ -224,7 +213,7 @@ Without `default`, such an export would give an error:
export class { // Error! (non-default export needs a name)
constructor() {}
}
-```
+```
### The "default" name
@@ -321,12 +310,12 @@ export {default as User} from './user.js'; // re-export default
Why would that be needed? Let's see a practical use case.
-Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages), and many modules are just "helpers", for internal use in other package modules.
+Imagine, we're writing a "package": a folder with a lot of modules, with some of the functionality exported outside (tools like NPM allow us to publish and distribute such packages, but we don't have to use them), and many modules are just "helpers", for internal use in other package modules.
The file structure could be like this:
```
auth/
- index.js
+ index.js
user.js
helpers.js
tests/
@@ -337,13 +326,19 @@ auth/
...
```
-We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this:
+We'd like to expose the package functionality via a single entry point.
+
+In other words, a person who would like to use our package, should import only from the "main file" `auth/index.js`.
+
+Like this:
```js
import {login, logout} from 'auth/index.js'
```
-The idea is that outsiders, developers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes.
+The "main file", `auth/index.js` exports all the functionality that we'd like to provide in our package.
+
+The idea is that outsiders, other programmers who use our package, should not meddle with its internal structure, search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes.
As the actual exported functionality is scattered among the package, we can import it into `auth/index.js` and export from it:
@@ -366,19 +361,21 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo
```js
// ð auth/index.js
-// import login/logout and immediately export them
+// re-export login/logout
export {login, logout} from './helpers.js';
-// import default as User and export it
+// re-export the default export as User
export {default as User} from './user.js';
...
```
+The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions.
+
### Re-exporting the default export
The default export needs separate handling when re-exporting.
-Let's say we have `user.js`, and we'd like to re-export class `User` from it:
+Let's say we have `user.js` with the `export default class User` and would like to re-export it:
```js
// ð user.js
@@ -387,19 +384,21 @@ export default class User {
}
```
-1. `export User from './user.js'` won't work. What can go wrong?... But that's a syntax error!
+We can come across two problems with it:
+
+1. `export User from './user.js'` won't work. That would lead to a syntax error.
- To re-export the default export, we have to write `export {default as User}`, as in the example above.
+ To re-export the default export, we have to write `export {default as User}`, as in the example above.
2. `export * from './user.js'` re-exports only named exports, but ignores the default one.
- If we'd like to re-export both named and the default export, then two statements are needed:
+ If we'd like to re-export both named and default exports, then two statements are needed:
```js
export * from './user.js'; // to re-export named exports
export {default} from './user.js'; // to re-export the default export
```
-Such oddities of re-exporting the default export are one of the reasons why some developers don't like them.
+Such oddities of re-exporting a default export are one of the reasons why some developers don't like default exports and prefer named ones.
## Summary
@@ -418,14 +417,14 @@ You can check yourself by reading them and recalling what they mean:
Import:
-- Named exports from module:
+- Importing named exports:
- `import {x [as y], ...} from "module"`
-- Default export:
+- Importing the default export:
- `import x from "module"`
- `import {default as x} from "module"`
-- Everything:
+- Import all:
- `import * as obj from "module"`
-- Import the module (its code runs), but do not assign it to a variable:
+- Import the module (its code runs), but do not assign any of its exports to variables:
- `import "module"`
We can put `import/export` statements at the top or at the bottom of a script, that doesn't matter.
diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md
index 357a57313..9db69cb2f 100644
--- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md
+++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/solution.md
@@ -19,5 +19,5 @@ function wrap(target) {
user = wrap(user);
alert(user.name); // John
-alert(user.age); // ReferenceError: Property doesn't exist "age"
+alert(user.age); // ReferenceError: Property doesn't exist: "age"
```
diff --git a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md
index d7093c0c3..47985e1a7 100644
--- a/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md
+++ b/1-js/99-js-misc/01-proxy/01-error-nonexisting/task.md
@@ -27,6 +27,6 @@ user = wrap(user);
alert(user.name); // John
*!*
-alert(user.age); // ReferenceError: Property doesn't exist "age"
+alert(user.age); // ReferenceError: Property doesn't exist: "age"
*/!*
```
diff --git a/1-js/99-js-misc/01-proxy/article.md b/1-js/99-js-misc/01-proxy/article.md
index 3f7ef63b8..1f84912e5 100644
--- a/1-js/99-js-misc/01-proxy/article.md
+++ b/1-js/99-js-misc/01-proxy/article.md
@@ -39,7 +39,7 @@ As there are no traps, all operations on `proxy` are forwarded to `target`.
As we can see, without any traps, `proxy` is a transparent wrapper around `target`.
-
+
`Proxy` is a special "exotic object". It doesn't have own properties. With an empty `handler` it transparently forwards operations to `target`.
@@ -61,13 +61,13 @@ For every internal method, there's a trap in this table: the name of the method
| `[[Delete]]` | `deleteProperty` | `delete` operator |
| `[[Call]]` | `apply` | function call |
| `[[Construct]]` | `construct` | `new` operator |
-| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) |
-| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) |
-| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) |
-| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) |
-| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) |
-| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` |
-| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object/keys/values/entries` |
+| `[[GetPrototypeOf]]` | `getPrototypeOf` | [Object.getPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/getPrototypeOf) |
+| `[[SetPrototypeOf]]` | `setPrototypeOf` | [Object.setPrototypeOf](mdn:/JavaScript/Reference/Global_Objects/Object/setPrototypeOf) |
+| `[[IsExtensible]]` | `isExtensible` | [Object.isExtensible](mdn:/JavaScript/Reference/Global_Objects/Object/isExtensible) |
+| `[[PreventExtensions]]` | `preventExtensions` | [Object.preventExtensions](mdn:/JavaScript/Reference/Global_Objects/Object/preventExtensions) |
+| `[[DefineOwnProperty]]` | `defineProperty` | [Object.defineProperty](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperty), [Object.defineProperties](mdn:/JavaScript/Reference/Global_Objects/Object/defineProperties) |
+| `[[GetOwnProperty]]` | `getOwnPropertyDescriptor` | [Object.getOwnPropertyDescriptor](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor), `for..in`, `Object.keys/values/entries` |
+| `[[OwnPropertyKeys]]` | `ownKeys` | [Object.getOwnPropertyNames](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames), [Object.getOwnPropertySymbols](mdn:/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols), `for..in`, `Object.keys/values/entries` |
```warn header="Invariants"
JavaScript enforces some invariants -- conditions that must be fulfilled by internal methods and traps.
@@ -335,7 +335,7 @@ let user = {
_password: "secret"
};
-alert(user._password); // secret
+alert(user._password); // secret
```
Let's use proxies to prevent any access to properties starting with `_`.
@@ -376,7 +376,7 @@ user = new Proxy(user, {
},
*!*
deleteProperty(target, prop) { // to intercept property deletion
-*/!*
+*/!*
if (prop.startsWith('_')) {
throw new Error("Access denied");
} else {
@@ -437,7 +437,7 @@ user = {
```
-A call to `user.checkPassword()` call gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error.
+A call to `user.checkPassword()` gets proxied `user` as `this` (the object before dot becomes `this`), so when it tries to access `this._password`, the `get` trap activates (it triggers on any property read) and throws an error.
So we bind the context of object methods to the original object, `target`, in the line `(*)`. Then their future calls will use `target` as `this`, without any traps.
@@ -963,9 +963,13 @@ revoke();
alert(proxy.data); // Error
```
-A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected. The target object can be garbage-collected after that.
+A call to `revoke()` removes all internal references to the target object from the proxy, so they are no longer connected.
+
+Initially, `revoke` is separate from `proxy`, so that we can pass `proxy` around while leaving `revoke` in the current scope.
-We can also store `revoke` in a `WeakMap`, to be able to easily find it by a proxy object:
+We can also bind `revoke` method to proxy by setting `proxy.revoke = revoke`.
+
+Another option is to create a `WeakMap` that has `proxy` as the key and the corresponding `revoke` as the value, that allows to easily find `revoke` for a proxy:
```js run
*!*
@@ -980,21 +984,19 @@ let {proxy, revoke} = Proxy.revocable(object, {});
revokes.set(proxy, revoke);
-// ..later in our code..
+// ..somewhere else in our code..
revoke = revokes.get(proxy);
revoke();
alert(proxy.data); // Error (revoked)
```
-The benefit of such an approach is that we don't have to carry `revoke` around. We can get it from the map by `proxy` when needed.
-
We use `WeakMap` instead of `Map` here because it won't block garbage collection. If a proxy object becomes "unreachable" (e.g. no variable references it any more), `WeakMap` allows it to be wiped from memory together with its `revoke` that we won't need any more.
## References
- Specification: [Proxy](https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots).
-- MDN: [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
+- MDN: [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy).
## Summary
@@ -1016,13 +1018,13 @@ We can trap:
- Reading (`get`), writing (`set`), deleting (`deleteProperty`) a property (even a non-existing one).
- Calling a function (`apply` trap).
- The `new` operator (`construct` trap).
-- Many other operations (the full list is at the beginning of the article and in the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)).
+- Many other operations (the full list is at the beginning of the article and in the [docs](mdn:/JavaScript/Reference/Global_Objects/Proxy)).
That allows us to create "virtual" properties and methods, implement default values, observable objects, function decorators and so much more.
We can also wrap an object multiple times in different proxies, decorating it with various aspects of functionality.
-The [Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects.
+The [Reflect](mdn:/JavaScript/Reference/Global_Objects/Reflect) API is designed to complement [Proxy](mdn:/JavaScript/Reference/Global_Objects/Proxy). For any `Proxy` trap, there's a `Reflect` call with same arguments. We should use those to forward calls to target objects.
Proxies have some limitations:
diff --git a/1-js/99-js-misc/03-currying-partials/article.md b/1-js/99-js-misc/03-currying-partials/article.md
index bb308847c..d71ac23f8 100644
--- a/1-js/99-js-misc/03-currying-partials/article.md
+++ b/1-js/99-js-misc/03-currying-partials/article.md
@@ -155,7 +155,7 @@ function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
- return function pass(...args2) { // (2)
+ return function(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
@@ -164,18 +164,10 @@ function curried(...args) {
When we run it, there are two `if` execution branches:
-1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it.
-2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result.
+1. If passed `args` count is the same or more than the original function has in its definition (`func.length`) , then just pass the call to it using `func.apply`.
+2. Otherwise, get a partial: we don't call `func` just yet. Instead, another wrapper is returned, that will re-apply `curried` providing previous arguments together with the new ones.
-For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`.
-
-For the call `curried(1)(2)(3)`:
-
-1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`.
-2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together. As the argument count is still less than 3, `curry` returns `pass`.
-3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function.
-
-If that's still not obvious, just trace the calls sequence in your mind or on paper.
+Then, if we call it, again, we'll get either a new partial (if not enough arguments) or, finally, the result.
```smart header="Fixed-length functions only"
The currying requires the function to have a fixed number of arguments.
diff --git a/1-js/99-js-misc/04-reference-type/3-why-this/solution.md b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md
index 31ea4ff88..e4ee78748 100644
--- a/1-js/99-js-misc/04-reference-type/3-why-this/solution.md
+++ b/1-js/99-js-misc/04-reference-type/3-why-this/solution.md
@@ -5,7 +5,7 @@ Here's the explanations.
2. The same, parentheses do not change the order of operations here, the dot is first anyway.
-3. Here we have a more complex call `(expression).method()`. The call works as if it were split into two lines:
+3. Here we have a more complex call `(expression)()`. The call works as if it were split into two lines:
```js no-beautify
f = obj.go; // calculate the expression
@@ -14,7 +14,7 @@ Here's the explanations.
Here `f()` is executed as a function, without `this`.
-4. The similar thing as `(3)`, to the left of the dot `.` we have an expression.
+4. The similar thing as `(3)`, to the left of the parentheses `()` we have an expression.
To explain the behavior of `(3)` and `(4)` we need to recall that property accessors (dot or square brackets) return a value of the Reference Type.
diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md
index c680c17f9..894db8fc6 100644
--- a/1-js/99-js-misc/04-reference-type/article.md
+++ b/1-js/99-js-misc/04-reference-type/article.md
@@ -4,7 +4,7 @@
```warn header="In-depth language feature"
This article covers an advanced topic, to understand certain edge-cases better.
-It's not important. Many experienced developers live fine without knowing it. Read on if you're want to know how things work under the hood.
+It's not important. Many experienced developers live fine without knowing it. Read on if you want to know how things work under the hood.
```
A dynamically evaluated method call can lose `this`.
@@ -59,7 +59,7 @@ If we put these operations on separate lines, then `this` will be lost for sure:
let user = {
name: "John",
hi() { alert(this.name); }
-}
+};
*!*
// split getting and calling the method in two lines
@@ -87,13 +87,13 @@ 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 `()`.
Any other operation like assignment `hi = user.hi` discards the reference type as a whole, takes the value of `user.hi` (a function) and passes it on. So any further operation "loses" `this`.
-So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). Later in this tutorial, we will learn various ways to solve this problem such as [func.bind()](/bind#solution-2-bind).
+So, as the result, the value of `this` is only passed the right way if the function is called directly using a dot `obj.method()` or square brackets `obj['method']()` syntax (they do the same here). There are various ways to solve this problem such as [func.bind()](/bind#solution-2-bind).
## Summary
diff --git a/1-js/99-js-misc/05-bigint/article.md b/1-js/99-js-misc/05-bigint/article.md
index 062dd6017..2a1cfc843 100644
--- a/1-js/99-js-misc/05-bigint/article.md
+++ b/1-js/99-js-misc/05-bigint/article.md
@@ -126,5 +126,5 @@ We can use such JSBI code "as is" for engines that don't support bigints and for
## References
-- [MDN docs on BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt).
+- [MDN docs on BigInt](mdn:/JavaScript/Reference/Global_Objects/BigInt).
- [Specification](https://tc39.es/ecma262/#sec-bigint-objects).
diff --git a/1-js/99-js-misc/06-unicode/article.md b/1-js/99-js-misc/06-unicode/article.md
new file mode 100644
index 000000000..4f144f824
--- /dev/null
+++ b/1-js/99-js-misc/06-unicode/article.md
@@ -0,0 +1,172 @@
+
+# Unicode, String internals
+
+```warn header="Advanced knowledge"
+The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters, or other rare symbols.
+```
+
+As we already know, JavaScript strings are based on [Unicode](https://en.wikipedia.org/wiki/Unicode): each character is represented by a byte sequence of 1-4 bytes.
+
+JavaScript allows us to insert a character into a string by specifying its hexadecimal Unicode code with one of these three notations:
+
+- `\xXX`
+
+ `XX` must be two hexadecimal digits with a value between `00` and `FF`, then `\xXX` is the character whose Unicode code is `XX`.
+
+ Because the `\xXX` notation supports only two hexadecimal digits, it can be used only for the first 256 Unicode characters.
+
+ These first 256 characters include the Latin alphabet, most basic syntax characters, and some others. For example, `"\x7A"` is the same as `"z"` (Unicode `U+007A`).
+
+ ```js run
+ alert( "\x7A" ); // z
+ alert( "\xA9" ); // ©, the copyright symbol
+ ```
+
+- `\uXXXX`
+ `XXXX` must be exactly 4 hex digits with the value between `0000` and `FFFF`, then `\uXXXX` is the character whose Unicode code is `XXXX`.
+
+ Characters with Unicode values greater than `U+FFFF` can also be represented with this notation, but in this case, we will need to use a so called surrogate pair (we will talk about surrogate pairs later in this chapter).
+
+ ```js run
+ alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation
+ alert( "\u044F" ); // Ñ, the Cyrillic alphabet letter
+ alert( "\u2191" ); // â, the arrow up symbol
+ ```
+
+- `\u{Xâ¦XXXXXX}`
+
+ `Xâ¦XXXXXX` must be a hexadecimal value of 1 to 6 bytes between `0` and `10FFFF` (the highest code point defined by Unicode). This notation allows us to easily represent all existing Unicode characters.
+
+ ```js run
+ alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode)
+ alert( "\u{1F60D}" ); // ð, a smiling face symbol (another long Unicode)
+ ```
+
+## Surrogate pairs
+
+All frequently used characters have 2-byte codes (4 hex digits). Letters in most European languages, numbers, and the basic unified CJK ideographic sets (CJK -- from Chinese, Japanese, and Korean writing systems), have a 2-byte representation.
+
+Initially, JavaScript was based on UTF-16 encoding that only allowed 2 bytes per character. But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol of Unicode.
+
+So rare symbols that require more than 2 bytes are encoded with a pair of 2-byte characters called "a surrogate pair".
+
+As a side effect, the length of such symbols is `2`:
+
+```js run
+alert( 'ð³'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X
+alert( 'ð'.length ); // 2, FACE WITH TEARS OF JOY
+alert( 'ð©·¶'.length ); // 2, a rare Chinese character
+```
+
+That's because surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language!
+
+We actually have a single symbol in each of the strings above, but the `length` property shows a length of `2`.
+
+Getting a symbol can also be tricky, because most language features treat surrogate pairs as two characters.
+
+For example, here we can see two odd characters in the output:
+
+```js run
+alert( 'ð³'[0] ); // shows strange symbols...
+alert( 'ð³'[1] ); // ...pieces of the surrogate pair
+```
+
+Pieces of a surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage.
+
+Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard.
+
+So the methods [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) and [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) were added in JavaScript to deal with surrogate pairs.
+
+They are essentially the same as [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt), but they treat surrogate pairs correctly.
+
+One can see the difference here:
+
+```js run
+// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of ð³:
+
+alert( 'ð³'.charCodeAt(0).toString(16) ); // d835
+
+// codePointAt is surrogate-pair aware
+alert( 'ð³'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair
+```
+
+That said, if we take from position 1 (and that's rather incorrect here), then they both return only the 2nd part of the pair:
+
+```js run
+alert( 'ð³'.charCodeAt(1).toString(16) ); // dcb3
+alert( 'ð³'.codePointAt(1).toString(16) ); // dcb3
+// meaningless 2nd half of the pair
+```
+
+You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here.
+
+````warn header="Takeaway: splitting strings at an arbitrary point is dangerous"
+We can't just split a string at an arbitrary position, e.g. take `str.slice(0, 4)` and expect it to be a valid string, e.g.:
+
+```js run
+alert( 'hi ð'.slice(0, 4) ); // hi [?]
+```
+
+Here we can see a garbage character (first half of the smile surrogate pair) in the output.
+
+Just be aware of it if you intend to reliably work with surrogate pairs. May not be a big problem, but at least you should understand what happens.
+````
+
+## Diacritical marks and normalization
+
+In many languages, there are symbols that are composed of the base character with a mark above/under it.
+
+For instance, the letter `a` can be the base character for these characters: `à áâäãåÄ`.
+
+Most common "composite" characters have their own code in the Unicode table. But not all of them, because there are too many possible combinations.
+
+To support arbitrary compositions, the Unicode standard allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it.
+
+For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as SÌ.
+
+```js run
+alert( 'S\u0307' ); // SÌ
+```
+
+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": `SÌÌ£`.
+
+For example:
+
+```js run
+alert( 'S\u0307\u0323' ); // SÌÌ£
+```
+
+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ÌÌ£, S + dot above + dot below
+let s2 = 'S\u0323\u0307'; // SÌ£Ì, 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:
+
+
+
+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":
+
+
+
+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:
+
+
+
+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:
+
+
+
+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":
+
+ 
+
+
+ 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:
+
+
+
+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:
+
+
+
+
+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.
+
+
+
+
+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:
+
+
+
+
+
+
+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:
+
+
+
+
+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:
+
+
+
+
+
+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:
+
+
+
+
+
+Let's "play around" a little more, by replacing one of the images again and creating a new collage:
+
+
+
+
+
+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?
+
+
+
+
+
+
+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
+
+
+
+
+
+
Start sending 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
+
+
+
+
+
+
+
+
+
+
+
+
+ Create collage
+ Start over
+ Download
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js
new file mode 100644
index 000000000..983b34d9a
--- /dev/null
+++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js
@@ -0,0 +1,228 @@
+import {
+ createImageFile,
+ loadImage,
+ weakRefCache,
+ LAYOUTS,
+ images,
+ THUMBNAIL_PARAMS,
+ stateObj,
+} from "./utils.js";
+
+export const state = new Proxy(stateObj, {
+ set(target, property, value) {
+ const previousValue = target[property];
+
+ target[property] = value;
+
+ if (previousValue !== value) {
+ handleStateChange(target);
+ }
+
+ return true;
+ },
+});
+
+// Elements.
+const thumbnailsContainerEl = document.querySelector(".thumbnails-container");
+const selectEl = document.querySelector(".select");
+const previewContainerEl = document.querySelector(".previewContainer");
+const canvasEl = document.querySelector(".canvas");
+const createCollageBtn = document.querySelector(".btn-create-collage");
+const startOverBtn = document.querySelector(".btn-start-over");
+const downloadBtn = document.querySelector(".btn-download");
+const spinnerContainerEl = document.querySelector(".spinnerContainer");
+const spinnerTextEl = document.querySelector(".spinnerText");
+const loggerContainerEl = document.querySelector(".loggerContainer");
+
+// Renders.
+// Render thumbnails previews.
+images.forEach((img) => {
+ const thumbnail = document.createElement("div");
+ thumbnail.classList.add("thumbnail-item");
+
+ thumbnail.innerHTML = `
+
+ `;
+
+ thumbnail.addEventListener("click", (e) => handleSelection(e, img));
+
+ thumbnailsContainerEl.appendChild(thumbnail);
+});
+// Render layouts select.
+LAYOUTS.forEach((layout) => {
+ const option = document.createElement("option");
+ option.value = JSON.stringify(layout);
+ option.innerHTML = layout.name;
+ selectEl.appendChild(option);
+});
+
+const handleStateChange = (state) => {
+ if (state.loading) {
+ selectEl.disabled = true;
+ createCollageBtn.disabled = true;
+ startOverBtn.disabled = true;
+ downloadBtn.disabled = true;
+ previewContainerEl.classList.add("previewContainer--disabled");
+ spinnerContainerEl.classList.remove("spinnerContainer--hidden");
+ spinnerTextEl.innerText = "Loading...";
+ canvasEl.classList.remove("canvas--ready");
+ } else if (!state.loading) {
+ selectEl.disabled = false;
+ createCollageBtn.disabled = false;
+ startOverBtn.disabled = false;
+ downloadBtn.disabled = false;
+ previewContainerEl.classList.remove("previewContainer--disabled");
+ spinnerContainerEl.classList.add("spinnerContainer--hidden");
+ canvasEl.classList.add("canvas--ready");
+ }
+
+ if (!state.selectedImages.size) {
+ createCollageBtn.disabled = true;
+ document.querySelectorAll(".badge").forEach((item) => item.remove());
+ } else if (state.selectedImages.size && !state.loading) {
+ createCollageBtn.disabled = false;
+ }
+
+ if (!state.collageRendered) {
+ downloadBtn.disabled = true;
+ } else if (state.collageRendered) {
+ downloadBtn.disabled = false;
+ }
+};
+handleStateChange(state);
+
+const handleSelection = (e, imgName) => {
+ const imgEl = e.currentTarget;
+
+ imgEl.classList.toggle("thumbnail-item--selected");
+
+ if (state.selectedImages.has(imgName)) {
+ state.selectedImages.delete(imgName);
+ state.selectedImages = new Set(state.selectedImages);
+ imgEl.querySelector(".badge")?.remove();
+ } else {
+ state.selectedImages = new Set(state.selectedImages.add(imgName));
+
+ const badge = document.createElement("div");
+ badge.classList.add("badge");
+ badge.innerHTML = `
+
+ `;
+ imgEl.prepend(badge);
+ }
+};
+
+// Make a wrapper function.
+let getCachedImage;
+(async () => {
+ getCachedImage = await weakRefCache(loadImage);
+})();
+
+const calculateGridRows = (blobsLength) =>
+ Math.ceil(blobsLength / state.currentLayout.columns);
+
+const drawCollage = (images) => {
+ state.drawing = true;
+
+ let context = canvasEl.getContext("2d");
+
+ /**
+ * Calculate canvas dimensions based on the current layout.
+ * */
+ context.canvas.width =
+ state.currentLayout.itemWidth * state.currentLayout.columns;
+ context.canvas.height =
+ calculateGridRows(images.length) * state.currentLayout.itemHeight;
+
+ let currentRow = 0;
+ let currentCanvasDx = 0;
+ let currentCanvasDy = 0;
+
+ for (let i = 0; i < images.length; i++) {
+ /**
+ * Get current row of the collage.
+ * */
+ if (i % state.currentLayout.columns === 0) {
+ currentRow += 1;
+ currentCanvasDx = 0;
+
+ if (currentRow > 1) {
+ currentCanvasDy += state.currentLayout.itemHeight;
+ }
+ }
+
+ context.drawImage(
+ images[i],
+ 0,
+ 0,
+ images[i].width,
+ images[i].height,
+ currentCanvasDx,
+ currentCanvasDy,
+ state.currentLayout.itemWidth,
+ state.currentLayout.itemHeight,
+ );
+
+ currentCanvasDx += state.currentLayout.itemWidth;
+ }
+
+ state.drawing = false;
+ state.collageRendered = true;
+};
+
+const createCollage = async () => {
+ state.loading = true;
+
+ const images = [];
+
+ for (const image of state.selectedImages.values()) {
+ const blobImage = await getCachedImage(image.img);
+
+ const url = URL.createObjectURL(blobImage);
+ const img = await createImageFile(url);
+
+ images.push(img);
+ URL.revokeObjectURL(url);
+ }
+
+ state.loading = false;
+
+ drawCollage(images);
+};
+
+/**
+ * Clear all settled data to start over.
+ * */
+const startOver = () => {
+ state.selectedImages = new Set();
+ state.collageRendered = false;
+ const context = canvasEl.getContext("2d");
+ context.clearRect(0, 0, canvasEl.width, canvasEl.height);
+
+ document
+ .querySelectorAll(".thumbnail-item--selected")
+ .forEach((item) => item.classList.remove("thumbnail-item--selected"));
+
+ loggerContainerEl.innerHTML = 'Logger:
';
+};
+
+const downloadCollage = () => {
+ const date = new Date();
+ const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`;
+ const img = canvasEl.toDataURL("image/png");
+ const link = document.createElement("a");
+ link.download = fileName;
+ link.href = img;
+ link.click();
+ link.remove();
+};
+
+const changeLayout = ({ target }) => {
+ state.currentLayout = JSON.parse(target.value);
+};
+
+// Listeners.
+selectEl.addEventListener("change", changeLayout);
+createCollageBtn.addEventListener("click", createCollage);
+startOverBtn.addEventListener("click", startOver);
+downloadBtn.addEventListener("click", downloadCollage);
diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js
new file mode 100644
index 000000000..f0140c116
--- /dev/null
+++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js
@@ -0,0 +1,321 @@
+const loggerContainerEl = document.querySelector(".loggerContainer");
+
+export const images = [
+ {
+ img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1533105079780-92b9be482077",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1518684079-3c830dcef090",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1505832018823-50331d70d237",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1499363536502-87642509e31b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1545389336-cf090694435e",
+ },
+ {
+ img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1506125840744-167167210587",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1510662145379-13537db782dc",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1573790387438-4da905039392",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1512757776214-26d36777b513",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7",
+ },
+ {
+ img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a",
+ },
+];
+export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format";
+
+// Console styles.
+export const CONSOLE_BASE_STYLES = [
+ "font-size: 12px",
+ "padding: 4px",
+ "border: 2px solid #5a5a5a",
+ "color: white",
+].join(";");
+export const CONSOLE_PRIMARY = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #13315a",
+].join(";");
+export const CONSOLE_SUCCESS = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #385a4e",
+].join(";");
+export const CONSOLE_ERROR = [
+ CONSOLE_BASE_STYLES,
+ "background-color: #5a1a24",
+].join(";");
+
+// Layouts.
+export const LAYOUT_4_COLUMNS = {
+ name: "Layout 4 columns",
+ columns: 4,
+ itemWidth: 240,
+ itemHeight: 240,
+};
+export const LAYOUT_8_COLUMNS = {
+ name: "Layout 8 columns",
+ columns: 8,
+ itemWidth: 240,
+ itemHeight: 240,
+};
+export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS];
+
+export const createImageFile = async (src) =>
+ new Promise((resolve, reject) => {
+ const img = new Image();
+ img.src = src;
+ img.onload = () => resolve(img);
+ img.onerror = () => reject(new Error("Failed to construct image."));
+ });
+
+export const loadImage = async (url) => {
+ try {
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error(String(response.status));
+ }
+
+ return await response.blob();
+ } catch (e) {
+ console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR);
+ }
+};
+
+export const weakRefCache = (fetchImg) => {
+ const imgCache = new Map();
+ const registry = new FinalizationRegistry(({ imgName, size, type }) => {
+ const cachedImg = imgCache.get(imgName);
+ if (cachedImg && !cachedImg.deref()) {
+ imgCache.delete(imgName);
+ console.log(
+ `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`,
+ CONSOLE_ERROR,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--error");
+ logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+ }
+ });
+
+ return async (imgName) => {
+ const cachedImg = imgCache.get(imgName);
+
+ if (cachedImg?.deref() !== undefined) {
+ console.log(
+ `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`,
+ CONSOLE_SUCCESS,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--success");
+ logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+
+ return cachedImg?.deref();
+ }
+
+ const newImg = await fetchImg(imgName);
+ console.log(
+ `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`,
+ CONSOLE_PRIMARY,
+ );
+
+ const logEl = document.createElement("div");
+ logEl.classList.add("logger-item", "logger--primary");
+ logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`;
+ loggerContainerEl.appendChild(logEl);
+ loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight;
+
+ imgCache.set(imgName, new WeakRef(newImg));
+ registry.register(newImg, {
+ imgName,
+ size: newImg.size,
+ type: newImg.type,
+ });
+
+ return newImg;
+ };
+};
+
+export const stateObj = {
+ loading: false,
+ drawing: true,
+ collageRendered: false,
+ currentLayout: LAYOUTS[0],
+ selectedImages: new Set(),
+};
diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md
index 56b568833..eedc28fb3 100644
--- a/2-ui/1-document/01-browser-environment/article.md
+++ b/2-ui/1-document/01-browser-environment/article.md
@@ -1,10 +1,10 @@
# Browser environment, specs
-The JavaScript language was initially created for web browsers. Since then it has evolved and become a language with many uses and platforms.
+The JavaScript language was initially created for web browsers. Since then, it has evolved into a language with many uses and platforms.
-A platform may be a browser, or a web-server or another *host*, even a "smart" coffee machine, if it can run JavaScript. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*.
+A platform may be a browser, or a web-server or another *host*, or even a "smart" coffee machine if it can run JavaScript. Each of these provides platform-specific functionality. The JavaScript specification calls that a *host environment*.
-A host environment provides own objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on.
+A host environment provides its own objects and functions in addition to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on.
Here's a bird's-eye view of what we have when JavaScript runs in a web browser:
@@ -15,9 +15,9 @@ There's a "root" object called `window`. It has two roles:
1. First, it is a global object for JavaScript code, as described in the chapter .
2. Second, it represents the "browser window" and provides methods to control it.
-For instance, here we use it as a global object:
+For instance, we can use it as a global object:
-```js run
+```js run global
function sayHi() {
alert("Hello");
}
@@ -26,17 +26,17 @@ function sayHi() {
window.sayHi();
```
-And here we use it as a browser window, to see the window height:
+And we can use it as a browser window, to show the window height:
```js run
alert(window.innerHeight); // inner window height
```
-There are more window-specific methods and properties, we'll cover them later.
+There are more window-specific methods and properties, which we'll cover later.
## DOM (Document Object Model)
-Document Object Model, or DOM for short, represents all page content as objects that can be modified.
+The Document Object Model, or DOM for short, represents all page content as objects that can be modified.
The `document` object is the main "entry point" to the page. We can change or create anything on the page using it.
@@ -49,18 +49,18 @@ document.body.style.background = "red";
setTimeout(() => document.body.style.background = "", 1000);
```
-Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org).
+Here, we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org).
```smart header="DOM is not only for browsers"
The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use DOM too.
-For instance, server-side scripts that download HTML pages and process them can also use DOM. They may support only a part of the specification though.
+For instance, server-side scripts that download HTML pages and process them can also use the DOM. They may support only a part of the specification though.
```
```smart header="CSSOM for styling"
There's also a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/) for CSS rules and stylesheets, that explains how they are represented as objects, and how to read and write them.
-CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible.
+The CSSOM is used together with the DOM when we modify style rules for the document. In practice though, the CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible.
```
## BOM (Browser Object Model)
@@ -69,7 +69,7 @@ The Browser Object Model (BOM) represents additional objects provided by the bro
For instance:
-- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc).
+- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differentiate between Windows/Linux/Mac etc).
- The [location](mdn:api/Window/location) object allows us to read the current URL and can redirect the browser to a new one.
Here's how we can use the `location` object:
@@ -81,12 +81,12 @@ if (confirm("Go to Wikipedia?")) {
}
```
-Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user.
+The functions `alert/confirm/prompt` are also a part of the BOM: they are not directly related to the document, but represent pure browser methods for communicating with the user.
```smart header="Specifications"
-BOM is the part of the general [HTML specification](https://html.spec.whatwg.org).
+The BOM is a part of the general [HTML specification](https://html.spec.whatwg.org).
-Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at .
+Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods, and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at .
```
## Summary
@@ -94,20 +94,20 @@ Yes, you heard that right. The HTML spec at is no
Talking about standards, we have:
DOM specification
-: Describes the document structure, manipulations and events, see .
+: Describes the document structure, manipulations, and events, see .
CSSOM specification
-: Describes stylesheets and style rules, manipulations with them and their binding to documents, see .
+: Describes stylesheets and style rules, manipulations with them, and their binding to documents, see .
HTML specification
: Describes the HTML language (e.g. tags) and also the BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see . It takes the DOM specification and extends it with many additional properties and methods.
Additionally, some classes are described separately at .
-Please note these links, as there's so much stuff to learn it's impossible to cover and remember everything.
+Please note these links, as there's so much to learn that it's impossible to cover everything and remember it all.
-When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete.
+When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete.
To find something, it's often convenient to use an internet search "WHATWG [term]" or "MDN [term]", e.g , .
-Now we'll get down to learning DOM, because the document plays the central role in the UI.
+Now, we'll get down to learning the DOM, because the document plays the central role in the UI.
diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md
index 019398be9..f7f2be91d 100644
--- a/2-ui/1-document/02-dom-nodes/article.md
+++ b/2-ui/1-document/02-dom-nodes/article.md
@@ -51,7 +51,7 @@ The DOM represents HTML as a tree structure of tags. Here's how it looks:
@@ -143,7 +143,7 @@ drawHtmlTree(node4, 'div.domtree', 690, 360);
````warn header="Tables always have ``"
-An interesting "special case" is tables. By the DOM specification they must have ` `, but HTML text may (officially) omit it. Then the browser creates ` ` in the DOM automatically.
+An interesting "special case" is tables. By DOM specification they must have ` ` tag, but HTML text may omit it. Then the browser creates ` ` in the DOM automatically.
For the HTML:
@@ -160,7 +160,7 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType":
drawHtmlTree(node5, 'div.domtree', 600, 200);
-You see? The ` ` appeared out of nowhere. You should keep this in mind while working with tables to avoid surprises.
+You see? The ` ` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises.
````
## Other node types
@@ -188,7 +188,7 @@ For example, comments:
@@ -199,7 +199,7 @@ We may think -- why is a comment added to the DOM? It doesn't affect the visual
**Everything in HTML, even comments, becomes a part of the DOM.**
-Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. We are not going to touch that node, we even don't draw it on diagrams for that reason, but it's there.
+Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there.
The `document` object that represents the whole document is, formally, a DOM node as well.
@@ -212,7 +212,7 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu
## See it for yourself
-To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant.
+To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant.
Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing.
diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md
index f7123d70d..b5f03098c 100644
--- a/2-ui/1-document/03-dom-navigation/article.md
+++ b/2-ui/1-document/03-dom-navigation/article.md
@@ -214,7 +214,7 @@ alert( document.body.previousSibling ); // HTMLHeadElement
## Element-only navigation
-Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist.
+Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist.
But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page.
diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md
index f5ab0b785..405129694 100644
--- a/2-ui/1-document/04-searching-elements-dom/article.md
+++ b/2-ui/1-document/04-searching-elements-dom/article.md
@@ -55,7 +55,7 @@ Also, there's a global variable named by `id` that references the element:
```
```warn header="Please don't use id-named global variables to access elements"
-This behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), so it's kind of standard. But it is supported mainly for compatibility.
+This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility.
The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from.
@@ -71,7 +71,7 @@ If there are multiple elements with the same `id`, then the behavior of methods
```
```warn header="Only `document.getElementById`, not `anyElem.getElementById`"
-The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document.
+The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document.
```
## querySelectorAll [#querySelectorAll]
@@ -116,7 +116,7 @@ In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but t
Previous methods were searching the DOM.
-The [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`.
+The [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`.
The method comes in handy when we are iterating over elements (like in an array or something) and trying to filter out those that interest us.
@@ -142,7 +142,7 @@ For instance:
*Ancestors* of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top.
-The method `elem.closest(css)` looks the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search.
+The method `elem.closest(css)` looks for the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search.
In other words, the method `closest` goes up from the element and checks each of parents. If it matches the selector, then the search stops, and the ancestor is returned.
@@ -154,7 +154,7 @@ For instance:
Chapter 1
- Chapter 1
+ Chapter 2
@@ -363,7 +363,7 @@ There are 6 main methods to search for nodes in DOM:
-By far the most used are `querySelector` and `querySelectorAll`, but `getElementBy*` can be sporadically helpful or found in the old scripts.
+By far the most used are `querySelector` and `querySelectorAll`, but `getElement(s)By*` can be sporadically helpful or found in the old scripts.
Besides that:
diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md
index 76469c187..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
@@ -10,7 +10,7 @@ Different DOM nodes may have different properties. For instance, an element node
Each DOM node belongs to the corresponding built-in class.
-The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it.
+The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](https://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it.
Here's the picture, explanations to follow:
@@ -18,16 +18,39 @@ Here's the picture, explanations to follow:
The classes are:
-- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later.
-- [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes.
-- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. A browser supports not only HTML, but also XML and SVG. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`.
-- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by concrete HTML elements:
+- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class for everything.
+
+ Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later.
+
+- [Node](https://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes.
+
+ It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are other classes that inherit from it (and so inherit the `Node` functionality).
+
+- [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole.
+
+ The `document` global object belongs exactly to this class. It serves as an entry point to the DOM.
+
+- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by:
+ - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `Hello
`.
+ - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- the class for comments. They are not shown, but each comment becomes a member of DOM.
+
+- [Element](https://dom.spec.whatwg.org/#interface-element) -- is the base class for DOM elements.
+
+ It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`.
+
+ A browser supports not only HTML, but also XML and SVG. So the `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` (we don't need them here) and `HTMLElement`.
+
+- Finally, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is the basic class for all HTML elements. We'll work with it most of the time.
+
+ It is inherited by concrete HTML elements:
- [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for ` ` elements,
- [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `` elements,
- [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `` elements,
- - ...and so on, each tag has its own class that may provide specific properties and methods.
+ - ...and so on.
+
+There are many other tags with their own classes that may have specific properties and methods, while some elements, such as ``, ``, `` do not have any specific properties, so they are instances of `HTMLElement` class.
-So, the full set of properties and methods of a given node comes as the result of the inheritance.
+So, the full set of properties and methods of a given node comes as the result of the chain of inheritance.
For example, let's consider the DOM object for an ` ` element. It belongs to [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class.
@@ -128,13 +151,13 @@ For instance:
```html run
-
```
-But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back:
+But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back:
```html run
@@ -298,7 +298,7 @@ For instance, here for the order state the attribute `order-state` is used:
```
-Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`?
+Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `.order-state-canceled`?
Because an attribute is more convenient to manage. The state can be changed as easy as:
diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md
index e127bc0ef..40c75dff3 100644
--- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md
+++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md
@@ -6,7 +6,7 @@ importance: 5
We have an empty DOM element `elem` and a string `text`.
-Which of these 3 commands do exactly the same?
+Which of these 3 commands will do exactly the same?
1. `elem.append(document.createTextNode(text))`
2. `elem.innerHTML = text`
diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md
index 15238fcf4..1414e90c1 100644
--- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md
+++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md
@@ -39,15 +39,19 @@ The clock-managing functions:
```js
let timerId;
-function clockStart() { // run the clock
- timerId = setInterval(update, 1000);
+function clockStart() { // run the clock
+ if (!timerId) { // only set a new interval if the clock is not running
+ timerId = setInterval(update, 1000);
+ }
update(); // (*)
}
function clockStop() {
clearInterval(timerId);
- timerId = null;
+ timerId = null; // (**)
}
```
Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then.
+
+Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`.
diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html
index 1bf642b10..84ee26f19 100644
--- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html
+++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html
@@ -43,15 +43,19 @@
}
function clockStart() {
- timerId = setInterval(update, 1000);
+ // set a new interval only if the clock is stopped
+ // otherwise we would rewrite the timerID reference to the running interval and wouldn't be able to stop the clock ever again
+ if (!timerId) {
+ timerId = setInterval(update, 1000);
+ }
update(); // <-- start right now, don't wait 1 second till the first setInterval works
}
function clockStop() {
clearInterval(timerId);
+ timerId = null; // <-- clear timerID to indicate that the clock has been stopped, so that it is possible to start it again in clockStart()
}
- clockStart();
diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md
index 6b85168b9..3d1f6698f 100644
--- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md
+++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md
@@ -1,9 +1,9 @@
The HTML in the task is incorrect. That's the reason of the odd thing.
-The browser has to fix it automatically. But there may be no text inside the ``: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the ``.
+The browser has to fix it automatically. But there may be no text inside the ``: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the ``.
Now it's obvious that when we remove the table, it remains.
-The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the ``.
+The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the ``.
The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct.
diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md
index f87074dba..861f70503 100644
--- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md
+++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md
@@ -22,6 +22,6 @@ Why does that happen?
alert(table); // the table, as it should be
table.remove();
- // why there's still aaa in the document?
+ // why there's still "aaa" in the document?
```
diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md
index 43b0a34a7..a57e7e2d9 100644
--- a/2-ui/1-document/07-modifying-document/6-create-list/task.md
+++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md
@@ -10,7 +10,7 @@ For every list item:
1. Ask a user about its content using `prompt`.
2. Create the `` with it and add it to ``.
-3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt).
+3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry).
All elements should be created dynamically.
diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md
index 9154d43d6..46aaa3b00 100644
--- a/2-ui/1-document/08-styles-and-classes/article.md
+++ b/2-ui/1-document/08-styles-and-classes/article.md
@@ -128,6 +128,14 @@ setTimeout(() => document.body.style.display = "", 1000); // back to normal
If we set `style.display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style.display` property at all.
+Also there is a special method for that, `elem.style.removeProperty('style property')`. So, We can remove a property like this:
+
+```js run
+document.body.style.background = 'red'; //set background to red
+
+setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second
+```
+
````smart header="Full rewrite with `style.cssText`"
Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only.
@@ -261,20 +269,6 @@ So nowadays `getComputedStyle` actually returns the resolved value of the proper
We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed.
For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here.
-
-There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not:
-
-```html run
-
-
-```
````
```smart header="Styles applied to `:visited` links are hidden!"
diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md
index 13e245ebb..66f28115f 100644
--- a/2-ui/1-document/09-size-and-scroll/article.md
+++ b/2-ui/1-document/09-size-and-scroll/article.md
@@ -17,8 +17,8 @@ As a sample element to demonstrate properties we'll use the one given below:
width: 300px;
height: 200px;
border: 25px solid #E8C48F;
- padding: 20px;
- overflow: auto;
+ padding: 20px;
+ overflow: auto;
}
```
@@ -106,7 +106,7 @@ Geometry properties are calculated only for displayed elements.
If an element (or any of its ancestors) has `display:none` or is not in the document, then all geometry properties are zero (or `null` for `offsetParent`).
-For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or it's ancestor) has `display:none`.
+For example, `offsetParent` is `null`, and `offsetWidth`, `offsetHeight` are `0` when we created an element, but haven't inserted it into the document yet, or it (or its ancestor) has `display:none`.
We can use this to check if an element is hidden, like this:
@@ -116,7 +116,7 @@ function isHidden(elem) {
}
```
-Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes (like an empty ``).
+Please note that such `isHidden` returns `true` for elements that are on-screen, but have zero sizes.
````
## clientTop/Left
diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md
index 10898dbf7..08a2f6576 100644
--- a/2-ui/1-document/10-size-and-scroll-window/article.md
+++ b/2-ui/1-document/10-size-and-scroll-window/article.md
@@ -2,11 +2,11 @@
How do we find the width and height of the browser window? How do we get the full width and height of the document, including the scrolled out part? How do we scroll the page using JavaScript?
-For most such requests, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities important enough to consider.
+For this type of information, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities to consider.
## Width/height of the window
-To get window width and height we can use `clientWidth/clientHeight` of `document.documentElement`:
+To get window width and height, we can use the `clientWidth/clientHeight` of `document.documentElement`:

@@ -16,12 +16,12 @@ For instance, this button shows the height of your window:
alert(document.documentElement.clientHeight)
```
-````warn header="Not `window.innerWidth/Height`"
-Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So why not to use them instead?
+````warn header="Not `window.innerWidth/innerHeight`"
+Browsers also support properties like `window.innerWidth/innerHeight`. They look like what we want, so why not to use them instead?
-If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return width/height of the visible part of the document, available for the content.
+If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return the width/height of the visible part of the document, available for the content.
-...And `window.innerWidth/innerHeight` include the scrollbar.
+`window.innerWidth/innerHeight` includes the scrollbar.
If there's a scrollbar, and it occupies some space, then these two lines show different values:
```js run
@@ -29,7 +29,7 @@ alert( window.innerWidth ); // full window width
alert( document.documentElement.clientWidth ); // window width minus the scrollbar
```
-In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientHeight/Width`.
+In most cases, we need the *available* window width in order to draw or position something within scrollbars (if there are any), so we should use `documentElement.clientHeight/clientWidth`.
````
```warn header="`DOCTYPE` is important"
@@ -40,9 +40,9 @@ In modern HTML we should always write `DOCTYPE`.
## Width/height of the document
-Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure document full size as `document.documentElement.scrollWidth/scrollHeight`.
+Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure the document's full size as `document.documentElement.scrollWidth/scrollHeight`.
-But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Sounds like a nonsense, weird, right?
+But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera, if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Weird, right?
To reliably obtain the full document height, we should take the maximum of these properties:
@@ -60,11 +60,11 @@ Why so? Better don't ask. These inconsistencies come from ancient times, not a "
## Get the current scroll [#page-scroll]
-DOM elements have their current scroll state in `elem.scrollLeft/scrollTop`.
+DOM elements have their current scroll state in their `scrollLeft/scrollTop` properties.
-For document scroll `document.documentElement.scrollLeft/Top` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`.
+For document scroll, `document.documentElement.scrollLeft/scrollTop` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`.
-Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties `window.pageXOffset/pageYOffset`:
+Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties, `window.pageXOffset/pageYOffset`:
```js run
alert('Current scroll from the top: ' + window.pageYOffset);
@@ -73,19 +73,25 @@ alert('Current scroll from the left: ' + window.pageXOffset);
These properties are read-only.
+```smart header="Also available as `window` properties `scrollX` and `scrollY`"
+For historical reasons, both properties exist, but they are the same:
+- `window.pageXOffset` is an alias of `window.scrollX`.
+- `window.pageYOffset` is an alias of `window.scrollY`.
+```
+
## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll]
```warn
-To scroll the page from JavaScript, its DOM must be fully built.
+To scroll the page with JavaScript, its DOM must be fully built.
-For instance, if we try to scroll the page from the script in ``, it won't work.
+For instance, if we try to scroll the page with a script in ``, it won't work.
```
Regular elements can be scrolled by changing `scrollTop/scrollLeft`.
-We can do the same for the page using `document.documentElement.scrollTop/Left` (except Safari, where `document.body.scrollTop/Left` should be used instead).
+We can do the same for the page using `document.documentElement.scrollTop/scrollLeft` (except Safari, where `document.body.scrollTop/Left` should be used instead).
-Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
+Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
- The method `scrollBy(x,y)` scrolls the page *relative to its current position*. For instance, `scrollBy(0,10)` scrolls the page `10px` down.
@@ -106,28 +112,28 @@ These methods work for all browsers the same way.
## scrollIntoView
-For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView).
+For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView).
The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument:
-- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element is aligned with the window top.
-- if `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element is aligned with the window bottom.
+- If `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element will be aligned with the window top.
+- If `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element will be aligned with the window bottom.
```online
-The button below scrolls the page to make itself show at the window top:
+The button below scrolls the page to position itself at the window top:
this.scrollIntoView()
-And this button scrolls the page to show it at the bottom:
+And this button scrolls the page to position itself at the bottom:
this.scrollIntoView(false)
```
## Forbid the scrolling
-Sometimes we need to make the document "unscrollable". For instance, when we need to cover it with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document.
+Sometimes we need to make the document "unscrollable". For instance, when we need to cover the page with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document.
-To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll.
+To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will "freeze" at its current scroll position.
```online
Try it:
@@ -136,20 +142,20 @@ Try it:
document.body.style.overflow = ''
-The first button freezes the scroll, the second one resumes it.
+The first button freezes the scroll, while the second one releases it.
```
-We can use the same technique to "freeze" the scroll for other elements, not just for `document.body`.
+We can use the same technique to freeze the scroll for other elements, not just for `document.body`.
-The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it.
+The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free and the content "jumps" to fill it.
-That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width the same.
+That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze. If it increased (the scrollbar disappeared), then add `padding` to `document.body` in place of the scrollbar to keep the content width the same.
## Summary
Geometry:
-- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/Height`
+- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/clientHeight`
- Width/height of the whole document, with the scrolled out part:
```js
diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md
index 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:
-
+
```
-Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `mouse.onclick`, so event handlers are totally separate.
+Now `dispatchEvent` runs asynchronously after the current code execution is finished, including `menu.onclick`, so event handlers are totally separate.
The output order becomes: 1 -> 2 -> nested.
@@ -283,9 +282,9 @@ Other constructors of native events like `MouseEvent`, `KeyboardEvent` and so on
For custom events we should use `CustomEvent` constructor. It has an additional option named `detail`, we should assign the event-specific data to it. Then all handlers can access it as `event.detail`.
-Despite the technical possibility to generate browser events like `click` or `keydown`, we should use with the great care.
+Despite the technical possibility of generating browser events like `click` or `keydown`, we should use them with great care.
-We shouldn't generate browser events as it's a hacky way to run handlers. That's a bad architecture most of the time.
+We shouldn't generate browser events as it's a hacky way to run handlers. That's bad architecture most of the time.
Native events might be generated:
diff --git a/2-ui/3-event-details/1-mouse-events-basics/article.md b/2-ui/3-event-details/1-mouse-events-basics/article.md
index b5535bea5..9574b0c83 100644
--- a/2-ui/3-event-details/1-mouse-events-basics/article.md
+++ b/2-ui/3-event-details/1-mouse-events-basics/article.md
@@ -1,3 +1,4 @@
+
# Mouse events
In this chapter we'll get into more details about mouse events and their properties.
@@ -39,9 +40,9 @@ In cases when a single action initiates multiple events, their order is fixed. T
```online
Click the button below and you'll see the events. Try double-click too.
-On the teststand below all mouse events are logged, and if there is more than a 1 second delay between them they are separated by a horizontal ruler.
+On the teststand below, all mouse events are logged, and if there is more than a 1 second delay between them, they are separated by a horizontal rule.
-Also we can see the `button` property that allows to detect the mouse button, it's explained below.
+Also, we can see the `button` property that allows us to detect the mouse button; it's explained below.
```
@@ -52,21 +53,21 @@ Click-related events always have the `button` property, which allows to get the
We usually don't use it for `click` and `contextmenu` events, because the former happens only on left-click, and the latter -- only on right-click.
-From the other hand, `mousedown` and `mouseup` handlers we may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown".
+On the other hand, `mousedown` and `mouseup` handlers may need `event.button`, because these events trigger on any button, so `button` allows to distinguish between "right-mousedown" and "left-mousedown".
The possible values of `event.button` are:
| Button state | `event.button` |
|--------------|----------------|
| Left button (primary) | 0 |
-| Middle button (auxillary) | 1 |
+| Middle button (auxiliary) | 1 |
| Right button (secondary) | 2 |
| X1 button (back) | 3 |
| X2 button (forward) | 4 |
Most mouse devices only have the left and right buttons, so possible values are `0` or `2`. Touch devices also generate similar events when one taps on them.
-Also there's `event.buttons` property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at [MDN](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons) if you ever need it.
+Also there's `event.buttons` property that has all currently pressed buttons as an integer, one bit per button. In practice this property is very rarely used, you can find details at [MDN](mdn:/api/MouseEvent/buttons) if you ever need it.
```warn header="The outdated `event.which`"
Old code may use `event.which` property that's an old non-standard way of getting a button, with possible values:
@@ -154,9 +155,9 @@ Move the mouse over the input field to see `clientX/clientY` (the example is in
## Preventing selection on mousedown
-Double mouse click has a side-effect that may be disturbing in some interfaces: it selects text.
+Double mouse click has a side effect that may be disturbing in some interfaces: it selects text.
-For instance, a double-click on the text below selects it in addition to our handler:
+For instance, double-clicking on the text below selects it in addition to our handler:
```html autorun height=50
Double-click me
diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html
index e998165fd..84d52b18c 100644
--- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html
+++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/solution.view/index.html
@@ -54,7 +54,7 @@
Once upon a time there was a mother pig who had three little pigs.
-
The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."
+
The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."
The three little pigs set off. "We will take care that the wolf does not catch us," they said.
diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html
index 2dc4394e7..774e24a21 100644
--- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html
+++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/1-behavior-nested-tooltip/source.view/index.html
@@ -54,7 +54,7 @@
Once upon a time there was a mother pig who had three little pigs.
-
The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."
+
The three little pigs grew so big that their mother said to them, "You are too big to live here any longer. You must go and build houses for yourselves. But take care that the wolf does not catch you."
The three little pigs set off. "We will take care that the wolf does not catch us," they said.
diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js
index 4e6e2a3e9..7503ca9c2 100644
--- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js
+++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/2-hoverintent/solution.view/hoverIntent.js
@@ -88,7 +88,7 @@ class HoverIntent {
if (speed < this.sensitivity) {
clearInterval(this.checkSpeedInterval);
this.isHover = true;
- this.over.call(this.elem, event);
+ this.over.call(this.elem);
} else {
// speed fast, remember new coordinates as the previous ones
this.prevX = this.lastX;
diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md
index c7ac0d4db..d409c3f12 100644
--- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md
+++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/article.md
@@ -80,7 +80,7 @@ An important feature of `mouseout` -- it triggers, when the pointer moves from a
```
-If we're on `#parent` and then move the pointer deeper into `#child`, but we get `mouseout` on `#parent`!
+If we're on `#parent` and then move the pointer deeper into `#child`, we get `mouseout` on `#parent`!

diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js
index 6d87199c2..5752e83ae 100755
--- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js
+++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js
@@ -3,7 +3,7 @@ parent.onmouseover = parent.onmouseout = parent.onmousemove = handler;
function handler(event) {
let type = event.type;
- while (type < 11) type += ' ';
+ while (type.length < 11) type += ' ';
log(type + " target=" + event.target.id)
return false;
diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md
index a47bfade6..4c928eef1 100644
--- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md
+++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md
@@ -18,19 +18,19 @@ The basic Drag'n'Drop algorithm looks like this:
2. Then on `mousemove` move it by changing `left/top` with `position:absolute`.
3. On `mouseup` - perform all actions related to finishing the drag'n'drop.
-These are the basics. Later we'll see how to other features, such as highlighting current underlying elements while we drag over them.
+These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them.
Here's the implementation of dragging a ball:
```js
-ball.onmousedown = function(event) {
+ball.onmousedown = function(event) {
// (1) prepare to moving: make absolute and on top by z-index
ball.style.position = 'absolute';
ball.style.zIndex = 1000;
// move it out of any current parents directly into body
// to make it positioned relative to the body
- document.body.append(ball);
+ document.body.append(ball);
// centers the ball at (pageX, pageY) coordinates
function moveAt(pageX, pageY) {
@@ -93,14 +93,14 @@ So we should listen on `document` to catch it.
## Correct positioning
-In the examples above the ball is always moved so, that it's center is under the pointer:
+In the examples above the ball is always moved so that its center is under the pointer:
```js
ball.style.left = pageX - ball.offsetWidth / 2 + 'px';
ball.style.top = pageY - ball.offsetHeight / 2 + 'px';
```
-Not bad, but there's a side-effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer.
+Not bad, but there's a side effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer.
It would be better if we keep the initial shift of the element relative to the pointer.
@@ -124,7 +124,7 @@ Let's update our algorithm:
```js
// onmousemove
- // ball has position:absoute
+ // ball has position:absolute
ball.style.left = event.pageX - *!*shiftX*/!* + 'px';
ball.style.top = event.pageY - *!*shiftY*/!* + 'px';
```
@@ -219,7 +219,7 @@ That's why the initial idea to put handlers on potential droppables doesn't work
So, what to do?
-There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window).
+There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). If there are multiple overlapping elements on the same coordinates, then the topmost one is returned.
We can use it in any of our mouse event handlers to detect the potential droppable under the pointer, like this:
@@ -276,7 +276,7 @@ function onMouseMove(event) {
}
```
-In the example below when the ball is dragged over the soccer gate, the gate is highlighted.
+In the example below when the ball is dragged over the soccer goal, the goal is highlighted.
[codetabs height=250 src="ball4"]
@@ -300,4 +300,4 @@ We can lay a lot on this foundation.
- We can use event delegation for `mousedown/up`. A large-area event handler that checks `event.target` can manage Drag'n'Drop for hundreds of elements.
- And so on.
-There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-part solution.
+There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-party solution.
diff --git a/2-ui/3-event-details/6-pointer-events/article.md b/2-ui/3-event-details/6-pointer-events/article.md
index 9d92144bc..ecc144712 100644
--- a/2-ui/3-event-details/6-pointer-events/article.md
+++ b/2-ui/3-event-details/6-pointer-events/article.md
@@ -8,23 +8,29 @@ Let's make a small overview, so that you understand the general picture and the
- Long ago, in the past, there were only mouse events.
- Then touch devices appeared. For the old code to work, they also generate mouse events. For instance, tapping generates `mousedown`. But mouse events were not good enough, as touch devices are more powerful in many aspects. For example, it's possible to touch multiple points at once, and mouse events don't have any properties for that.
+ Then touch devices became widespread, phones and tablets in particular. For the existing scripts to work, they generated (and still generate) mouse events. For instance, tapping a touchscreen generates `mousedown`. So touch devices worked well with web pages.
+
+ But touch devices have more capabilities than a mouse. For example, it's possible to touch multiple points at once ("multi-touch"). Although, mouse events don't have necessary properties to handle such multi-touches.
- So touch events were introduced, such as `touchstart`, `touchend`, `touchmove`, that have touch-specific properties (we don't cover them in detail here, because pointer events are even better).
- Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing code that listens for both touch and mouse events was cumbersome.
+ Still, it wasn't enough, as there are many other devices, such as pens, that have their own features. Also, writing code that listens for both touch and mouse events was cumbersome.
- To solve these issues, the new standard Pointer Events was introduced. It provides a single set of events for all kinds of pointing devices.
-As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works. Unless you code for Internet Explorer 10, or for Safari 12 or below, there's no point in using mouse or touch events any more -- we can switch to pointer events.
+As of now, [Pointer Events Level 2](https://www.w3.org/TR/pointerevents2/) specification is supported in all major browsers, while the newer [Pointer Events Level 3](https://w3c.github.io/pointerevents/) is in the works and is mostly compatible with Pointer Events level 2.
+
+Unless you develop for old browsers, such as Internet Explorer 10, or for Safari 12 or below, there's no point in using mouse or touch events any more -- we can switch to pointer events.
+
+Then your code will work well with both touch and mouse devices.
-That being said, they have some important peculiarities that one should know in order to use them correctly and avoid surprises. We'll make note of them in this article.
+That said, there are some important peculiarities that one should know in order to use Pointer Events correctly and avoid surprises. We'll make note of them in this article.
## Pointer event types
Pointer events are named similarly to mouse events:
-| Pointer Event | Mouse event |
+| Pointer event | Similar mouse event |
|---------------|-------------|
| `pointerdown` | `mousedown` |
| `pointerup` | `mouseup` |
@@ -37,12 +43,12 @@ Pointer events are named similarly to mouse events:
| `gotpointercapture` | - |
| `lostpointercapture` | - |
-As we can see, for every `mouse`, there's a `pointer` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll explain them soon.
+As we can see, for every `mouse`, there's a `pointer` that plays a similar role. Also there are 3 additional pointer events that don't have a corresponding `mouse...` counterpart, we'll explain them soon.
```smart header="Replacing `mouse` with `pointer` in our code"
We can replace `mouse` events with `pointer` in our code and expect things to continue working fine with mouse.
-The support for touch devices will also "magically" improve, but we'll probably need to add `touch-action: none` in CSS. See the details below in the section about `pointercancel`.
+The support for touch devices will also "magically" improve. Although, we may need to add `touch-action: none` in some places in CSS. We'll cover it below in the section about `pointercancel`.
```
## Pointer event properties
@@ -50,22 +56,22 @@ The support for touch devices will also "magically" improve, but we'll probably
Pointer events have the same properties as mouse events, such as `clientX/Y`, `target`, etc., plus some others:
- `pointerId` - the unique identifier of the pointer causing the event.
-
- Allows us to handle multiple pointers, such as a touchscreen with stylus and multi-touch (explained below).
-- `pointerType` - the pointing device type. Must be a string, one of: "mouse", "pen" or "touch".
+
+ Browser-generated. Allows us to handle multiple pointers, such as a touchscreen with stylus and multi-touch (examples will follow).
+- `pointerType` - the pointing device type. Must be a string, one of: "mouse", "pen" or "touch".
We can use this property to react differently on various pointer types.
-- `isPrimary` - `true` for the primary pointer (the first finger in multi-touch).
+- `isPrimary` - is `true` for the primary pointer (the first finger in multi-touch).
-For pointers that measure contact area and pressure, e.g. a finger on the touchscreen, the additional properties can be useful:
+Some pointer devices measure contact area and pressure, e.g. for a finger on the touchscreen, there are additional properties for that:
-- `width` - the width of the area where the pointer touches the device. Where unsupported, e.g. for a mouse, it's always `1`.
+- `width` - the width of the area where the pointer (e.g. a finger) touches the device. Where unsupported, e.g. for a mouse, it's always `1`.
- `height` - the height of the area where the pointer touches the device. Where unsupported, it's always `1`.
- `pressure` - the pressure of the pointer tip, in range from 0 to 1. For devices that don't support pressure must be either `0.5` (pressed) or `0`.
- `tangentialPressure` - the normalized tangential pressure.
-- `tiltX`, `tiltY`, `twist` - pen-specific properties that describe how the pen is positioned relative the surface.
+- `tiltX`, `tiltY`, `twist` - pen-specific properties that describe how the pen is positioned relative to the surface.
-These properties aren't very well supported across devices, so they are rarely used. You can find the details in the [specification](https://w3c.github.io/pointerevents/#pointerevent-interface) if needed.
+These properties aren't supported by most devices, so they are rarely used. You can find the details about them in the [specification](https://w3c.github.io/pointerevents/#pointerevent-interface) if needed.
## Multi-touch
@@ -73,11 +79,11 @@ One of the things that mouse events totally don't support is multi-touch: a user
Pointer Events allow handling multi-touch with the help of the `pointerId` and `isPrimary` properties.
-Here's what happens when a user touches a screen in one place, then puts another finger somewhere else on it:
+Here's what happens when a user touches a touchscreen in one place, then puts another finger somewhere else on it:
-1. At the first touch:
+1. At the first finger touch:
- `pointerdown` with `isPrimary=true` and some `pointerId`.
-2. For the second finger and further touches:
+2. For the second finger and more fingers (assuming the first one is still touching):
- `pointerdown` with `isPrimary=false` and a different `pointerId` for every finger.
Please note: the `pointerId` is assigned not to the whole device, but for each touching finger. If we use 5 fingers to simultaneously touch the screen, we have 5 `pointerdown` events, each with their respective coordinates and a different `pointerId`.
@@ -91,53 +97,51 @@ Here's the demo that logs `pointerdown` and `pointerup` events:
[iframe src="multitouch" edit height=200]
-Please note: you must be using a touchscreen device, such as a phone or a tablet, to actually see the difference. For single-touch devices, such as a mouse, there'll be always same `pointerId` with `isPrimary=true`, for all pointer events.
+Please note: you must be using a touchscreen device, such as a phone or a tablet, to actually see the difference in `pointerId/isPrimary`. For single-touch devices, such as a mouse, there'll be always same `pointerId` with `isPrimary=true`, for all pointer events.
```
## Event: pointercancel
-We've mentioned the importance of `touch-action: none` before. Now let's explain why, as skipping this may cause our interfaces to malfunction.
-
-The `pointercancel` event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated.
+The `pointercancel` event fires when there's an ongoing pointer interaction, and then something happens that causes it to be aborted, so that no more pointer events are generated.
-Such causes are:
-- The pointer device hardware was disabled.
-- The device orientation changed (tablet rotated).
+Such causes are:
+- The pointer device hardware was physically disabled.
+- The device orientation changed (tablet rotated).
- The browser decided to handle the interaction on its own, considering it a mouse gesture or zoom-and-pan action or something else.
We'll demonstrate `pointercancel` on a practical example to see how it affects us.
-Let's say we're impelementing drag'n'drop for a ball, just as in the beginning of the article .
+Let's say we're implementing drag'n'drop for a ball, just as in the beginning of the article .
Here is the flow of user actions and the corresponding events:
-1) The user presses the mouse button on an image, to start dragging
+1) The user presses on an image, to start dragging
- `pointerdown` event fires
-2) Then they start dragging the image
+2) Then they start moving the pointer (thus dragging the image)
- `pointermove` fires, maybe several times
-3) Surprise! The browser has native drag'n'drop support for images, that kicks in and takes over the drag'n'drop process, thus generating `pointercancel` event.
+3) And then the surprise happens! The browser has native drag'n'drop support for images, that kicks in and takes over the drag'n'drop process, thus generating `pointercancel` event.
- The browser now handles drag'n'drop of the image on its own. The user may even drag the ball image out of the browser, into their Mail program or a File Manager.
- No more `pointermove` events for us.
-So the issue is that the browser "hijacks" the interaction: `pointercancel` fires and no more `pointermove` events are generated.
+So the issue is that the browser "hijacks" the interaction: `pointercancel` fires in the beginning of the "drag-and-drop" process, and no more `pointermove` events are generated.
```online
-Here's the demo with pointer events (only `up/down`, `move` and `cancel`) logged in the textarea:
+Here's the drag'n'drop demo with logging of pointer events (only `up/down`, `move` and `cancel`) in the `textarea`:
[iframe src="ball" height=240 edit]
```
-We'd like to implement our own drag'n'drop, so let's tell the browser not to take it over.
+We'd like to implement the drag'n'drop on our own, so let's tell the browser not to take it over.
-**Prevent default browser actions to avoid `pointercancel`.**
+**Prevent the default browser action to avoid `pointercancel`.**
We need to do two things:
1. Prevent native drag'n'drop from happening:
- We can do this by setting `ball.ondragstart = () => false`, just as described in the article .
- That works well for mouse events.
-2. For touch devices, there are also touch-related browser actions. We'll have problems with them too.
- - We can prevent them by setting `#ball { touch-action: none }` in CSS.
+2. For touch devices, there are other touch-related browser actions (besides drag'n'drop). To avoid problems with them too:
+ - Prevent them by setting `#ball { touch-action: none }` in CSS.
- Then our code will start working on touch devices.
After we do that, the events will work as intended, the browser won't hijack the process and doesn't emit `pointercancel`.
@@ -156,46 +160,79 @@ Now we can add the code to actually move the ball, and our drag'n'drop will work
Pointer capturing is a special feature of pointer events.
-The idea is that we can "bind" all events with a particular `pointerId` to a given element. Then all subsequent events with the same `pointerId` will be retargeted to the same element. That is: the browser sets that element as the target and trigger associated handlers, no matter where it actually happened.
+The idea is very simple, but may seem quite odd at first, as nothing like that exists for any other event type.
+
+The main method is:
+- `elem.setPointerCapture(pointerId)` -- binds events with the given `pointerId` to `elem`. After the call all pointer events with the same `pointerId` will have `elem` as the target (as if happened on `elem`), no matter where in document they really happened.
-The related methods are:
-- `elem.setPointerCapture(pointerId)` - binds the given `pointerId` to `elem`.
-- `elem.releasePointerCapture(pointerId)` - unbinds the given `pointerId` from `elem`.
+In other words, `elem.setPointerCapture(pointerId)` retargets all subsequent events with the given `pointerId` to `elem`.
-Such binding doesn't hold long. It's automatically removed after `pointerup` or `pointercancel` events, or when the target `elem` is removed from the document.
+The binding is removed:
+- automatically when `pointerup` or `pointercancel` events occur,
+- automatically when `elem` is removed from the document,
+- when `elem.releasePointerCapture(pointerId)` is called.
+
+Now what is it good for? It's time to see a real-life example.
+
+**Pointer capturing can be used to simplify drag'n'drop kind of interactions.**
+
+Let's recall how one can implement a custom slider, described in the .
+
+We can make a `slider` element to represent the strip and the "runner" (`thumb`) inside it:
+
+```html
+
+```
-Now when do we need this?
+With styles, it looks like this:
-**Pointer capturing is used to simplify drag'n'drop kind of interactions.**
+[iframe src="slider-html" height=40 edit]
-Let's recall the problem we met while making a custom slider in the article .
+
-1) First, the user presses `pointerdown` on the slider thumb to start dragging it.
-2) ...But then, as they move the pointer, it may leave the slider: go below or over it.
+And here's the working logic, as it was described, after replacing mouse events with similar pointer events:
-But we continue tracking track `pointermove` events and move the thumb until `pointerup`, even though the pointer is not on the slider any more.
+1. The user presses on the slider `thumb` -- `pointerdown` triggers.
+2. Then they move the pointer -- `pointermove` triggers, and our code moves the `thumb` element along.
+ - ...As the pointer moves, it may leave the slider `thumb` element, go above or below it. The `thumb` should move strictly horizontally, remaining aligned with the pointer.
-[Previously](info:mouse-drag-and-drop), to handle `pointermove` events that happen outside of the slider, we listened for `pointermove` events on the whole `document`.
+In the mouse event based solution, to track all pointer movements, including when it goes above/below the `thumb`, we had to assign `mousemove` event handler on the whole `document`.
-Pointer capturing provides an alternative solution: we can call `thumb.setPointerCapture(event.pointerId)` in `pointerdown` handler, and then all future pointer events until `pointerup` will be retargeted to `thumb`.
+That's not a cleanest solution, though. One of the problems is that when a user moves the pointer around the document, it may trigger event handlers (such as `mouseover`) on some other elements, invoke totally unrelated UI functionality, and we don't want that.
-That is: events handlers on `thumb` will be called, and `event.target` will always be `thumb`, even if the user moves their pointer around the whole document. So we can listen at `thumb` for `pointermove`, no matter where it happens.
+This is the place where `setPointerCapture` comes into play.
+
+- We can call `thumb.setPointerCapture(event.pointerId)` in `pointerdown` handler,
+- Then future pointer events until `pointerup/cancel` will be retargeted to `thumb`.
+- When `pointerup` happens (dragging complete), the binding is removed automatically, we don't need to care about it.
+
+So, even if the user moves the pointer around the whole document, events handlers will be called on `thumb`. Nevertheless, coordinate properties of the event objects, such as `clientX/clientY` will still be correct - the capturing only affects `target/currentTarget`.
Here's the essential code:
```js
thumb.onpointerdown = function(event) {
- // retarget all pointer events (until pointerup) to me
+ // retarget all pointer events (until pointerup) to thumb
thumb.setPointerCapture(event.pointerId);
-};
-thumb.onpointermove = function(event) {
- // move the slider: listen at thumb, as all events are retargeted to it
- let newLeft = event.clientX - slider.getBoundingClientRect().left;
- thumb.style.left = newLeft + 'px';
+ // start tracking pointer moves
+ thumb.onpointermove = function(event) {
+ // moving the slider: listen on the thumb, as all pointer events are retargeted to it
+ let newLeft = event.clientX - slider.getBoundingClientRect().left;
+ thumb.style.left = newLeft + 'px';
+ };
+
+ // on pointer up finish tracking pointer moves
+ thumb.onpointerup = function(event) {
+ thumb.onpointermove = null;
+ thumb.onpointerup = null;
+ // ...also process the "drag end" if needed
+ };
};
-// note: no need to call thumb.releasePointerCapture,
+// note: no need to call thumb.releasePointerCapture,
// it happens on pointerup automatically
```
@@ -203,27 +240,43 @@ thumb.onpointermove = function(event) {
The full demo:
[iframe src="slider" height=100 edit]
+
+
+
+In the demo, there's also an additional element with `onmouseover` handler showing the current date.
+
+Please note: while you're dragging the thumb, you may hover over this element, and its handler *does not* trigger.
+
+So the dragging is now free of side effects, thanks to `setPointerCapture`.
```
-**As a summary: the code becomes cleaner as we don't need to add/remove handlers on the whole `document` any more. That's what pointer capturing does.**
-There are two associated pointer events:
+
+At the end, pointer capturing gives us two benefits:
+1. The code becomes cleaner as we don't need to add/remove handlers on the whole `document` any more. The binding is released automatically.
+2. If there are other pointer event handlers in the document, they won't be accidentally triggered by the pointer while the user is dragging the slider.
+
+### Pointer capturing events
+
+There's one more thing to mention here, for the sake of completeness.
+
+There are two events associated with pointer capturing:
- `gotpointercapture` fires when an element uses `setPointerCapture` to enable capturing.
- `lostpointercapture` fires when the capture is released: either explicitly with `releasePointerCapture` call, or automatically on `pointerup`/`pointercancel`.
## Summary
-Pointer events allow handling mouse, touch and pen events simultaneously.
+Pointer events allow handling mouse, touch and pen events simultaneously, with a single piece of code.
Pointer events extend mouse events. We can replace `mouse` with `pointer` in event names and expect our code to continue working for mouse, with better support for other device types.
-Remember to set `touch-events: none` in CSS for elements that we engage, otherwise the browser will hijack many types of touch interactions, and pointer events won't be generated.
+For drag'n'drops and complex touch interactions that the browser may decide to hijack and handle on its own - remember to cancel the default action on events and set `touch-action: none` in CSS for elements that we engage.
-Additional abilities of Pointer events are:
+Additional abilities of pointer events are:
- Multi-touch support using `pointerId` and `isPrimary`.
- Device-specific properties, such as `pressure`, `width/height`, and others.
- Pointer capturing: we can retarget all pointer events to a specific element until `pointerup`/`pointercancel`.
-As of now, pointer events are supported in all major browsers, so we can safely switch to them, as long as IE10- and Safari 12- are not needed. And even with those browsers, there are polyfills that enable the support of pointer events.
+As of now, pointer events are supported in all major browsers, so we can safely switch to them, especially if IE10- and Safari 12- are not needed. And even with those browsers, there are polyfills that enable the support of pointer events.
diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html
new file mode 100644
index 000000000..781016f52
--- /dev/null
+++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css
new file mode 100644
index 000000000..9b3d3b82d
--- /dev/null
+++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css
@@ -0,0 +1,19 @@
+.slider {
+ border-radius: 5px;
+ background: #E0E0E0;
+ background: linear-gradient(left top, #E0E0E0, #EEEEEE);
+ width: 310px;
+ height: 15px;
+ margin: 5px;
+}
+
+.thumb {
+ width: 10px;
+ height: 25px;
+ border-radius: 3px;
+ position: relative;
+ left: 10px;
+ top: -5px;
+ background: blue;
+ cursor: pointer;
+}
diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/index.html b/2-ui/3-event-details/6-pointer-events/slider.view/index.html
index 2c2a69ec7..b29e646a1 100644
--- a/2-ui/3-event-details/6-pointer-events/slider.view/index.html
+++ b/2-ui/3-event-details/6-pointer-events/slider.view/index.html
@@ -5,22 +5,33 @@
+Mouse over here to see the date
+
diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css
index 9b3d3b82d..a84cd5e7e 100644
--- a/2-ui/3-event-details/6-pointer-events/slider.view/style.css
+++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css
@@ -8,6 +8,7 @@
}
.thumb {
+ touch-action: none;
width: 10px;
height: 25px;
border-radius: 3px;
diff --git a/2-ui/3-event-details/7-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md
index 617852ccf..12fe63201 100644
--- a/2-ui/3-event-details/7-keyboard-events/article.md
+++ b/2-ui/3-event-details/7-keyboard-events/article.md
@@ -107,7 +107,7 @@ So, `event.code` may match a wrong character for unexpected layout. Same letters
To reliably track layout-dependent characters, `event.key` may be a better way.
-On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch.
+On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location. So hotkeys that rely on it work well even in case of a language switch.
Do we want to handle layout-dependant keys? Then `event.key` is the way to go.
@@ -139,22 +139,25 @@ For instance, the ` ` below expects a phone number, so it does not accept
```html autorun height=60 run
```
-Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`.
+The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`.
-Let's relax it a little bit:
+As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the ` ` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters)
+Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side effect of the strict filter `checkPhoneKey`. These keys make it return `false`.
+
+Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`:
```html autorun height=60 run
@@ -162,7 +165,9 @@ function checkPhoneKey(key) {
Now arrows and deletion works well.
-...But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid.
+Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable.
+
+The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the ` ` when it's invalid. Or we can use both event handlers together.
## Legacy
@@ -170,6 +175,12 @@ In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `whic
There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more.
+## Mobile Keyboards
+
+When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's [`e.keyCode` should be `229`](https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode) and [`e.key` should be `"Unidentified"`](https://www.w3.org/TR/uievents-key/#key-attr-values).
+
+While some of these keyboards might still use the right values for `e.key`, `e.code`, `e.keyCode`... when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices.
+
## Summary
Pressing a key always generates a keyboard event, be it symbol keys or special keys like `key:Shift` or `key:Ctrl` and so on. The only exception is `key:Fn` key that sometimes presents on a laptop keyboard. There's no keyboard event for it, because it's often implemented on lower level than OS.
diff --git a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html
index 401062830..a0d5a4f40 100644
--- a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html
+++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html
@@ -28,7 +28,7 @@
-
+
diff --git a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js
index 5eba24c7a..d97f7a7b5 100644
--- a/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js
+++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js
@@ -5,6 +5,8 @@ let lastTime = Date.now();
function handle(e) {
if (form.elements[e.type + 'Ignore'].checked) return;
+ area.scrollTop = 1e6;
+
let text = e.type +
' key=' + e.key +
' code=' + e.code +
diff --git a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md
index 10945ccd7..54c101193 100644
--- a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md
+++ b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md
@@ -55,11 +55,11 @@ function populate() {
// document bottom
let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;
- // if the user scrolled far enough (<100px to the end)
- if (windowRelativeBottom < document.documentElement.clientHeight + 100) {
- // let's add more data
- document.body.insertAdjacentHTML("beforeend", `Date: ${new Date()}
`);
- }
+ // if the user hasn't scrolled far enough (>100px to the end)
+ if (windowRelativeBottom > document.documentElement.clientHeight + 100) break;
+
+ // let's add more data
+ document.body.insertAdjacentHTML("beforeend", `Date: ${new Date()}
`);
}
}
```
diff --git a/2-ui/3-event-details/8-onscroll/article.md b/2-ui/3-event-details/8-onscroll/article.md
index 7b5cf4848..734bd84c6 100644
--- a/2-ui/3-event-details/8-onscroll/article.md
+++ b/2-ui/3-event-details/8-onscroll/article.md
@@ -1,6 +1,6 @@
# Scrolling
-The `scroll` event allows to react on a page or element scrolling. There are quite a few good things we can do here.
+The `scroll` event allows reacting to a page or element scrolling. There are quite a few good things we can do here.
For instance:
- Show/hide additional controls or information depending on where in the document the user is.
@@ -34,4 +34,4 @@ If we add an event handler to these events and `event.preventDefault()` in it, t
There are many ways to initiate a scroll, so it's more reliable to use CSS, `overflow` property.
-Here are few tasks that you can solve or look through to see the applications on `onscroll`.
+Here are few tasks that you can solve or look through to see applications of `onscroll`.
diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md
index 01af1f400..7bc87a0f0 100644
--- a/2-ui/4-forms-controls/1-form-elements/article.md
+++ b/2-ui/4-forms-controls/1-form-elements/article.md
@@ -8,11 +8,11 @@ Working with forms will be much more convenient when we learn them.
Document forms are members of the special collection `document.forms`.
-That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form.
+That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form.
```js no-beautify
-document.forms.my - the form with name="my"
-document.forms[0] - the first form in the document
+document.forms.my; // the form with name="my"
+document.forms[0]; // the first form in the document
```
When we have a form, then any element is available in the named collection `form.elements`.
@@ -36,9 +36,9 @@ For instance:
```
-There may be multiple elements with the same name, that's often the case with radio buttons.
+There may be multiple elements with the same name. This is typical with radio buttons and checkboxes.
-In that case `form.elements[name]` is a collection, for instance:
+In that case, `form.elements[name]` is a *collection*. For instance:
```html run height=40
@@ -119,7 +119,7 @@ That's easy to see in an example:
```
-That's usually not a problem, because we rarely change names of form elements.
+That's usually not a problem, however, because we rarely change names of form elements.
````
@@ -155,7 +155,7 @@ Let's talk about form controls.
### input and textarea
-We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes.
+We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes and radio buttons.
Like this:
@@ -177,18 +177,16 @@ It stores only the HTML that was initially on the page, not the current value.
A `` element has 3 important properties:
1. `select.options` -- the collection of `` subelements,
-2. `select.value` -- the value of the currently selected ` `,
-3. `select.selectedIndex` -- the number of the currently selected ` `.
+2. `select.value` -- the *value* of the currently selected ` `,
+3. `select.selectedIndex` -- the *number* of the currently selected ` `.
They provide three different ways of setting a value for a ``:
-1. Find the corresponding `` element and set `option.selected` to `true`.
-2. Set `select.value` to the value.
-3. Set `select.selectedIndex` to the number of the option.
+1. Find the corresponding ` ` element (e.g. among `select.options`) and set its `option.selected` to `true`.
+2. If we know a new value: set `select.value` to the new value.
+3. If we know the new option number: set `select.selectedIndex` to that number.
-The first way is the most obvious, but `(2)` and `(3)` are usually more convenient.
-
-Here is an example:
+Here is an example of all three methods:
```html run
@@ -199,15 +197,18 @@ Here is an example:
```
-Unlike most other controls, `` allows to select multiple options at once if it has `multiple` attribute. That's feature is rarely used. In that case we need to use the first way: add/remove the `selected` property from `` subelements.
+Unlike most other controls, `` allows to select multiple options at once if it has `multiple` attribute. This attribute is rarely used, though.
+
+For multiple selected values, use the first way of setting values: add/remove the `selected` property from `` subelements.
-We can get their collection as `select.options`, for instance:
+Here's an example of how to get selected values from a multi-select:
```html run
@@ -230,31 +231,31 @@ The full specification of the `` element is available in the specificati
### new Option
-This is rarely used on its own. But there's still an interesting thing.
-
-In the [specification](https://html.spec.whatwg.org/multipage/forms.html#the-option-element) there's a nice short syntax to create `` elements:
+In the [specification](https://html.spec.whatwg.org/multipage/forms.html#the-option-element) there's a nice short syntax to create an ` ` element:
```js
option = new Option(text, value, defaultSelected, selected);
```
-Parameters:
+This syntax is optional. We can use `document.createElement('option')` and set attributes manually. Still, it may be shorter, so here are the parameters:
- `text` -- the text inside the option,
- `value` -- the option value,
- `defaultSelected` -- if `true`, then `selected` HTML-attribute is created,
- `selected` -- if `true`, then the option is selected.
-There may be a small confusion about `defaultSelected` and `selected`. That's simple: `defaultSelected` sets HTML-attribute, that we can get using `option.getAttribute('selected')`. And `selected` - whether the option is selected or not, that's more important. Usually both values are either set to `true` or not set (same as `false`).
+The difference between `defaultSelected` and `selected` is that `defaultSelected` sets the HTML-attribute (that we can get using `option.getAttribute('selected')`), while `selected` sets whether the option is selected or not.
-For instance:
+In practice, one should usually set _both_ values to `true` or `false`. (Or, simply omit them; both default to `false`.)
+
+For instance, here's a new "unselected" option:
```js
let option = new Option("Text", "value");
// creates Text
```
-The same element selected:
+The same option, but selected:
```js
let option = new Option("Text", "value", true, true);
@@ -288,9 +289,9 @@ Form navigation:
`element.form`
: Elements reference their form in the `form` property.
-Value is available as `input.value`, `textarea.value`, `select.value` etc, or `input.checked` for checkboxes and radio buttons.
+Value is available as `input.value`, `textarea.value`, `select.value`, etc. (For checkboxes and radio buttons, use `input.checked` to determine whether a value is selected.)
-For `` we can also get the value by the index `select.selectedIndex` or through the options collection `select.options`.
+For ``, one can also get the value by the index `select.selectedIndex` or through the options collection `select.options`.
These are the basics to start working with forms. We'll meet many examples further in the tutorial.
diff --git a/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md b/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md
index 2cccea020..378bd1f54 100644
--- a/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md
+++ b/2-ui/4-forms-controls/2-focus-blur/4-edit-td-click/task.md
@@ -6,7 +6,7 @@ importance: 5
Make table cells editable on click.
-- On click -- the cell should became "editable" (textarea appears inside), we can change HTML. There should be no resize, all geometry should remain the same.
+- On click -- the cell should become "editable" (textarea appears inside), we can change HTML. There should be no resize, all geometry should remain the same.
- Buttons OK and CANCEL appear below the cell to finish/cancel the editing.
- Only one cell may be editable at a moment. While a `` is in "edit mode", clicks on other cells are ignored.
- The table may have many cells. Use event delegation.
diff --git a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md
index fc48c21ff..644d814d9 100644
--- a/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md
+++ b/2-ui/4-forms-controls/2-focus-blur/5-keyboard-mouse/task.md
@@ -9,4 +9,5 @@ Focus on the mouse. Then use arrow keys to move it:
[demo src="solution"]
P.S. Don't put event handlers anywhere except the `#mouse` element.
+
P.P.S. Don't modify HTML/CSS, the approach should be generic and work with any element.
diff --git a/2-ui/4-forms-controls/2-focus-blur/article.md b/2-ui/4-forms-controls/2-focus-blur/article.md
index d42013e5b..c253dc11d 100644
--- a/2-ui/4-forms-controls/2-focus-blur/article.md
+++ b/2-ui/4-forms-controls/2-focus-blur/article.md
@@ -1,6 +1,6 @@
# Focusing: focus/blur
-An element receives a focus when the user either clicks on it or uses the `key:Tab` key on the keyboard. There's also an `autofocus` HTML attribute that puts the focus into an element by default when a page loads and other means of getting a focus.
+An element receives the focus when the user either clicks on it or uses the `key:Tab` key on the keyboard. There's also an `autofocus` HTML attribute that puts the focus onto an element by default when a page loads and other means of getting the focus.
Focusing on an element generally means: "prepare to accept the data here", so that's the moment when we can run the code to initialize the required functionality.
@@ -18,7 +18,7 @@ Let's use them for validation of an input field.
In the example below:
-- The `blur` handler checks if the field the email is entered, and if not -- shows an error.
+- The `blur` handler checks if the field has an email entered, and if not -- shows an error.
- The `focus` handler hides the error message (on `blur` it will be checked again):
```html run autorun height=60
@@ -90,6 +90,8 @@ If we enter something into the input and then try to use `key:Tab` or click away
Please note that we can't "prevent losing focus" by calling `event.preventDefault()` in `onblur`, because `onblur` works *after* the element lost the focus.
+In practice though, one should think well, before implementing something like this, because we generally *should show errors* to the user, but *should not prevent their progress* in filling our form. They may want to fill other fields first.
+
```warn header="JavaScript-initiated focus loss"
A focus loss can occur for many reasons.
@@ -104,11 +106,11 @@ The best recipe is to be careful when using these events. If we want to track us
```
## Allow focusing on any element: tabindex
-By default many elements do not support focusing.
+By default, many elements do not support focusing.
The list varies a bit between browsers, but one thing is always correct: `focus/blur` support is guaranteed for elements that a visitor can interact with: ``, ` `, ``, `` and so on.
-From the other hand, elements that exist to format something, such as ``, `
`, `` -- are unfocusable by default. The method `elem.focus()` doesn't work on them, and `focus/blur` events are never triggered.
+On the other hand, elements that exist to format something, such as ``, `
`, `` -- are unfocusable by default. The method `elem.focus()` doesn't work on them, and `focus/blur` events are never triggered.
This can be changed using HTML-attribute `tabindex`.
@@ -118,7 +120,7 @@ That is: if we have two elements, the first has `tabindex="1"`, and the second h
The switch order is: elements with `tabindex` from `1` and above go first (in the `tabindex` order), and then elements without `tabindex` (e.g. a regular ` `).
-Elements with matching `tabindex` are switched in the document source order (the default order).
+Elements without matching `tabindex` are switched in the document source order (the default order).
There are two special values:
@@ -131,7 +133,7 @@ There are two special values:
For instance, here's a list. Click the first item and press `key:Tab`:
```html autorun no-beautify
-Click the first item and press Tab. Keep track of the order. Please note that many subsequent Tabs can move the focus out of the iframe with the example.
+Click the first item and press Tab. Keep track of the order. Please note that many subsequent Tabs can move the focus out of the iframe in the example.
One
Zero
@@ -216,7 +218,7 @@ So here's another working variant:
## Summary
-Events `focus` and `blur` trigger on focusing/losing focus on the element.
+Events `focus` and `blur` trigger on an element focusing/losing focus.
Their specials are:
- They do not bubble. Can use capturing state instead or `focusin/focusout`.
diff --git a/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/solution.view/index.html b/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/solution.view/index.html
index 4850b2ca9..0515c839e 100644
--- a/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/solution.view/index.html
+++ b/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/solution.view/index.html
@@ -96,7 +96,7 @@
let years = form.months.value / 12;
if (!years) return;
- let result = Math.round(initial * (1 + interest * years));
+ let result = Math.round(initial * (1 + interest) ** years);
let height = result / form.money.value * 100 + 'px';
document.getElementById('height-after').style.height = height;
diff --git a/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/task.md b/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/task.md
index e324577a9..73f0477ff 100644
--- a/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/task.md
+++ b/2-ui/4-forms-controls/3-events-change-input/1-deposit-calculator/task.md
@@ -17,5 +17,5 @@ The formula is:
// initial: the initial money sum
// interest: e.g. 0.05 means 5% per year
// years: how many years to wait
-let result = Math.round(initial * (1 + interest * years));
+let result = Math.round(initial * (1 + interest) ** years);
```
diff --git a/2-ui/4-forms-controls/3-events-change-input/article.md b/2-ui/4-forms-controls/3-events-change-input/article.md
index b43a305b3..480197ae5 100644
--- a/2-ui/4-forms-controls/3-events-change-input/article.md
+++ b/2-ui/4-forms-controls/3-events-change-input/article.md
@@ -58,29 +58,50 @@ So we can't use `event.preventDefault()` there -- it's just too late, there woul
These events occur on cutting/copying/pasting a value.
-They belong to [ClipboardEvent](https://www.w3.org/TR/clipboard-apis/#clipboard-event-interfaces) class and provide access to the data that is copied/pasted.
+They belong to [ClipboardEvent](https://www.w3.org/TR/clipboard-apis/#clipboard-event-interfaces) class and provide access to the data that is cut/copied/pasted.
We also can use `event.preventDefault()` to abort the action, then nothing gets copied/pasted.
-For instance, the code below prevents all such events and shows what we are trying to cut/copy/paste:
+For instance, the code below prevents all `cut/copy/paste` events and shows the text we're trying to cut/copy/paste:
```html autorun height=40 run
```
-Please note, that it's possible to copy/paste not just text, but everything. For instance, we can copy a file in the OS file manager, and paste it.
+Please note: inside `cut` and `copy` event handlers a call to `event.clipboardData.getData(...)` returns an empty string. That's because technically the data isn't in the clipboard yet. If we use `event.preventDefault()` it won't be copied at all.
+
+So the example above uses `document.getSelection()` to get the selected text. You can find more details about document selection in the article .
+
+It's possible to copy/paste not just text, but everything. For instance, we can copy a file in the OS file manager, and paste it.
+
+That's because `clipboardData` implements `DataTransfer` interface, commonly used for drag'n'drop and copy/pasting. It's a bit beyond our scope now, but you can find its methods in the [DataTransfer specification](https://html.spec.whatwg.org/multipage/dnd.html#the-datatransfer-interface).
+
+Also, there's an additional asynchronous API of accessing the clipboard: `navigator.clipboard`. More about it in the specification [Clipboard API and events](https://www.w3.org/TR/clipboard-apis/), [not supported by Firefox](https://caniuse.com/async-clipboard).
+
+### Safety restrictions
+
+The clipboard is a "global" OS-level thing. A user may switch between various applications, copy/paste different things, and a browser page shouldn't see all that.
+
+So most browsers allow seamless read/write access to the clipboard only in the scope of certain user actions, such as copying/pasting etc.
+
+It's forbidden to generate "custom" clipboard events with `dispatchEvent` in all browsers except Firefox. And even if we manage to dispatch such event, the specification clearly states that such "synthetic" events must not provide access to the clipboard.
-There's a list of methods [in the specification](https://www.w3.org/TR/clipboard-apis/#dfn-datatransfer) that can work with different data types including files, read/write to the clipboard.
+Even if someone decides to save `event.clipboardData` in an event handler, and then access it later -- it won't work.
-But please note that clipboard is a "global" OS-level thing. Most browsers allow read/write access to the clipboard only in the scope of certain user actions for the safety, e.g. in `onclick` event handlers.
+To reiterate, [event.clipboardData](https://www.w3.org/TR/clipboard-apis/#clipboardevent-clipboarddata) works solely in the context of user-initiated event handlers.
-Also it's forbidden to generate "custom" clipboard events with `dispatchEvent` in all browsers except Firefox.
+On the other hand, [navigator.clipboard](https://www.w3.org/TR/clipboard-apis/#h-navigator-clipboard) is the more recent API, meant for use in any context. It asks for user permission, if needed.
## Summary
@@ -90,4 +111,4 @@ Data change events:
|---------|----------|-------------|
| `change`| A value was changed. | For text inputs triggers on focus loss. |
| `input` | For text inputs on every change. | Triggers immediately unlike `change`. |
-| `cut/copy/paste` | Cut/copy/paste actions. | The action can be prevented. The `event.clipboardData` property gives read/write access to the clipboard. |
+| `cut/copy/paste` | Cut/copy/paste actions. | The action can be prevented. The `event.clipboardData` property gives access to the clipboard. All browsers except Firefox also support `navigator.clipboard`. |
diff --git a/2-ui/5-loading/01-onload-ondomcontentloaded/article.md b/2-ui/5-loading/01-onload-ondomcontentloaded/article.md
index 3d8b98180..07624a658 100644
--- a/2-ui/5-loading/01-onload-ondomcontentloaded/article.md
+++ b/2-ui/5-loading/01-onload-ondomcontentloaded/article.md
@@ -2,7 +2,7 @@
The lifecycle of an HTML page has three important events:
-- `DOMContentLoaded` -- the browser fully loaded HTML, and the DOM tree is built, but external resources like pictures ` ` and stylesheets may be not yet loaded.
+- `DOMContentLoaded` -- the browser fully loaded HTML, and the DOM tree is built, but external resources like pictures ` ` and stylesheets may not yet have loaded.
- `load` -- not only HTML is loaded, but also all the external resources: images, styles etc.
- `beforeunload/unload` -- the user is leaving the page.
@@ -33,7 +33,7 @@ For instance:
function ready() {
alert('DOM is ready');
- // image is not yet loaded (unless was cached), so the size is 0x0
+ // image is not yet loaded (unless it was cached), so the size is 0x0
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
}
@@ -45,7 +45,7 @@ For instance:
```
-In the example the `DOMContentLoaded` handler runs when the document is loaded, so it can see all the elements, including ` ` below.
+In the example, the `DOMContentLoaded` handler runs when the document is loaded, so it can see all the elements, including ` ` below.
But it doesn't wait for the image to load. So `alert` shows zero sizes.
@@ -88,7 +88,7 @@ But there's a pitfall. If we have a script after the style, then that script mus
```html run
```
@@ -114,7 +114,7 @@ The example below correctly shows image sizes, because `window.onload` waits for
```html run height=200 refresh
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/5-loading/02-script-async-defer/article.md b/2-ui/5-loading/02-script-async-defer/article.md
index ca82a7302..f97c000d6 100644
--- a/2-ui/5-loading/02-script-async-defer/article.md
+++ b/2-ui/5-loading/02-script-async-defer/article.md
@@ -3,7 +3,7 @@
In modern websites, scripts are often "heavier" than HTML: their download size is larger, and processing time is also longer.
-When the browser loads HTML and comes across a `` tag, it can't continue building the DOM. It must execute the script right now. The same happens for external scripts ``: the browser must wait until the script downloads, execute it, and only after process the rest of the page.
+When the browser loads HTML and comes across a `` tag, it can't continue building the DOM. It must execute the script right now. The same happens for external scripts ``: the browser must wait for the script to download, execute the downloaded script, and only then can it process the rest of the page.
That leads to two important issues:
@@ -37,7 +37,7 @@ Luckily, there are two `
@@ -68,40 +70,44 @@ The following example demonstrates that:
```
1. The page content shows up immediately.
-2. `DOMContentLoaded` waits for the deferred script. It only triggers when the script `(2)` is downloaded and executed.
+2. `DOMContentLoaded` event handler waits for the deferred script. It only triggers when the script is downloaded and executed.
-Deferred scripts keep their relative order, just like regular scripts.
+**Deferred scripts keep their relative order, just like regular scripts.**
-So, if we have a long script first, and then a smaller one, then the latter one waits.
+Let's say, we have two deferred scripts: the `long.js` and then `small.js`:
```html
```
-```smart header="The small script downloads first, runs second"
-Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The `small.js` probably makes it first.
+Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The `small.js` probably finishes first.
-But the specification requires scripts to execute in the document order, so it waits for `long.js` to execute.
-```
+...But the `defer` attribute, besides telling the browser "not to block", ensures that the relative order is kept. So even though `small.js` loads first, it still waits and runs after `long.js` executes.
+
+That may be important for cases when we need to load a JavaScript library and then a script that depends on it.
```smart header="The `defer` attribute is only for external scripts"
The `defer` attribute is ignored if the `
```
+```smart header="The `async` attribute is only for external scripts"
+Just like `defer`, the `async` attribute is ignored if the `
+```
+
+Here we take the first child of `` (that's the text node) and specify the text positions inside it:
+
+
+
+### Selecting element nodes
+
+**Alternatively, if `node` is an element node, then `offset` must be the child number.**
-```html
+That's handy for making ranges that contain nodes as a whole, not stop somewhere inside their text.
+
+For example, we have a more complex document fragment:
+
+```html autorun
Example: italic and bold
```
-Here's its DOM structure, note that here text nodes are important for us:
+Here's its DOM structure with both element and text nodes:
@@ -72,10 +102,21 @@ let selectPDomtree = {
drawHtmlTree(selectPDomtree, 'div.select-p-domtree', 690, 320);
-Let's select `"Example: italic "`. That's two first children of `` (counting text nodes):
+Let's make a range for `"Example: italic "`.
+
+As we can see, this phrase consists of exactly two children of `
`, with indexes `0` and `1`:

+- The starting point has `
` as the parent `node`, and `0` as the offset.
+
+ So we can set it as `range.setStart(p, 0)`.
+- The ending point also has `
` as the parent `node`, but `2` as the offset (it specifies the range up to, but not including `offset`).
+
+ So we can set it as `range.setEnd(p, 2)`.
+
+Here's the demo. If you run it, you can see that the text gets selected:
+
```html run
Example: italic and bold
@@ -87,18 +128,15 @@ Let's select `"Example: italic "`. That's two first children of `` (cou
range.setEnd(p, 2);
*/!*
- // toString of a range returns its content as text (without tags)
- alert(range); // Example: italic
+ // toString of a range returns its content as text, without tags
+ console.log(range); // Example: italic
- // apply this range for document selection (explained later)
+ // apply this range for document selection (explained later below)
document.getSelection().addRange(range);
```
-- `range.setStart(p, 0)` -- sets the start at the 0th child of `
` (that's the text node `"Example: "`).
-- `range.setEnd(p, 2)` -- spans the range up to (but not including) 2nd child of `
` (that's the text node `" and "`, but as the end is not included, so the last selected node is ``).
-
-Here's a more flexible test stand where you try more variants:
+Here's a more flexible test stand where you can set range start/end numbers and explore other variants:
```html run autorun
Example: italic and bold
@@ -114,26 +152,28 @@ From â To ` first child (taking all but two first letters of "Example: ")
@@ -148,14 +188,20 @@ We need to create a range, that:
range.setStart(p.firstChild, 2);
range.setEnd(p.querySelector('b').firstChild, 3);
- alert(range); // ample: italic and bol
+ console.log(range); // ample: italic and bol
// use this range for selection (explained later)
window.getSelection().addRange(range);
```
-The range object has following properties:
+As you can see, it's fairly easy to make a range of whatever we want.
+
+If we'd like to take nodes as a whole, we can pass elements in `setStart/setEnd`. Otherwise, we can work on the text level.
+
+## Range properties
+
+The range object that we created in the example above has following properties:

@@ -168,9 +214,12 @@ The range object has following properties:
- `commonAncestorContainer` -- the nearest common ancestor of all nodes within the range,
- in the example above: ``
-## Range methods
-There are many convenience methods to manipulate ranges.
+## Range selection methods
+
+There are many convenient methods to manipulate ranges.
+
+We've already seen `setStart` and `setEnd`, here are other similar methods.
Set range start:
@@ -184,15 +233,19 @@ Set range end (similar methods):
- `setEndBefore(node)` set end at: right before `node`
- `setEndAfter(node)` set end at: right after `node`
-**As it was demonstrated, `node` can be both a text or element node: for text nodes `offset` skips that many of characters, while for element nodes that many child nodes.**
+Technically, `setStart/setEnd` can do anything, but more methods provide more convenience.
+
+In all these methods, `node` can be both a text or element node: for text nodes `offset` skips that many of characters, while for element nodes that many child nodes.
-Others:
+Even more methods to create ranges:
- `selectNode(node)` set range to select the whole `node`
- `selectNodeContents(node)` set range to select the whole `node` contents
- `collapse(toStart)` if `toStart=true` set end=start, otherwise set start=end, thus collapsing the range
- `cloneRange()` creates a new range with the same start/end
-To manipulate the content within the range:
+## Range editing methods
+
+Once the range is created, we can manipulate its content using these methods:
- `deleteContents()` -- remove range content from the document
- `extractContents()` -- remove range content from the document and return as [DocumentFragment](info:modifying-document#document-fragment)
@@ -204,7 +257,7 @@ With these methods we can do basically anything with selected nodes.
Here's the test stand to see them in action:
-```html run autorun height=260
+```html run refresh autorun height=260
Click buttons to run methods on the selection, "resetExample" to reset it.
Example: italic and bold
@@ -237,7 +290,7 @@ Click buttons to run methods on the selection, "resetExample" to reset it.
let newNode = document.createElement('u');
try {
range.surroundContents(newNode);
- } catch(e) { alert(e) }
+ } catch(e) { console.log(e) }
},
resetExample() {
p.innerHTML = `Example: italic and bold `;
@@ -259,16 +312,16 @@ Click buttons to run methods on the selection, "resetExample" to reset it.
```
-There also exist methods to compare ranges, but these are rarely used. When you need them, please refer to the [spec](https://dom.spec.whatwg.org/#interface-range) or [MDN manual](https://developer.mozilla.org/en-US/docs/Web/API/Range).
+There also exist methods to compare ranges, but these are rarely used. When you need them, please refer to the [spec](https://dom.spec.whatwg.org/#interface-range) or [MDN manual](mdn:/api/Range).
## Selection
-`Range` is a generic object for managing selection ranges. We may create such objects, pass them around -- they do not visually select anything on their own.
+`Range` is a generic object for managing selection ranges. Although, creating a `Range` doesn't mean that we see a selection on screen.
-The document selection is represented by `Selection` object, that can be obtained as `window.getSelection()` or `document.getSelection()`.
+We may create `Range` objects, pass them around -- they do not visually select anything on their own.
-A selection may include zero or more ranges. At least, the [Selection API specification](https://www.w3.org/TR/selection-api/) says so. In practice though, only Firefox allows to select multiple ranges in the document by using `key:Ctrl+click` (`key:Cmd+click` for Mac).
+The document selection is represented by `Selection` object, that can be obtained as `window.getSelection()` or `document.getSelection()`. A selection may include zero or more ranges. At least, the [Selection API specification](https://www.w3.org/TR/selection-api/) says so. In practice though, only Firefox allows to select multiple ranges in the document by using `key:Ctrl+click` (`key:Cmd+click` for Mac).
Here's a screenshot of a selection with 3 ranges, made in Firefox:
@@ -276,9 +329,19 @@ Here's a screenshot of a selection with 3 ranges, made in Firefox:
Other browsers support at maximum 1 range. As we'll see, some of `Selection` methods imply that there may be many ranges, but again, in all browsers except Firefox, there's at maximum 1.
+Here's a small demo that shows the current selection (select something and click) as text:
+
+alert(document.getSelection())
+
## Selection properties
-Similar to a range, a selection has a start, called "anchor", and the end, called "focus".
+As said, a selection may in theory contain multiple ranges. We can get these range objects using the method:
+
+- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used.
+
+Also, there exist properties that often provide better convenience.
+
+Similar to a range, a selection object has a start, called "anchor", and the end, called "focus".
The main selection properties are:
@@ -289,36 +352,39 @@ The main selection properties are:
- `isCollapsed` -- `true` if selection selects nothing (empty range), or doesn't exist.
- `rangeCount` -- count of ranges in the selection, maximum `1` in all browsers except Firefox.
-````smart header="Selection end may be in the document before start"
-There are many ways to select the content, depending on the user agent: mouse, hotkeys, taps on a mobile etc.
+```smart header="Selection end/start vs Range"
+
+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.
+
+For selections, that's not always the case.
-Some of them, such as a mouse, allow the same selection can be created in two directions: "left-to-right" and "right-to-left".
+Selecting something with a mouse can be done in both directions: either "left-to-right" or "right-to-left".
-If the start (anchor) of the selection goes in the document before the end (focus), this selection is said to have "forward" direction.
+In other words, when the mouse button is pressed, and then it moves forward in the document, then its end (focus) will be after its start (anchor).
E.g. if the user starts selecting with mouse and goes from "Example" to "italic":

-Otherwise, if they go from the end of "italic" to "Example", the selection is directed "backward", its focus will be before the anchor:
+...But the same selection could be done backwards: starting from "italic" to "Example" (backward direction), then its end (focus) will be before the start (anchor):

-
-That's different from `Range` objects that are always directed forward: the range start can't be after its end.
-````
+```
## Selection events
There are events on to keep track of selection:
-- `elem.onselectstart` -- when a selection starts on `elem`, e.g. the user starts moving mouse with pressed button.
- - Preventing the default action makes the selection not start.
-- `document.onselectionchange` -- whenever a selection changes.
- - Please note: this handler can be set only on `document`.
+- `elem.onselectstart` -- when a selection *starts* specifically on element `elem` (or inside it). For instance, when the user presses the mouse button on it and starts to move the pointer.
+ - Preventing the default action cancels the selection start. So starting a selection from this element becomes impossible, but the element is still selectable. The visitor just needs to start the selection from elsewhere.
+- `document.onselectionchange` -- whenever a selection changes or starts.
+ - Please note: this handler can be set only on `document`, it tracks all selections in it.
### Selection tracking demo
-Here's a small demo that shows selection boundaries dynamically as it changes:
+Here's a small demo. It tracks the current selection on the `document` and shows its boundaries:
```html run height=80
Select me: italic and bold
@@ -326,21 +392,25 @@ Here's a small demo that shows selection boundaries dynamically as it changes:
From â To
```
-### Selection getting demo
+### Selection copying demo
+
+There are two approaches to copying the selected content:
-To get the whole selection:
-- As text: just call `document.getSelection().toString()`.
-- As DOM nodes: get the underlying ranges and call their `cloneContents()` method (only first range if we don't support Firefox multiselection).
+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 `getRangeAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere.
-And here's the demo of getting the selection both as text and as DOM nodes:
+Here's the demo of copying the selected content both as text and as DOM nodes:
```html run height=100
Select me: italic and bold
@@ -368,15 +438,15 @@ As text:
## Selection methods
-Selection methods to add/remove ranges:
+We can work with the selection by adding/removing ranges:
-- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except firefox, only `0` is used.
+- `getRangeAt(i)` -- get i-th range, starting from `0`. In all browsers except Firefox, only `0` is used.
- `addRange(range)` -- add `range` to selection. All browsers except Firefox ignore the call, if the selection already has an associated range.
- `removeRange(range)` -- remove `range` from the selection.
- `removeAllRanges()` -- remove all ranges.
- `empty()` -- alias to `removeAllRanges`.
-Also, there are convenience methods to manipulate the selection range directly, without `Range`:
+There are also convenience methods to manipulate the selection range directly, without intermediate `Range` calls:
- `collapse(node, offset)` -- replace selected range with a new one that starts and ends at the given `node`, at position `offset`.
- `setPosition(node, offset)` -- alias to `collapse`.
@@ -388,7 +458,7 @@ Also, there are convenience methods to manipulate the selection range directly,
- `deleteFromDocument()` -- remove selected content from the document.
- `containsNode(node, allowPartialContainment = false)` -- checks whether the selection contains `node` (partially if the second argument is `true`)
-So, for many tasks we can call `Selection` methods, no need to access the underlying `Range` object.
+For most tasks these methods are just fine, there's no need to access the underlying `Range` object.
For example, selecting the whole contents of the paragraph ``:
@@ -415,10 +485,10 @@ The same thing using ranges:
```
-```smart header="To select, remove the existing selection first"
-If the selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges.
+```smart header="To select something, remove the existing selection first"
+If a document selection already exists, empty it first with `removeAllRanges()`. And then add ranges. Otherwise, all browsers except Firefox ignore new ranges.
-The exception is some selection methods, that replace the existing selection, like `setBaseAndExtent`.
+The exception is some selection methods, that replace the existing selection, such as `setBaseAndExtent`.
```
## Selection in form controls
@@ -494,7 +564,7 @@ Focus on me, the cursor will be at position 10.
// zero delay setTimeout to run after browser "focus" action finishes
setTimeout(() => {
// we can set any selection
- // if start=end, the cursor it exactly at that place
+ // if start=end, the cursor is exactly at that place
area.selectionStart = area.selectionEnd = 10;
});
};
@@ -620,7 +690,7 @@ The second API is very simple, as it works with text.
The most used recipes are probably:
1. Getting the selection:
- ```js run
+ ```js
let selection = document.getSelection();
let cloned = /* element to clone the selected nodes to */;
@@ -632,7 +702,7 @@ The most used recipes are probably:
}
```
2. Setting the selection:
- ```js run
+ ```js
let selection = document.getSelection();
// directly:
diff --git a/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg
new file mode 100644
index 000000000..2951607a2
--- /dev/null
+++ b/2-ui/99-ui-misc/02-selection-range/range-hello-1.svg
@@ -0,0 +1 @@
+<p>Hello</p> p.firstChild
\ No newline at end of file
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
new file mode 100644
index 000000000..2911b76cf
--- /dev/null
+++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md
@@ -0,0 +1,50 @@
+The console output is: 1 7 3 5 2 6 4.
+
+The task is quite simple, we just need to know how microtask and macrotask queues work.
+
+Let's see what's going on, step by step.
+
+```js
+console.log(1);
+// The first line executes immediately, it outputs `1`.
+// Macrotask and microtask queues are empty, as of now.
+
+setTimeout(() => console.log(2));
+// `setTimeout` appends the callback to the macrotask queue.
+// - macrotask queue content:
+// `console.log(2)`
+
+Promise.resolve().then(() => console.log(3));
+// The callback is appended to the microtask queue.
+// - microtask queue content:
+// `console.log(3)`
+
+Promise.resolve().then(() => setTimeout(() => console.log(4)));
+// The callback with `setTimeout(...4)` is appended to microtasks
+// - microtask queue content:
+// `console.log(3); setTimeout(...4)`
+
+Promise.resolve().then(() => console.log(5));
+// The callback is appended to the microtask queue
+// - microtask queue content:
+// `console.log(3); setTimeout(...4); console.log(5)`
+
+setTimeout(() => console.log(6));
+// `setTimeout` appends the callback to macrotasks
+// - macrotask queue content:
+// `console.log(2); console.log(6)`
+
+console.log(7);
+// Outputs 7 immediately.
+```
+
+To summarize,
+
+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` 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`.
+
+Finally, we have the output: `1 7 3 5 2 6 4`.
\ No newline at end of file
diff --git a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md
new file mode 100644
index 000000000..ad406b3be
--- /dev/null
+++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/task.md
@@ -0,0 +1,21 @@
+importance: 5
+
+---
+
+# What will be the output of this code?
+
+```js
+console.log(1);
+
+setTimeout(() => console.log(2));
+
+Promise.resolve().then(() => console.log(3));
+
+Promise.resolve().then(() => setTimeout(() => console.log(4)));
+
+Promise.resolve().then(() => console.log(5));
+
+setTimeout(() => console.log(6));
+
+console.log(7);
+```
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 4a1625288..f33188491 100644
--- a/2-ui/99-ui-misc/03-event-loop/article.md
+++ b/2-ui/99-ui-misc/03-event-loop/article.md
@@ -9,7 +9,7 @@ In this chapter we first cover theoretical details about how things work, and th
## Event Loop
-The concept of *event loop* is very simple. There's an endless loop, when JavaScript engine waits for tasks, executes them and then sleeps waiting for more tasks.
+The *event loop* concept is very simple. There's an endless loop, where the JavaScript engine waits for tasks, executes them and then sleeps, waiting for more tasks.
The general algorithm of the engine:
@@ -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. JavaScript engine does nothing most of the time, 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,21 +30,21 @@ 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):

-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. Doesn't matter if the task takes a long time. Changes to DOM are painted only after the task is complete.
-2. If a task takes too long, the browser can't do other tasks, process user events, so after a time it raises an alert like "Page Unresponsive" suggesting to kill the task with the whole page. That happens when there are a lot of complex calculations or a programming error leading to infinite loop.
+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 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 a theory. Now let's see how we can apply that knowledge.
+That was the theory. Now let's see how we can apply that knowledge.
## Use-case 1: splitting CPU-hungry tasks
@@ -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`.
@@ -160,7 +160,7 @@ Finally, we've split a CPU-hungry task into parts - now it doesn't block the use
Another benefit of splitting heavy tasks for browser scripts is that we can show progress indication.
-Usually the browser renders after the currently running code is complete. Doesn't matter if the task takes a long time. Changes to DOM are painted only after the task is finished.
+As mentioned earlier, changes to DOM are painted only after the currently running task is completed, irrespective of how long it takes.
On one hand, that's great, because our function may create many elements, add them one-by-one to the document and change their styles -- the visitor won't see any "intermediate", unfinished state. An important thing, right?
@@ -238,7 +238,7 @@ menu.onclick = function() {
## Macrotasks and Microtasks
-Along with *macrotasks*, described in this chapter, there exist *microtasks*, mentioned in the chapter .
+Along with *macrotasks*, described in this chapter, there are *microtasks*, mentioned in the chapter .
Microtasks come solely from our code. They are usually created by promises: an execution of `.then/catch/finally` handler becomes a microtask. Microtasks are used "under the cover" of `await` as well, as it's another form of promise handling.
@@ -303,7 +303,7 @@ Here's an example with "counting progress bar", similar to the one shown previou
## Summary
-The more detailed algorithm of the event loop (though still simplified compare to the [specification](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)):
+A more detailed event loop algorithm (though still simplified compared to the [specification](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)):
1. Dequeue and run the oldest task from the *macrotask* queue (e.g. "script").
2. Execute all *microtasks*:
@@ -316,7 +316,7 @@ The more detailed algorithm of the event loop (though still simplified compare t
To schedule a new *macrotask*:
- Use zero delayed `setTimeout(f)`.
-That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react on user events and show progress between them.
+That may be used to split a big calculation-heavy task into pieces, for the browser to be able to react to user events and show progress between them.
Also, used in event handlers to schedule an action after the event is fully handled (bubbling done).
diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md
index 1ce04b7c4..f2c87d1e0 100644
--- a/3-frames-and-windows/01-popup-windows/article.md
+++ b/3-frames-and-windows/01-popup-windows/article.md
@@ -7,7 +7,7 @@ Basically, you just run:
window.open('https://javascript.info/')
```
-...And it will open a new window with given URL. Most modern browsers are configured to open new tabs instead of separate windows.
+...And it will open a new window with given URL. Most modern browsers are configured to open url in new tabs instead of separate windows.
Popups exist from really ancient times. The initial idea was to show another content without closing the main window. As of now, there are other ways to do that: we can load content dynamically with [fetch](info:fetch) and show it in a dynamically generated ``. So, popups is not something we use everyday.
@@ -15,7 +15,7 @@ Also, popups are tricky on mobile devices, that don't show multiple windows simu
Still, there are tasks where popups are still used, e.g. for OAuth authorization (login with Google/Facebook/...), because:
-1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe.
+1. A popup is a separate window which has its own independent JavaScript environment. So opening a popup from a third-party, non-trusted site is safe.
2. It's very easy to open a popup.
3. A popup can navigate (change URL) and send messages to the opener window.
@@ -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)`:
@@ -69,7 +49,7 @@ name
: A name of the new window. Each window has a `window.name`, and here we can specify which window to use for the popup. If there's already a window with such name -- the given URL opens in it, otherwise a new window is opened.
params
-: The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: `width:200,height=100`.
+: The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: `width=200,height=100`.
Settings for `params`:
@@ -87,9 +67,9 @@ Settings for `params`:
There is also a number of less supported browser-specific features, which are usually not used. Check
window.open in MDN for examples.
-## Example: a minimalistic window
+## Example: a minimalistic window
-Let's open a window with minimal set of features just to see which of them browser allows to disable:
+Let's open a window with minimal set of features, just to see which of them browser allows to disable:
```js run
let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,
@@ -120,7 +100,7 @@ Rules for omitted settings:
## Accessing popup from window
-The `open` call returns a reference to the new window. It can be used to manipulate it's properties, change location and even more.
+The `open` call returns a reference to the new window. It can be used to manipulate its properties, change location and even more.
In this example, we generate popup content from JavaScript:
@@ -192,7 +172,7 @@ newWindow.onload = function() {
```
-## Scrolling and resizing
+## Moving and resizing
There are methods to move/resize a window:
@@ -237,28 +217,30 @@ There's also `window.onscroll` event.
## Focus/blur on a window
-Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. Also there are `focus/blur` events that allow to focus a window and catch the moment when the visitor switches elsewhere.
+Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. And there are also `focus/blur` events that allow to catch the moment when the visitor focuses on a window and switches elsewhere.
+
+Although, in practice they are severely limited, because in the past evil pages abused them.
-In the past evil pages abused those. For instance, look at this code:
+For instance, look at this code:
```js run
window.onblur = () => window.focus();
```
-When a user attempts to switch out of the window (`blur`), it brings it back to focus. The intention is to "lock" the user within the `window`.
+When a user attempts to switch out of the window (`window.onblur`), it brings the window back into focus. The intention is to "lock" the user within the `window`.
-So, there are limitations that forbid the code like that. There are many limitations to protect the user from ads and evils pages. They depend on the browser.
+So browsers had to introduce many limitations to forbid the code like that and protect the user from ads and evils pages. They depend on the browser.
-For instance, a mobile browser usually ignores that call completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window.
+For instance, a mobile browser usually ignores `window.focus()` completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window.
-Still, there are some things that can be done.
+Still, there are some use cases when such calls do work and can be useful.
For instance:
-- When we open a popup, it's might be a good idea to run a `newWindow.focus()` on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now.
+- When we open a popup, it might be a good idea to run `newWindow.focus()` on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now.
- If we want to track when a visitor actually uses our web-app, we can track `window.onfocus/onblur`. That allows us to suspend/resume in-page activities, animations etc. But please note that the `blur` event means that the visitor switched out from the window, but they still may observe it. The window is in the background, but still may be visible.
-## Summary
+## Summary
Popup windows are used rarely, as there are alternatives: loading and displaying information in-page, or in iframe.
diff --git a/3-frames-and-windows/03-cross-window-communication/article.md b/3-frames-and-windows/03-cross-window-communication/article.md
index 53f5f55fc..4d4e320e4 100644
--- a/3-frames-and-windows/03-cross-window-communication/article.md
+++ b/3-frames-and-windows/03-cross-window-communication/article.md
@@ -116,6 +116,13 @@ document.domain = 'site.com';
That's all. Now they can interact without limitations. Again, that's only possible for pages with the same second-level domain.
+```warn header="Deprecated, but still working"
+The `document.domain` property is in the process of being removed from the [specification](https://html.spec.whatwg.org/multipage/origin.html#relaxing-the-same-origin-restriction). The cross-window messaging (explained soon below) is the suggested replacement.
+
+That said, as of now all browsers support it. And the support will be kept for the future, not to break old code that relies on `document.domain`.
+```
+
+
## Iframe: wrong document pitfall
When an iframe comes from the same origin, and we may access its `document`, there's a pitfall. It's not related to cross-origin things, but important to know.
@@ -263,12 +270,12 @@ The window that wants to send a message calls [postMessage](mdn:api/Window.postM
Arguments:
`data`
-: The data to send. Can be any object, the data is cloned using the "structured cloning algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser.
+: The data to send. Can be any object, the data is cloned using the "structured serialization algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser.
`targetOrigin`
: Specifies the origin for the target window, so that only a window from the given origin will get the message.
-The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read it's `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it.
+The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read its `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it.
Specifying `targetOrigin` ensures that the window only receives the data if it's still at the right site. Important when the data is sensitive.
diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md
index 1daa87dd0..34d0a91ae 100644
--- a/3-frames-and-windows/06-clickjacking/article.md
+++ b/3-frames-and-windows/06-clickjacking/article.md
@@ -154,7 +154,7 @@ Depending on your browser, the `iframe` above is either empty or alerting you th
## Showing with disabled functionality
-The `X-Frame-Options` header has a side-effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so.
+The `X-Frame-Options` header has a side effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so.
So there are other solutions... For instance, we can "cover" the page with a `
` with styles `height: 100%; width: 100%;`, so that it will intercept all clicks. That `
` is to be removed if `window == top` or if we figure out that we don't need the protection.
diff --git a/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js b/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js
index 2f51384ef..00c37bb94 100644
--- a/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js
+++ b/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js
@@ -2,9 +2,9 @@ function concat(arrays) {
// sum of individual array lengths
let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
- if (!arrays.length) return null;
-
let result = new Uint8Array(totalLength);
+
+ if (!arrays.length) return result;
// for each array - copy it over result
// next array is copied right after the previous one
diff --git a/4-binary/01-arraybuffer-binary-arrays/article.md b/4-binary/01-arraybuffer-binary-arrays/article.md
index 278f41f73..2827e277e 100644
--- a/4-binary/01-arraybuffer-binary-arrays/article.md
+++ b/4-binary/01-arraybuffer-binary-arrays/article.md
@@ -30,11 +30,11 @@ Let's eliminate a possible source of confusion. `ArrayBuffer` has nothing in com
**To manipulate an `ArrayBuffer`, we need to use a "view" object.**
-A view object does not store anything on it's own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`.
+A view object does not store anything on its own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`.
For instance:
-- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer".
+- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer".
- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer".
- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer".
- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from
5.0x10-324 to
1.8x10308 .
@@ -71,13 +71,13 @@ for(let num of view) {
## TypedArray
-The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities.
+The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properties.
Please note, there's no constructor called `TypedArray`, it's just a common "umbrella" term to represent one of views over `ArrayBuffer`: `Int8Array`, `Uint8Array` and so on, the full list will soon follow.
When you see something like `new TypedArray`, it means any of `new Int8Array`, `new Uint8Array`, etc.
-Typed array behave like regular arrays: have indexes and iterable.
+Typed arrays behave like regular arrays: have indexes and are iterable.
A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types.
@@ -126,9 +126,9 @@ new TypedArray();
We can create a `TypedArray` directly, without mentioning `ArrayBuffer`. But a view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these cases except the first one (when provided).
-To access the `ArrayBuffer`, there are properties:
-- `arr.buffer` -- references the `ArrayBuffer`.
-- `arr.byteLength` -- the length of the `ArrayBuffer`.
+To access the underlying `ArrayBuffer`, there are following properties in `TypedArray`:
+- `buffer` -- references the `ArrayBuffer`.
+- `byteLength` -- the length of the `ArrayBuffer`.
So, we can always move from one view to another:
```js
@@ -209,7 +209,7 @@ These methods allow us to copy typed arrays, mix them, create new arrays from ex
## DataView
-[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format.
+[DataView](mdn:/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format.
- For typed arrays, the constructor dictates what the format is. The whole array is supposed to be uniform. The i-th number is `arr[i]`.
- With `DataView` we access the data with methods like `.getUint8(i)` or `.getUint16(i)`. We choose the format at method call time instead of the construction time.
@@ -235,7 +235,7 @@ let dataView = new DataView(buffer);
// get 8-bit number at offset 0
alert( dataView.getUint8(0) ); // 255
-// now get 16-bit number at offset 0, it consists of 2 bytes, together iterpreted as 65535
+// now get 16-bit number at offset 0, it consists of 2 bytes, together interpreted as 65535
alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)
// get 32-bit number at offset 0
@@ -244,7 +244,7 @@ alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
dataView.setUint32(0, 0); // set 4-byte number to zero, thus setting all bytes to 0
```
-`DataView` is great when we store mixed-format data in the same buffer. E.g we store a sequence of pairs (16-bit integer, 32-bit float). Then `DataView` allows to access them easily.
+`DataView` is great when we store mixed-format data in the same buffer. For example, when we store a sequence of pairs (16-bit integer, 32-bit float), `DataView` allows to access them easily.
## Summary
@@ -259,7 +259,7 @@ To do almost any operation on `ArrayBuffer`, we need a view.
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
- Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`.
-In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed.
+In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common denominator". We can access it as `.buffer` and make another view if needed.
There are also two additional terms, that are used in descriptions of methods that operate on binary data:
- `ArrayBufferView` is an umbrella term for all these kinds of views.
diff --git a/4-binary/02-text-decoder/article.md b/4-binary/02-text-decoder/article.md
index fe9b8c042..a0c80145c 100644
--- a/4-binary/02-text-decoder/article.md
+++ b/4-binary/02-text-decoder/article.md
@@ -2,7 +2,7 @@
What if the binary data is actually a string? For instance, we received a file with textual data.
-The build-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows to read the value into an actual JavaScript string, given the buffer and the encoding.
+The built-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows one to read the value into an actual JavaScript string, given the buffer and the encoding.
We first need to create it:
```js
@@ -12,7 +12,7 @@ let decoder = new TextDecoder([label], [options]);
- **`label`** -- the encoding, `utf-8` by default, but `big5`, `windows-1251` and many other are also supported.
- **`options`** -- optional object:
- **`fatal`** -- boolean, if `true` then throw an exception for invalid (non-decodable) characters, otherwise (default) replace them with character `\uFFFD`.
- - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order unicode mark), rarely needed.
+ - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order Unicode mark), rarely needed.
...And then decode:
diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md
index 062e1834b..fc0150577 100644
--- a/4-binary/03-blob/article.md
+++ b/4-binary/03-blob/article.md
@@ -55,7 +55,7 @@ This behavior is similar to JavaScript strings: we can't change a character in a
## Blob as URL
-A Blob can be easily used as an URL for `
`, ` ` or other tags, to show its contents.
+A Blob can be easily used as a URL for ` `, ` ` or other tags, to show its contents.
Thanks to `type`, we can also download/upload `Blob` objects, and the `type` naturally becomes `Content-Type` in network requests.
@@ -74,7 +74,7 @@ link.href = URL.createObjectURL(blob);
We can also create a link dynamically in JavaScript and simulate a click by `link.click()`, then download starts automatically.
-Here's the similar code that causes user to download the dynamicallly created `Blob`, without any HTML:
+Here's the similar code that causes user to download the dynamically created `Blob`, without any HTML:
```js run
let link = document.createElement('a');
@@ -97,11 +97,11 @@ That's what the value of `link.href` looks like:
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
```
-The browser for each URL generated by `URL.createObjectURL` stores an the URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`.
+For each URL generated by `URL.createObjectURL` the browser stores a URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`.
-A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ` `, ` `, basically any other object that expects an url.
+A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ` `, ` `, basically any other object that expects a URL.
-There's a side-effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it.
+There's a side effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it.
The mapping is automatically cleared on document unload, so `Blob` objects are freed then. But if an app is long-living, then that doesn't happen soon.
@@ -119,7 +119,7 @@ An alternative to `URL.createObjectURL` is to convert a `Blob` into a base64-enc
That encoding represents binary data as a string of ultra-safe "readable" characters with ASCII-codes from 0 to 64. And what's more important -- we can use this encoding in "data-urls".
-A [data url](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls.
+A [data url](mdn:/http/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls.
For instance, here's a smiley:
@@ -151,7 +151,7 @@ reader.onload = function() {
};
```
-Both ways of making an URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster.
+Both ways of making a URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster.
```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url"
+ We need to revoke them if care about memory.
@@ -166,8 +166,8 @@ We can create a `Blob` of an image, an image part, or even make a page screensho
Image operations are done via `` element:
-1. Draw an image (or its part) on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage).
-2. Call canvas method [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done.
+1. Draw an image (or its part) on canvas using [canvas.drawImage](mdn:/api/CanvasRenderingContext2D/drawImage).
+2. Call canvas method [.toBlob(callback, format, quality)](mdn:/api/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done.
In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob:
@@ -186,7 +186,7 @@ let context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
// we can context.rotate(), and do many other things on canvas
-// toBlob is async opereation, callback is called when done
+// toBlob is async operation, callback is called when done
canvas.toBlob(function(blob) {
// blob ready, download it
let link = document.createElement('a');
@@ -211,21 +211,44 @@ For screenshotting a page, we can use a library such as /* process the ArrayBuffer */);
```
+## From Blob to stream
+
+When we read and write to a blob of more than `2 GB`, the use of `arrayBuffer` becomes more memory intensive for us. At this point, we can directly convert the blob to a stream.
+
+A stream is a special object that allows to read from it (or write into it) portion by portion. It's outside of our scope here, but here's an example, and you can read more at . Streams are convenient for data that is suitable for processing piece-by-piece.
+
+The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the `Blob`.
+
+Then we can read from it, like this:
+
+```js
+// get readableStream from blob
+const readableStream = blob.stream();
+const stream = readableStream.getReader();
+
+while (true) {
+ // for each iteration: value is the next blob fragment
+ let { done, value } = await stream.read();
+ if (done) {
+ // no more data in the stream
+ console.log('all blob processed.');
+ break;
+ }
+
+ // do something with the data portion we've just read from the blob
+ console.log(value);
+}
+```
## Summary
@@ -235,7 +258,9 @@ That makes Blobs convenient for upload/download operations, that are so common i
Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types.
-We can easily convert betweeen `Blob` and low-level binary data types:
+We can easily convert between `Blob` and low-level binary data types:
+
+- We can make a `Blob` from a typed array using `new Blob(...)` constructor.
+- We can get back `ArrayBuffer` from a Blob using `blob.arrayBuffer()`, and then create a view over it for low-level binary processing.
-- We can make a Blob from a typed array using `new Blob(...)` constructor.
-- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing.
+Conversion streams are very useful when we need to handle large blob. You can easily create a `ReadableStream` from a blob. The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the blob.
diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md
index 6acd8cb59..4669fc451 100644
--- a/5-network/01-fetch/article.md
+++ b/5-network/01-fetch/article.md
@@ -27,7 +27,7 @@ let promise = fetch(url, [options])
- **`url`** -- the URL to access.
- **`options`** -- optional parameters: method, headers etc.
-Without `options`, that is a simple GET request, downloading the contents of the `url`.
+Without `options`, this is a simple GET request, downloading the contents of the `url`.
The browser starts the request right away and returns a promise that the calling code should use to get the result.
@@ -65,7 +65,7 @@ if (response.ok) { // if HTTP-status is 200-299
- **`response.json()`** -- parse the response as JSON,
- **`response.formData()`** -- return the response as `FormData` object (explained in the [next chapter](info:formdata)),
- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type),
-- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representaion of binary data),
+- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representation of binary data),
- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows you to read the body chunk-by-chunk, we'll see an example later.
For instance, let's get a JSON-object with latest commits from GitHub:
@@ -195,7 +195,7 @@ To make a `POST` request, or a request with another method, we need to use `fetc
- **`method`** -- HTTP-method, e.g. `POST`,
- **`body`** -- the request body, one of:
- a string (e.g. JSON-encoded),
- - `FormData` object, to submit the data as `form/multipart`,
+ - `FormData` object, to submit the data as `multipart/form-data`,
- `Blob`/`BufferSource` to send binary data,
- [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used.
@@ -298,13 +298,13 @@ fetch(url, options)
Response properties:
- `response.status` -- HTTP code of the response,
-- `response.ok` -- `true` is the status is 200-299.
+- `response.ok` -- `true` if the status is 200-299.
- `response.headers` -- Map-like object with HTTP headers.
Methods to get response body:
- **`response.text()`** -- return the response as text,
- **`response.json()`** -- parse the response as JSON object,
-- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, see the next chapter),
+- **`response.formData()`** -- return the response as `FormData` object (`multipart/form-data` encoding, see the next chapter),
- **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type),
- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level binary data),
diff --git a/5-network/02-formdata/article.md b/5-network/02-formdata/article.md
index d281d0756..a73d554b1 100644
--- a/5-network/02-formdata/article.md
+++ b/5-network/02-formdata/article.md
@@ -47,7 +47,7 @@ As you can see, that's almost one-liner:
```
-In this example, the server code is not presented, as it's beyound our scope. The server accepts the POST request and replies "User saved".
+In this example, the server code is not presented, as it's beyond our scope. The server accepts the POST request and replies "User saved".
## FormData Methods
@@ -75,7 +75,7 @@ formData.append('key2', 'value2');
// List key/value pairs
for(let [name, value] of formData) {
- alert(`${name} = ${value}`); // key1=value1, then key2=value2
+ alert(`${name} = ${value}`); // key1 = value1, then key2 = value2
}
```
@@ -168,7 +168,7 @@ The server reads form data and the file, as if it were a regular form submission
[FormData](https://xhr.spec.whatwg.org/#interface-formdata) objects are used to capture HTML form and submit it using `fetch` or another network method.
-We can either create `new FormData(form)` from an HTML form, or create a object without a form at all, and then append fields with methods:
+We can either create `new FormData(form)` from an HTML form, or create an object without a form at all, and then append fields with methods:
- `formData.append(name, value)`
- `formData.append(name, blob, fileName)`
diff --git a/5-network/03-fetch-progress/article.md b/5-network/03-fetch-progress/article.md
index 2d003157d..76b05d514 100644
--- a/5-network/03-fetch-progress/article.md
+++ b/5-network/03-fetch-progress/article.md
@@ -5,11 +5,11 @@ The `fetch` method allows to track *download* progress.
Please note: there's currently no way for `fetch` to track *upload* progress. For that purpose, please use [XMLHttpRequest](info:xmlhttprequest), we'll cover it later.
-To track download progress, we can use `response.body` property. It's `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification.
+To track download progress, we can use `response.body` property. It's a `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification.
Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can count how much is consumed at any moment.
-Here's the sketch of code that reads the reponse from `response.body`:
+Here's the sketch of code that reads the response from `response.body`:
```js
// instead of response.json() and other methods
@@ -110,3 +110,5 @@ Let's explain that step-by-step:
At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process.
Once again, please note, that's not for *upload* progress (no way now with `fetch`), only for *download* progress.
+
+Also, if the size is unknown, we should check `receivedLength` in the loop and break it once it reaches a certain limit. So that the `chunks` won't overflow the memory.
diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md
index 757846287..eadc5aac2 100644
--- a/5-network/04-fetch-abort/article.md
+++ b/5-network/04-fetch-abort/article.md
@@ -1,61 +1,81 @@
# Fetch: Abort
-As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we abort a `fetch`?
+As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we cancel an ongoing `fetch`? E.g. if the user actions on our site indicate that the `fetch` isn't needed any more.
-There's a special built-in object for such purposes: `AbortController`, that can be used to abort not only `fetch`, but other asynchronous tasks as well.
+There's a special built-in object for such purposes: `AbortController`. It can be used to abort not only `fetch`, but other asynchronous tasks as well.
-The usage is pretty simple:
+The usage is very straightforward:
-- Step 1: create a controller:
+## The AbortController object
- ```js
- let controller = new AbortController();
- ```
+Create a controller:
- A controller is an extremely simple object.
+```js
+let controller = new AbortController();
+```
- - It has a single method `abort()`, and a single property `signal`.
- - When `abort()` is called:
- - `abort` event triggers on `controller.signal`
- - `controller.signal.aborted` property becomes `true`.
+A controller is an extremely simple object.
- All parties interested to learn about `abort()` call set listeners on `controller.signal` to track it.
+- It has a single method `abort()`,
+- And a single property `signal` that allows to set event listeners on it.
- Like this (without `fetch` yet):
+When `abort()` is called:
+- `controller.signal` emits the `"abort"` event.
+- `controller.signal.aborted` property becomes `true`.
- ```js run
- let controller = new AbortController();
- let signal = controller.signal;
+Generally, we have two parties in the process:
+1. The one that performs a cancelable operation, it sets a listener on `controller.signal`.
+2. The one that cancels: it calls `controller.abort()` when needed.
- // triggers when controller.abort() is called
- signal.addEventListener('abort', () => alert("abort!"));
+Here's the full example (without `fetch` yet):
- controller.abort(); // abort!
+```js run
+let controller = new AbortController();
+let signal = controller.signal;
+
+// The party that performs a cancelable operation
+// gets the "signal" object
+// and sets the listener to trigger when controller.abort() is called
+signal.addEventListener('abort', () => alert("abort!"));
- alert(signal.aborted); // true
- ```
+// The other party, that cancels (at any point later):
+controller.abort(); // abort!
-- Step 2: pass the `signal` property to `fetch` option:
+// The event triggers and signal.aborted becomes true
+alert(signal.aborted); // true
+```
- ```js
- let controller = new AbortController();
- fetch(url, {
- signal: controller.signal
- });
- ```
+As we can see, `AbortController` is just a mean to pass `abort` events when `abort()` is called on it.
- The `fetch` method knows how to work with `AbortController`, it listens to `abort` on `signal`.
+We could implement the same kind of event listening in our code on our own, without the `AbortController` object.
-- Step 3: to abort, call `controller.abort()`:
+But what's valuable is that `fetch` knows how to work with the `AbortController` object. It's integrated in it.
- ```js
- controller.abort();
- ```
+## Using with fetch
- We're done: `fetch` gets the event from `signal` and aborts the request.
+To be able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option:
-When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`:
+```js
+let controller = new AbortController();
+fetch(url, {
+ signal: controller.signal
+});
+```
+
+The `fetch` method knows how to work with `AbortController`. It will listen to `abort` events on `signal`.
+
+Now, to abort, call `controller.abort()`:
+
+```js
+controller.abort();
+```
+
+We're done: `fetch` gets the event from `signal` and aborts the request.
+
+When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`.
+
+Here's the full example with `fetch` aborted after 1 second:
```js run async
// abort in 1 second
@@ -75,28 +95,31 @@ try {
}
```
-**`AbortController` is scalable, it allows to cancel multiple fetches at once.**
+## AbortController is scalable
-For instance, here we fetch many `urls` in parallel, and the controller aborts them all:
+`AbortController` is scalable. It allows to cancel multiple fetches at once.
+
+Here's a sketch of code that fetches many `urls` in parallel, and uses a single controller to abort them all:
```js
let urls = [...]; // a list of urls to fetch in parallel
let controller = new AbortController();
+// an array of fetch promises
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
let results = await Promise.all(fetchJobs);
-// if controller.abort() is called from elsewhere,
+// if controller.abort() is called from anywhere,
// it aborts all fetches
```
-If we have our own asynchronous jobs, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches.
+If we have our own asynchronous tasks, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches.
-We just need to listen to its `abort` event:
+We just need to listen to its `abort` event in our tasks:
```js
let urls = [...];
@@ -114,8 +137,12 @@ let fetchJobs = urls.map(url => fetch(url, { // fetches
// Wait for fetches and our task in parallel
let results = await Promise.all([...fetchJobs, ourJob]);
-// if controller.abort() is called from elsewhere,
+// if controller.abort() is called from anywhere,
// it aborts all fetches and ourJob
```
-So `AbortController` is not only for `fetch`, it's a universal object to abort asynchronous tasks, and `fetch` has built-in integration with it.
+## Summary
+
+- `AbortController` is a simple object that generates an `abort` event on its `signal` property when the `abort()` method is called (and also sets `signal.aborted` to `true`).
+- `fetch` integrates with it: we pass the `signal` property as the option, and then `fetch` listens to it, so it's possible to abort the `fetch`.
+- We can use `AbortController` in our code. The "call `abort()`" -> "listen to `abort` event" interaction is simple and universal. We can use it even without `fetch`.
diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md
index 31fb0b8be..4420f43c7 100644
--- a/5-network/05-fetch-crossorigin/article.md
+++ b/5-network/05-fetch-crossorigin/article.md
@@ -28,7 +28,7 @@ Seriously. Let's make a very brief historical digression.
**For many years a script from one site could not access the content of another site.**
-That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access user's mailbox at website `gmail.com`. People felt safe.
+That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access the user's mailbox at website `gmail.com`. People felt safe.
JavaScript also did not have any special methods to perform network requests at that time. It was a toy language to decorate a web page.
@@ -44,7 +44,7 @@ One way to communicate with another server was to submit a `` there. Peopl
*/!*
-
+
*!*
*/!*
@@ -97,43 +97,43 @@ After a while, networking methods appeared in browser JavaScript.
At first, cross-origin requests were forbidden. But as a result of long discussions, cross-origin requests were allowed, but with any new capabilities requiring an explicit allowance by the server, expressed in special headers.
-## Simple requests
+## Safe requests
There are two types of cross-origin requests:
-1. Simple requests.
+1. Safe requests.
2. All the others.
-Simple Requests are, well, simpler to make, so let's start with them.
+Safe Requests are simpler to make, so let's start with them.
-A [simple request](http://www.w3.org/TR/cors/#terminology) is a request that satisfies two conditions:
+A request is safe if it satisfies two conditions:
-1. [Simple method](http://www.w3.org/TR/cors/#simple-method): GET, POST or HEAD
-2. [Simple headers](http://www.w3.org/TR/cors/#simple-header) -- the only allowed custom headers are:
+1. [Safe method](https://fetch.spec.whatwg.org/#cors-safelisted-method): GET, POST or HEAD
+2. [Safe headers](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) -- the only allowed custom headers are:
- `Accept`,
- `Accept-Language`,
- `Content-Language`,
- `Content-Type` with the value `application/x-www-form-urlencoded`, `multipart/form-data` or `text/plain`.
-Any other request is considered "non-simple". For instance, a request with `PUT` method or with an `API-Key` HTTP-header does not fit the limitations.
+Any other request is considered "unsafe". For instance, a request with `PUT` method or with an `API-Key` HTTP-header does not fit the limitations.
-**The essential difference is that a "simple request" can be made with a ` ` or a `
```
-Now let's cover animation properties one by one.
+Now, let's cover animation properties one by one.
## transition-property
-In `transition-property` we write a list of property to animate, for instance: `left`, `margin-left`, `height`, `color`.
+In `transition-property`, we write a list of properties to animate, for instance: `left`, `margin-left`, `height`, `color`. Or we could write `all`, which means "animate all properties".
-Not all properties can be animated, but [many of them](http://www.w3.org/TR/css3-transitions/#animatable-properties-). The value `all` means "animate all properties".
+Do note that, there are properties which can not be animated. However, [most of the generally used properties are animatable](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties).
## transition-duration
-In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](http://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`.
+In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](https://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`.
## transition-delay
-In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay: 1s`, then animation starts after 1 second after the change.
+In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay` is `1s` and `transition-duration` is `2s`, then the animation starts 1 second after the property change and the total duration will be 2 seconds.
-Negative values are also possible. Then the animation starts from the middle. For instance, if `transition-duration` is `2s`, and the delay is `-1s`, then the animation takes 1 second and starts from the half.
+Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if `transition-delay` is `-1s` and `transition-duration` is `2s`, then animation starts from the halfway point and total duration will be 1 second.
-Here's the animation shifts numbers from `0` to `9` using CSS `translate` property:
+Here the animation shifts numbers from `0` to `9` using CSS `translate` property:
[codetabs src="digits"]
@@ -108,13 +108,13 @@ In the example above JavaScript adds the class `.animate` to the element -- and
stripe.classList.add('animate');
```
-We can also start it "from the middle", from the exact number, e.g. corresponding to the current second, using the negative `transition-delay`.
+We could also start it from somewhere in the middle of the transition, from an exact number, e.g. corresponding to the current second, using a negative `transition-delay`.
Here if you click the digit -- it starts the animation from the current second:
[codetabs src="digits-negative-delay"]
-JavaScript does it by an extra line:
+JavaScript does it with an extra line:
```js
stripe.onclick = function() {
@@ -129,25 +129,25 @@ stripe.onclick = function() {
## transition-timing-function
-Timing function describes how the animation process is distributed along the time. Will it start slowly and then go fast or vise versa.
+The timing function describes how the animation process is distributed along its timeline. Will it start slowly and then go fast, or vice versa.
-That's the most complicated property from the first sight. But it becomes very simple if we devote a bit time to it.
+It appears to be the most complicated property at first. But it becomes very simple if we devote a bit time to it.
-That property accepts two kinds of values: a Bezier curve or steps. Let's start from the curve, as it's used more often.
+That property accepts two kinds of values: a Bezier curve or steps. Let's start with the curve, as it's used more often.
### Bezier curve
-The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfies the conditions:
+The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfy the conditions:
1. First control point: `(0,0)`.
2. Last control point: `(1,1)`.
-3. For intermediate points values of `x` must be in the interval `0..1`, `y` can be anything.
+3. For intermediate points, the values of `x` must be in the interval `0..1`, `y` can be anything.
The syntax for a Bezier curve in CSS: `cubic-bezier(x2, y2, x3, y3)`. Here we need to specify only 2nd and 3rd control points, because the 1st one is fixed to `(0,0)` and the 4th one is `(1,1)`.
-The timing function describes how fast the animation process goes in time.
+The timing function describes how fast the animation process goes.
-- The `x` axis is the time: `0` -- the starting moment, `1` -- the last moment of `transition-duration`.
+- The `x` axis is the time: `0` -- the start, `1` -- the end of `transition-duration`.
- The `y` axis specifies the completion of the process: `0` -- the starting value of the property, `1` -- the final value.
The simplest variant is when the animation goes uniformly, with the same linear speed. That can be specified by the curve `cubic-bezier(0, 0, 1, 1)`.
@@ -168,7 +168,7 @@ The CSS `transition` is based on that curve:
.train {
left: 0;
transition: left 5s cubic-bezier(0, 0, 1, 1);
- /* JavaScript sets left to 450px */
+ /* click on a train sets left to 450px, thus triggering the animation */
}
```
@@ -191,13 +191,13 @@ CSS:
.train {
left: 0;
transition: left 5s cubic-bezier(0, .5, .5, 1);
- /* JavaScript sets left to 450px */
+ /* click on a train sets left to 450px, thus triggering the animation */
}
```
There are several built-in curves: `linear`, `ease`, `ease-in`, `ease-out` and `ease-in-out`.
-The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, we saw it already.
+The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, which we described above.
Other names are shorthands for the following `cubic-bezier`:
@@ -210,27 +210,27 @@ Other names are shorthands for the following `cubic-bezier`:
So we could use `ease-out` for our slowing down train:
-
```css
.train {
left: 0;
transition: left 5s ease-out;
- /* transition: left 5s cubic-bezier(0, .5, .5, 1); */
+ /* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */
}
```
But it looks a bit differently.
-**A Bezier curve can make the animation "jump out" of its range.**
+**A Bezier curve can make the animation exceed its range.**
-The control points on the curve can have any `y` coordinates: even negative or huge. Then the Bezier curve would also jump very low or high, making the animation go beyond its normal range.
+The control points on the curve can have any `y` coordinates: even negative or huge ones. Then the Bezier curve would also extend very low or high, making the animation go beyond its normal range.
In the example below the animation code is:
+
```css
.train {
left: 100px;
transition: left 5s cubic-bezier(.5, -1, .5, 2);
- /* JavaScript sets left to 400px */
+ /* click on a train sets left to 450px */
}
```
@@ -244,21 +244,29 @@ But if you click the train, you'll see that:
[codetabs src="train-over"]
-Why it happens -- pretty obvious if we look at the graph of the given Bezier curve:
+Why it happens is pretty obvious if we look at the graph of the given Bezier curve:

-We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made put it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`.
+We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`.
-As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property lower than the starting `left` and `y>1` -- over the final `left`.
+As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property beyond the starting `left` and `y>1` -- past the final `left`.
That's a "soft" variant for sure. If we put `y` values like `-99` and `99` then the train would jump out of the range much more.
-But how to make the Bezier curve for a specific task? There are many tools. For instance, we can do it on the site .
+But how do we make a Bezier curve for a specific task? There are many tools.
+
+- For instance, we can do it on the site .
+- Browser developer tools also have special support for Bezier curves in CSS:
+ 1. Open the developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`).
+ 2. Select the `Elements` tab, then pay attention to the `Styles` sub-panel at the right side.
+ 3. CSS properties with a word `cubic-bezier` will have an icon before this word.
+ 4. Click this icon to edit the curve.
+
### Steps
-Timing function `steps(number of steps[, start/end])` allows to split animation into steps.
+The timing function `steps(number of steps[, start/end])` allows splitting an transition into multiple steps.
Let's see that in an example with digits.
@@ -266,7 +274,19 @@ Here's a list of digits, without any animations, just as a source:
[codetabs src="step-list"]
-We'll make the digits appear in a discrete way by making the part of the list outside of the red "window" invisible and shifting the list to the left with each step.
+In the HTML, a stripe of digits is enclosed into a fixed-length ``:
+
+```html
+
+```
+
+The `#digit` div has a fixed width and a border, so it looks like a red window.
+
+We'll make a timer: the digits will appear one by one, in a discrete way.
+
+To achieve that, we'll hide the `#stripe` outside of `#digit` using `overflow: hidden`, and then shift the `#stripe` to the left step-by-step.
There will be 9 steps, a step-move for each digit:
@@ -277,58 +297,60 @@ There will be 9 steps, a step-move for each digit:
}
```
-In action:
-
-[codetabs src="step"]
-
The first argument of `steps(9, start)` is the number of steps. The transform will be split into 9 parts (10% each). The time interval is automatically divided into 9 parts as well, so `transition: 9s` gives us 9 seconds for the whole animation â 1 second per digit.
The second argument is one of two words: `start` or `end`.
-The `start` means that in the beginning of animation we need to do make the first step immediately.
+The `start` means that in the beginning of animation we need to make the first step immediately.
+
+In action:
+
+[codetabs src="step"]
-We can observe that during the animation: when we click on the digit it changes to `1` (the first step) immediately, and then changes in the beginning of the next second.
+A click on the digit changes it to `1` (the first step) immediately, and then changes in the beginning of the next second.
The process is progressing like this:
- `0s` -- `-10%` (first change in the beginning of the 1st second, immediately)
- `1s` -- `-20%`
- ...
-- `8s` -- `-80%`
+- `8s` -- `-90%`
- (the last second shows the final value).
+Here, the first change was immediate because of `start` in the `steps`.
+
The alternative value `end` would mean that the change should be applied not in the beginning, but at the end of each second.
-So the process would go like this:
+So the process for `steps(9, end)` would go like this:
-- `0s` -- `0`
+- `0s` -- `0` (during the first second nothing changes)
- `1s` -- `-10%` (first change at the end of the 1st second)
- `2s` -- `-20%`
- ...
- `9s` -- `-90%`
-Here's `steps(9, end)` in action (note the pause between the first digit change):
+Here's `steps(9, end)` in action (note the pause before the first digit change):
[codetabs src="step-end"]
-There are also shorthand values:
+There are also some pre-defined shorthands for `steps(...)`:
- `step-start` -- is the same as `steps(1, start)`. That is, the animation starts immediately and takes 1 step. So it starts and finishes immediately, as if there were no animation.
- `step-end` -- the same as `steps(1, end)`: make the animation in a single step at the end of `transition-duration`.
-These values are rarely used, because that's not really animation, but rather a single-step change.
+These values are rarely used, as they represent not a real animation, but rather a single-step change. We mention them here for completeness.
-## Event transitionend
+## Event: "transitionend"
-When the CSS animation finishes the `transitionend` event triggers.
+When the CSS animation finishes, the `transitionend` event triggers.
It is widely used to do an action after the animation is done. Also we can join animations.
-For instance, the ship in the example below starts to swim there and back on click, each time farther and farther to the right:
+For instance, the ship in the example below starts to sail there and back when clicked, each time farther and farther to the right:
[iframe src="boat" height=300 edit link]
-The animation is initiated by the function `go` that re-runs each time when the transition finishes and flips the direction:
+The animation is initiated by the function `go` that re-runs each time the transition finishes, and flips the direction:
```js
boat.onclick = function() {
@@ -337,11 +359,11 @@ boat.onclick = function() {
function go() {
if (times % 2) {
- // swim to the right
+ // sail to the right
boat.classList.remove('back');
boat.style.marginLeft = 100 * times + 200 + 'px';
} else {
- // swim to the left
+ // sail to the left
boat.classList.add('back');
boat.style.marginLeft = 100 * times - 200 + 'px';
}
@@ -357,7 +379,7 @@ boat.onclick = function() {
};
```
-The event object for `transitionend` has few specific properties:
+The event object for `transitionend` has a few specific properties:
`event.propertyName`
: The property that has finished animating. Can be good if we animate multiple properties simultaneously.
@@ -369,7 +391,7 @@ The event object for `transitionend` has few specific properties:
We can join multiple simple animations together using the `@keyframes` CSS rule.
-It specifies the "name" of the animation and rules: what, when and where to animate. Then using the `animation` property we attach the animation to the element and specify additional parameters for it.
+It specifies the "name" of the animation and rules - what, when and where to animate. Then using the `animation` property, we can attach the animation to the element and specify additional parameters for it.
Here's an example with explanations:
@@ -405,11 +427,92 @@ Here's an example with explanations:
There are many articles about `@keyframes` and a [detailed specification](https://drafts.csswg.org/css-animations/).
-Probably you won't need `@keyframes` often, unless everything is in the constant move on your sites.
+You probably won't need `@keyframes` often, unless everything is in constant motion on your sites.
+
+## Performance
+
+Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect.
+
+However, not all animations will look as smooth as you'd like, because different CSS properties cost differently to change.
+
+In more technical details, when there's a style change, the browser goes through 3 steps to render the new look:
+
+1. **Layout**: re-compute the geometry and position of each element, then
+2. **Paint**: re-compute how everything should look like at their places, including background, colors,
+3. **Composite**: render the final results into pixels on screen, apply CSS transforms if they exist.
+
+During a CSS animation, this process repeats every frame. However, CSS properties that never affect geometry or position, such as `color`, may skip the Layout step. If a `color` changes, the browser doesn't calculate any new geometry, it goes to Paint -> Composite. And there are few properties that directly go to Composite. You can find a longer list of CSS properties and which stages they trigger at
.
+
+The calculations may take time, especially on pages with many elements and a complex layout. And the delays are actually visible on most devices, leading to "jittery", less fluid animations.
+
+Animations of properties that skip the Layout step are faster. It's even better if Paint is skipped too.
+
+The `transform` property is a great choice, because:
+- CSS transforms affect the target element box as a whole (rotate, flip, stretch, shift it).
+- CSS transforms never affect neighbour elements.
+
+...So browsers apply `transform` "on top" of existing Layout and Paint calculations, in the Composite stage.
+
+In other words, the browser calculates the Layout (sizes, positions), paints it with colors, backgrounds, etc at the Paint stage, and then applies `transform` to element boxes that need it.
+
+Changes (animations) of the `transform` property never trigger Layout and Paint steps. More than that, the browser leverages the graphics accelerator (a special chip on the CPU or graphics card) for CSS transforms, thus making them very efficient.
+
+Luckily, the `transform` property is very powerful. By using `transform` on an element, you could rotate and flip it, stretch and shrink it, move it around, and [much more](https://developer.mozilla.org/docs/Web/CSS/transform#syntax). So instead of `left/margin-left` properties we can use `transform: translateX(â¦)`, use `transform: scale` for increasing element size, etc.
+
+The `opacity` property also never triggers Layout (also skips Paint in Mozilla Gecko). We can use it for show/hide or fade-in/fade-out effects.
+
+Paring `transform` with `opacity` can usually solve most of our needs, providing fluid, good-looking animations.
+
+For example, here clicking on the `#boat` element adds the class with `transform: translateX(300px)` and `opacity: 0`, thus making it move `300px` to the right and disappear:
+
+```html run height=260 autorun no-beautify
+
+
+
+
+```
+
+Here's a more complex example, with `@keyframes`:
+
+```html run height=80 autorun no-beautify
+click me to start / stop
+
+```
## Summary
-CSS animations allow to smoothly (or not) animate changes of one or multiple CSS properties.
+CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties.
They are good for most animation tasks. We're also able to use JavaScript for animations, the next chapter is devoted to that.
@@ -419,9 +522,11 @@ Limitations of CSS animations compared to JavaScript animations:
+ Simple things done simply.
+ Fast and lightweight for CPU.
- JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element.
-- Not just property changes. We can create new elements in JavaScript for purposes of animation.
+- Not just property changes. We can create new elements in JavaScript as part of the animation.
```
-The majority of animations can be implemented using CSS as described in this chapter. And `transitionend` event allows to run JavaScript after the animation, so it integrates fine with the code.
+In early examples in this chapter, we animate `font-size`, `left`, `width`, `height`, etc. In real life projects, we should use `transform: scale()` and `transform: translate()` for better performance.
+
+The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code.
But in the next chapter we'll do some JavaScript animations to cover more complex cases.
diff --git a/7-animation/3-js-animation/1-animate-ball/solution.md b/7-animation/3-js-animation/1-animate-ball/solution.md
index 5d3f08eef..0dc67b8bd 100644
--- a/7-animation/3-js-animation/1-animate-ball/solution.md
+++ b/7-animation/3-js-animation/1-animate-ball/solution.md
@@ -2,7 +2,7 @@ To bounce we can use CSS property `top` and `position:absolute` for the ball ins
The bottom coordinate of the field is `field.clientHeight`. The CSS `top` property refers to the upper edge of the ball. So it should go from `0` till `field.clientHeight - ball.clientHeight`, that's the final lowest position of the upper edge of the ball.
-To to get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode.
+To get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode.
Here's the final code for the animation:
diff --git a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html
index 7e031e8d1..146033cf7 100644
--- a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html
+++ b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html
@@ -21,7 +21,7 @@
}
function bounce(timeFraction) {
- for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
+ for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
diff --git a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html
index b246f422f..f587ff607 100644
--- a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html
+++ b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html
@@ -21,7 +21,7 @@
}
function bounce(timeFraction) {
- for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
+ for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
diff --git a/7-animation/3-js-animation/article.md b/7-animation/3-js-animation/article.md
index 004954340..b85e91e21 100644
--- a/7-animation/3-js-animation/article.md
+++ b/7-animation/3-js-animation/article.md
@@ -77,9 +77,9 @@ setInterval(animate3, 20);
These several independent redraws should be grouped together, to make the redraw easier for the browser and hence load less CPU load and look smoother.
-There's one more thing to keep in mind. Sometimes when CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`.
+There's one more thing to keep in mind. Sometimes CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`.
-But how do we know about that in JavaScript? There's a specification [Animation timing](http://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more.
+But how do we know about that in JavaScript? There's a specification [Animation timing](https://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more.
The syntax:
```js
@@ -96,7 +96,7 @@ The returned value `requestId` can be used to cancel the call:
cancelAnimationFrame(requestId);
```
-The `callback` gets one argument -- the time passed from the beginning of the page load in microseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now).
+The `callback` gets one argument -- the time passed from the beginning of the page load in milliseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now).
Usually `callback` runs very soon, unless the CPU is overloaded or the laptop battery is almost discharged, or there's another reason.
@@ -159,7 +159,7 @@ Function `animate` accepts 3 parameters that essentially describes the animation
}
```
- It's graph:
+ Its graph:

That's just like `transition-timing-function: linear`. There are more interesting variants shown below.
@@ -227,7 +227,7 @@ See in action (click to activate):
[iframe height=40 src="quad" link]
-...Or the cubic curve or event greater `n`. Increasing the power makes it speed up faster.
+...Or the cubic curve or even greater `n`. Increasing the power makes it speed up faster.
Here's the graph for `progress` in the power `5`:
@@ -283,7 +283,7 @@ The `bounce` function does the same, but in the reverse order: "bouncing" starts
```js
function bounce(timeFraction) {
- for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
+ for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
@@ -397,7 +397,7 @@ The effect is clearly seen if we compare the graphs of `easeIn`, `easeOut` and `

-- Red is the regular variantof `circ` (`easeIn`).
+- Red is the regular variant of `circ` (`easeIn`).
- Green -- `easeOut`.
- Blue -- `easeInOut`.
@@ -405,7 +405,7 @@ As we can see, the graph of the first half of the animation is the scaled down `
## More interesting "draw"
-Instead of moving the element we can do something else. All we need is to write the write the proper `draw`.
+Instead of moving the element we can do something else. All we need is to write the proper `draw`.
Here's the animated "bouncing" text typing:
@@ -452,4 +452,4 @@ Surely we could improve it, add more bells and whistles, but JavaScript animatio
JavaScript animations can use any timing function. We covered a lot of examples and transformations to make them even more versatile. Unlike CSS, we are not limited to Bezier curves here.
-The same is about `draw`: we can animate anything, not just CSS properties.
+The same is true about `draw`: we can animate anything, not just CSS properties.
diff --git a/7-animation/3-js-animation/bounce-easeinout.view/index.html b/7-animation/3-js-animation/bounce-easeinout.view/index.html
index 837c50db1..aed3d9d08 100644
--- a/7-animation/3-js-animation/bounce-easeinout.view/index.html
+++ b/7-animation/3-js-animation/bounce-easeinout.view/index.html
@@ -26,7 +26,7 @@
function bounce(timeFraction) {
- for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
+ for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
diff --git a/7-animation/3-js-animation/bounce-easeout.view/index.html b/7-animation/3-js-animation/bounce-easeout.view/index.html
index e52eae8de..69dbb7ce0 100644
--- a/7-animation/3-js-animation/bounce-easeout.view/index.html
+++ b/7-animation/3-js-animation/bounce-easeout.view/index.html
@@ -22,7 +22,7 @@
}
function bounce(timeFraction) {
- for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
+ for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
diff --git a/7-animation/3-js-animation/bounce.view/index.html b/7-animation/3-js-animation/bounce.view/index.html
index 1be2580d9..3575ed820 100644
--- a/7-animation/3-js-animation/bounce.view/index.html
+++ b/7-animation/3-js-animation/bounce.view/index.html
@@ -19,7 +19,7 @@
animate({
duration: 3000,
timing: function bounce(timeFraction) {
- for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
+ for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
diff --git a/7-animation/3-js-animation/text.view/index.html b/7-animation/3-js-animation/text.view/index.html
index e404fe5c4..4947e4cd4 100644
--- a/7-animation/3-js-animation/text.view/index.html
+++ b/7-animation/3-js-animation/text.view/index.html
@@ -29,14 +29,14 @@
timing: bounce,
draw: function(progress) {
let result = (to - from) * progress + from;
- textArea.value = text.substr(0, Math.ceil(result))
+ textArea.value = text.slice(0, Math.ceil(result))
}
});
}
function bounce(timeFraction) {
- for (let a = 0, b = 1, result; 1; a += b, b /= 2) {
+ for (let a = 0, b = 1; 1; a += b, b /= 2) {
if (timeFraction >= (7 - 4 * a) / 11) {
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2)
}
diff --git a/8-web-components/1-webcomponents-intro/article.md b/8-web-components/1-webcomponents-intro/article.md
index 3279cb133..c3522dea9 100644
--- a/8-web-components/1-webcomponents-intro/article.md
+++ b/8-web-components/1-webcomponents-intro/article.md
@@ -26,9 +26,9 @@ The International Space Station:
...And this thing flies, keeps humans alive in space!
-How such complex devices are created?
+How are such complex devices created?
-Which principles we could borrow to make our development same-level reliable and scalable? Or, at least, close to it.
+Which principles could we borrow to make our development same-level reliable and scalable? Or, at least, close to it?
## Component architecture
diff --git a/8-web-components/2-custom-elements/article.md b/8-web-components/2-custom-elements/article.md
index 702ff9073..a84ed1192 100644
--- a/8-web-components/2-custom-elements/article.md
+++ b/8-web-components/2-custom-elements/article.md
@@ -115,7 +115,7 @@ customElements.define("time-formatted", TimeFormatted); // (2)
>
```
-1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time.
+1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](mdn:/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time.
2. We need to register our new element by `customElements.define(tag, class)`.
3. And then we can use it everywhere.
@@ -149,7 +149,7 @@ The `connectedCallback` triggers when the element is added to the document. Not
In the current implementation of ``, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like `a.href`, we expect the change to be immediately visible. So let's fix this.
-We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for an attribute for performance reasons.
+We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for other, unlisted attributes (that's for performance reasons).
Here's a new ``, that auto-updates when attributes change:
@@ -320,7 +320,7 @@ For example, buttons are instances of `HTMLButtonElement`, let's build upon it.
class HelloButton extends HTMLButtonElement { /* custom element methods */ }
```
-2. Provide an third argument to `customElements.define`, that specifies the tag:
+2. Provide the third argument to `customElements.define`, that specifies the tag:
```js
customElements.define('hello-button', HelloButton, *!*{extends: 'button'}*/!*);
```
@@ -365,7 +365,7 @@ Our new button extends the built-in one. So it keeps the same styles and standar
## References
- HTML Living Standard: .
-- Compatiblity: .
+- Compatiblity: .
## Summary
@@ -397,4 +397,4 @@ Custom elements can be of two types:
/* */
```
-Custom elements are well-supported among browsers. Edge is a bit behind, but there's a polyfill .
+Custom elements are well-supported among browsers. There's a polyfill .
diff --git a/8-web-components/3-shadow-dom/article.md b/8-web-components/3-shadow-dom/article.md
index fafc4754c..92614f777 100644
--- a/8-web-components/3-shadow-dom/article.md
+++ b/8-web-components/3-shadow-dom/article.md
@@ -37,7 +37,7 @@ input::-webkit-slider-runnable-track {
Once again, `pseudo` is a non-standard attribute. Chronologically, browsers first started to experiment with internal DOM structures to implement controls, and then, after time, shadow DOM was standardized to allow us, developers, to do the similar thing.
-Further on, we'll use the modern shadow DOM standard, covered by [DOM spec](https://dom.spec.whatwg.org/#shadow-trees) other related specifications.
+Further on, we'll use the modern shadow DOM standard, covered by [DOM spec](https://dom.spec.whatwg.org/#shadow-trees) and other related specifications.
## Shadow tree
diff --git a/8-web-components/4-template-element/article.md b/8-web-components/4-template-element/article.md
index d0fe12af0..5499c4edc 100644
--- a/8-web-components/4-template-element/article.md
+++ b/8-web-components/4-template-element/article.md
@@ -1,7 +1,7 @@
# Template element
-A built-in `` element serves as a storage for HTML markup templates. The browser ignores it contents, only checks for syntax validity, but we can access and use it in JavaScript, to create other elements.
+A built-in `` element serves as a storage for HTML markup templates. The browser ignores its contents, only checks for syntax validity, but we can access and use it in JavaScript, to create other elements.
In theory, we could create any invisible element somewhere in HTML for HTML markup storage purposes. What's special about ``?
diff --git a/8-web-components/5-slots-composition/article.md b/8-web-components/5-slots-composition/article.md
index 66b89a71e..c41e26e05 100644
--- a/8-web-components/5-slots-composition/article.md
+++ b/8-web-components/5-slots-composition/article.md
@@ -130,6 +130,7 @@ For example, the second `` here is ignored (as it's not a top-level child
If there are multiple elements in light DOM with the same slot name, they are appended into the slot, one after another.
For example, this:
+
```html
John
@@ -227,11 +228,11 @@ The flattened DOM looks like this:
- About me
+ Other information
*!*
- Hello
- I am John!
+ I like to swim.
+ ...And play volleyball too!
*/!*
@@ -268,7 +269,7 @@ The shadow DOM template with proper slots:
```
1. `` goes into ``.
-2. There are many `` in the template, but only one `` in the template. So all such `` are appended to `` one after another, thus forming the list.
+2. There are many `` in the ``, but only one `` in the template. So all such `` are appended to `` one after another, thus forming the list.
The flattened DOM becomes:
@@ -380,7 +381,7 @@ If we'd like to track internal modifications of light DOM from JavaScript, that'
Finally, let's mention the slot-related JavaScript methods.
-As we've seen before, JavaScript looks at the "real" DOM, without flattening. But, if the shadow tree has `{mode: 'open'}`, then we can figure out which elements assigned to a slot and, vise-versa, the slot by the element inside it:
+As we've seen before, JavaScript looks at the "real" DOM, without flattening. But, if the shadow tree has `{mode: 'open'}`, then we can figure out which elements assigned to a slot and, vice-versa, the slot by the element inside it:
- `node.assignedSlot` -- returns the `` element that the `node` is assigned to.
- `slot.assignedNodes({flatten: true/false})` -- DOM nodes, assigned to the slot. The `flatten` option is `false` by default. If explicitly set to `true`, then it looks more deeply into the flattened DOM, returning nested slots in case of nested components and the fallback content if no node assigned.
@@ -408,7 +409,7 @@ customElements.define('custom-menu', class extends HTMLElement {
`;
- // slottable is added/removed/replaced
+ // triggers when slot content changes
*!*
this.shadowRoot.firstElementChild.addEventListener('slotchange', e => {
let slot = e.target;
@@ -446,7 +447,7 @@ Composition does not really move nodes, from JavaScript point of view the DOM is
JavaScript can access slots using methods:
- `slot.assignedNodes/Elements()` -- returns nodes/elements inside the `slot`.
-- `node.assignedSlot` -- the reverse meethod, returns slot by a node.
+- `node.assignedSlot` -- the reverse property, returns slot by a node.
If we'd like to know what we're showing, we can track slot contents using:
- `slotchange` event -- triggers the first time a slot is filled, and on any add/remove/replace operation of the slotted element, but not its children. The slot is `event.target`.
diff --git a/8-web-components/6-shadow-dom-style/article.md b/8-web-components/6-shadow-dom-style/article.md
index 2be81fbb2..98e246a7f 100644
--- a/8-web-components/6-shadow-dom-style/article.md
+++ b/8-web-components/6-shadow-dom-style/article.md
@@ -111,22 +111,7 @@ customElements.define('custom-dialog', class extends HTMLElement {
Now the additional centering styles are only applied to the first dialog: `
`.
-## :host-context(selector)
-
-Same as `:host`, but applied only if the shadow host or any of its ancestors in the outer document matches the `selector`.
-
-E.g. `:host-context(.dark-theme)` matches only if there's `dark-theme` class on `` on anywhere above it:
-
-```html
-
-
- ...
-
-```
-
-To summarize, we can use `:host`-family of selectors to style the main element of the component, depending on the context. These styles (unless `!important`) can be overridden by the document.
+To summarize, we can use `:host`-family of selectors to style the main element of the component. These styles (unless `!important`) can be overridden by the document.
## Styling slotted content
@@ -259,7 +244,6 @@ For example, in shadow DOM we can use `--user-card-field-color` CSS variable to
Name:
Birthday:
-
```
Then, we can declare this property in the outer document for ``:
@@ -318,7 +302,7 @@ Shadow DOM can include styles, such as `