diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..490051876 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: iliakan diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index 1de346dad..b74454b88 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -34,16 +34,26 @@ Termenii de mai sus sunt bine de știut pentru că ei sunt folosiți în articol Motoarele sunt complicate. Dar bazele sunt ușoare. +<<<<<<< HEAD 1. Motorul (încorporat, dacă este un browser) citește("parsează") script-ul. 2. Apoi convertește("compilează") script-ul în limbajul mașină. 3. Apoi codul mașină rulează, destul de repede. +======= +1. The engine (embedded if it's a browser) reads ("parses") the script. +2. Then it converts ("compiles") the script to machine code. +3. And then the machine code runs, pretty fast. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Motorul aplică optimizări la fiecare stadiu al procesului. Ba chiar observă script-ul compilat cum rulează, analizează datele care trec prin el și aplică optimizări suplimentare asupra codului mașină bazate pe informațiile strânse. ``` ## Ce poate JavaScript-ul din browser să facă? +<<<<<<< HEAD JavaScript-ul modern este un limbaj de programare "sigur". Nu furnizează acces low-level la memorie sau la CPU, pentru că inițial a fost creat pentru browsere, care nu necesitau acest lucru. +======= +Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or the CPU, because it was initially created for browsers which do not require it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Capabilitățile depind mult de mediul în se care rulează JavaScript. De exemplu, [Node.JS](https://wikipedia.org/wiki/Node.js) suportă funcții care permit JavaScript-ului să citească/scrie fișiere arbitrare, să realizeze request-uri de rețea, etc. @@ -59,7 +69,11 @@ De exemplu, JavaScript din browser este capabil să: ## Ce NU poate JavaScript-ul din browser să facă? +<<<<<<< HEAD Abilitățile JavaScript-ului din browser sunt limitate de dragul siguranței utilizatorului. Scopul este de a preveni o pagină web malițioasă să acceseze informații private sau să corupă datele utilizatorului. +======= +JavaScript's abilities in the browser are limited to protect the user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Exemplele acestor restricții sunt: @@ -67,6 +81,7 @@ Exemplele acestor restricții sunt: Browserele moderne îi permit să lucreze cu fișiere, dar accesul este limitat și furnizat doar dacă utilizatorul realizează anumite acțiuni, cum ar fi "scăparea" unui fișier într-o fereastră de browser sau selectarea lui printr-un tag ``. +<<<<<<< HEAD Există mijloace prin care se poate interacționa cu camera/microfonul sau alte dispozitive, dar ele necesită permisiunea explicită a utilizatorului. Așadar o pagină pe care este activat JavaScript-ul nu ar putea activa o cameră web în mod viclean, și să privească împrejurimile și să trimită informații către [NSA](https://en.wikipedia.org/wiki/National_Security_Agency). - În general, diferite tab-uri/ferestre nu știu nimic unele despre celelalte. Câteodată acestea știu, de exemplu când o fereastră folosește JavaScript pentru a deschide cealaltă fereastră. Dar chiar și în acest caz, JavaScript nu poate accesa cealaltă fereastră dacă ambele ferestre vin de pe site-uri diferite (de la un domeniu, protocol sau port diferit). @@ -78,6 +93,19 @@ Exemplele acestor restricții sunt: ![](limitations.svg) Astfel de limite nu există dacă JavaScript este folosit în afara browser-ului, de exemplu pe un server. Browserele moderne permit de asemenea instalarea plugin-urilor/extensiilor care pot cere extinderea permisiunilor. +======= + There are ways to interact with the camera/microphone and other devices, but they require a user's explicit permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the [NSA](https://en.wikipedia.org/wiki/National_Security_Agency). +- Different tabs/windows generally do not know about each other. Sometimes they do, for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other page if they come from different sites (from a different domain, protocol or port). + + This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and must contain special JavaScript code that handles it. We'll cover that in the tutorial. + + This limitation is, again, for the user's safety. A page from `http://anysite.com` which a user has opened must not be able to access another browser tab with the URL `http://gmail.com`, for example, and steal information from there. +- JavaScript can easily communicate over the net to the server where the current page came from. But its ability to receive data from other sites/domains is crippled. Though possible, it requires explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's a safety limitation. + +![](limitations.svg) + +Such limitations do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow plugins/extensions which may ask for extended permissions. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Ce face JavaScript, unic? @@ -92,7 +120,11 @@ JavaScript este singura tehnologie browser care combină aceste trei lucruri. Asta e ceea ce face JavaScript unic. De aceea este cea mai răspândită unealtă pentru crearea de interfețe pentru browser. +<<<<<<< HEAD Acestea fiind spuse, JavaScript permite de asemenea crearea serverlor, aplicațiilor mobile etc. +======= +That said, JavaScript can be used to create servers, mobile applications, etc. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Limbaje "peste" JavaScript @@ -100,12 +132,17 @@ Sintaxa JavaScript-ului nu se potrivește cerințelor fiecăruia. Persoane difer Acest lucru este de așteptat, pentru că proiectele și cerințele sunt diferite pentru fiecare. +<<<<<<< HEAD Așa că, recent au apărut o pletoră de limbaje noi, care sunt *transpiled*(convertite) în JavaScript înainte ca ele să ruleze în browser. +======= +So, recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run in the browser. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Uneltele moderne fac transpilarea foarte rapidă și transparentă, permițând defapt dezvoltatorilor să codeze în alt limbaj și să auto convertească codul în cod "sub capotă"(under the hood). Exemple de astfel de limbaje: +<<<<<<< HEAD - [CoffeeScript](http://coffeescript.org/) este un "zahăr sintatic" pentru JavaScript. El introduce sintaxă mai scurtă, permițându-ne să scrim cod mai clar și mai precis. De obicei dezvoltatorii Ruby îl plac. - [TypeScript](http://www.typescriptlang.org/) este concentrat pe adăugarea de "tipizare strictă de date", pentru a simplifica dezvoltarea și suportul sistemelor complexe. Este dezvoltat de Microsoft. - [Flow](http://flow.org/) de asemeni adaugă data typing, dar într-un mod diferit. Dezvoltat de Facebook. @@ -114,6 +151,16 @@ Exemple de astfel de limbaje: - [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) este un limbaj de programare modern, concis și sigur care țintește browser-ul sau Node. Există mai multe. Desigur, chiar dacă folosim unul dintre aceste limbaje, ar trebui de asemenea să știm JavaScript, pentru a înțelege cu adevărat ce facem. +======= +- [CoffeeScript](https://coffeescript.org/) is "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it. +- [TypeScript](https://www.typescriptlang.org/) is concentrated on adding "strict data typing" to simplify the development and support of complex systems. It is developed by Microsoft. +- [Flow](https://flow.org/) also adds data typing, but in a different way. Developed by Facebook. +- [Dart](https://www.dartlang.org/) is a standalone language that has its own engine that runs in non-browser environments (like mobile apps), but also can be transpiled to JavaScript. Developed by Google. +- [Brython](https://brython.info/) is a Python transpiler to JavaScript that enables the writing of applications in pure Python without JavaScript. +- [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) is a modern, concise and safe programming language that can target the browser or Node. + +There are more. Of course, even if we use one of these transpiled languages, we should also know JavaScript to really understand what we're doing. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Rezumat 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 002d5ae0a..0f219916a 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 @@ # Manuale și specificații +<<<<<<< HEAD Această carte este un *tutorial*. Are ca obiectiv să te ajute să înveți treptat limbajul. Dar odată ce ești familiarizat cu principiile de bază, vei avea nevoie de alte surse. +======= +This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other resources. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Specificații @@ -9,7 +13,11 @@ Această carte este un *tutorial*. Are ca obiectiv să te ajute să înveți tre Dar fiind atât de formalizată, este grea de înțeles la început. Deci dacă ai nevoie de cea mai de încredere sursă de informație despre detaliile limbajului, specificațiile sunt locul potrivit. Dar nu sunt pentru uzul obișnuit. +<<<<<<< HEAD O nouă versiune a specificației este lansată în fiecare an. Printre aceste lansări, cea mai recentă schiță a specificației este la . +======= +A new specification version is released every year. Between these releases, the latest specification draft is at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Pentru a citi despre noile caracteristici avansate, incluzând acelea care sunt "aproape standard" (așa-numita "etapă a treia"), vezi propunerile la . @@ -19,11 +27,17 @@ De asemenea, dacă programezi pentru browser, atunci sunt și alte specificații - **MDN (Mozilla) JavaScript Reference** este manualul principal cu exemple și alte informații. Este minunat pentru a obține informații mai profunde despre funcții individuale ale limbajului, metode etc. +<<<<<<< HEAD Poate fi găsit la . Cu toate acestea, de cele mai multe ori este mai bine să facem o căutare pe internet. Folosiți doar "MDN [termen]" în query, e.g. pentru a căuta funcția `parseInt`. ## Tabele de compatibilitate +======= + You can find it at . + +Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for the `parseInt` function. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 JavaScipt este un limbaj în dezvoltare, noi caracteristici fiind adăugate regulat. @@ -32,6 +46,15 @@ Pentru a vedea suportul acestora printre browsere și alte motoare vezi: - - tabele pentru suport per-caracteristică, e.g. pentru a vedea ce motoare suportă funcțiile moderne de criptografie: . - - un tabel cu caracteristicile limbajului și motoarele ce le suportă sau nu. +<<<<<<< HEAD Toate aceste resurse sunt utile pentru dezvoltarea din viața reală, conținând informații valoroase despre detaliile limbajului, suportul acestora etc. Te rugăm să îți amintești de ele (sau de acestă pagină) pentru cazurile în care ai nevoie de informații mai profunde despre o caracteristică particulară. +======= +- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: . +- - a table with language features and engines that support those or don't support. + +All these resources are useful in real-life development, as they contain valuable information about language details, their support, etc. + +Please remember them (or this page) for the cases when you need in-depth information about a particular feature. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index 99aa1f4bb..e519fbc73 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -13,7 +13,11 @@ Un IDE încarcă proiectul (pot fi mai multe fișiere), permite navigarea între Dacă nu ai selectat până acum un IDE, uită-te la următoarele variante: - [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). +<<<<<<< HEAD - [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, plătit). +======= +- [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, paid). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Pentru Windows există de asemenea un editor "Visual Studio", a nu se confunda cu "Visual Studio Code". "Visual Studio" este un editor doar pentru Windows, plătit dar puternic, foarte potrivit pentru platforma .NET. O versiune gratuită al lui este [Visual Studio Community](https://www.visualstudio.com/vs/community/). @@ -29,11 +33,19 @@ Diferența majoră dintre un editor de "categorie ușoară" și un "IDE" este c În practică, editoarele de categorie ușoară pot avea multe plugin-uri, inclusiv analizatoare de sintaxă la nivel de director sau autocompletatoare, așadar nu este o graniță strictă între un editor de categorie ușoară și un IDE. +<<<<<<< HEAD Sunt multe opțiuni, spre exemplu: - [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). - [Notepad++](https://notepad-plus-plus.org/) (Windows, gratuit). - [Vim](http://www.vim.org/) și [Emacs](https://www.gnu.org/software/emacs/) sunt de asemenea utile dacă știi cum să le folosești. +======= +There are many options, for instance: + +- [Sublime Text](https://www.sublimetext.com/) (cross-platform, shareware). +- [Notepad++](https://notepad-plus-plus.org/) (Windows, free). +- [Vim](https://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Să nu ne certăm @@ -41,7 +53,11 @@ Editoarele din lista de mai sus sunt cele pe care fie eu sau prietenii mei, pe c Există și alte editoare grozave în lumea asta mare. Te rog alege un editor care îți place cel mai mult. +<<<<<<< HEAD Alegerea unui editor, ca a oricărui alt tool, este individuală și depinde de proiectele tale, obiceiurile și preferințele personale. +======= +The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 The author's personal opinion: 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 66281b555..4832240e4 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,15 +12,26 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` +<<<<<<< HEAD Aici avem o constantă `birthday` pentru dată, și de asemenea constanta `age`. +======= +Here we have a constant `birthday` for the date, and also the `age` constant. + +The `age` is calculated from `birthday` using `someCode()`, which means a function call that we didn't explain yet (we will soon!), but the details don't matter here, the point is that `age` is calculated somehow based on the `birthday`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `age` este calculată din `birthday` folosind `someCode()`, ceea ce înseamnă un apel de funcție pe care nu l-am explicat încă (o vom face în curând!), dar detaliile nu contează aici, ideea este că `age` este calculată cumva pe baza `birthday`. Ar fi corect să folosim majuscule pentru `birthday`? Pentru `age`? Sau chiar pentru ambele? ```js +<<<<<<< HEAD const BIRTHDAY = '18.04.1982'; // facem BIRTHDAY cu majuscule? const AGE = someCode(BIRTHDAY); // facem AGE cu majuscule? -``` +======= +const BIRTHDAY = '18.04.1982'; // make birthday uppercase? +const AGE = someCode(BIRTHDAY); // make age uppercase? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +``` diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 765886de6..a2ac973f0 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -63,7 +63,11 @@ let age = 25; let message = 'Bună'; ``` +<<<<<<< HEAD Unele persoane definesc de asemenea multiple variabile în acest stil multiliniar: +======= +Some people also define multiple variables in this multiline style: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js no-beautify let user = 'John', @@ -88,16 +92,26 @@ Technic, toate aceste variante fac același lucru. Deci, este o problemă de pre *!*var*/!* message = 'Bună'; ``` +<<<<<<< HEAD Termenul `var` este *aproape* la fel ca și `let`. El declară de asemenea o variabilă, dar într-un mod ușor diferit, de "școală-veche". Există diferențe subtile între `let` și `var`, dar nu contează pentru noi încă. O să le acoperim în detaliu în capitolul . +======= +The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way. + +There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## O analogie din viața reală Putem înțelege ușor conceptul de "variabilă" dacă ne-o imaginăm drept o "cutie" pentru date, cu un abțibild cu nume unic pe ea. +<<<<<<< HEAD De exemplu, variabila `message` poate fi imaginată drept o cutie cu eticheta `"mesaj"` cu valoarea `"Bună!"` în ea: +======= +For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](variable.svg) @@ -105,6 +119,11 @@ Putem pune orice valoare în cutie. De asemenea o putem schimba de câte ori vrem: +<<<<<<< HEAD +======= +We can also change it as many times as we want: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let message; @@ -150,12 +169,21 @@ let message = "Acesta"; // SyntaxError: 'message' has already been declared Deci, ar trebui să declarăm o variabilă o dată și apoi să ne referim la aceasta fără `let`. ```` +<<<<<<< HEAD ```smart header="Limbaje funcționale" Este interesant de notat că există așa-numitele limbaje de programare [pur funcționale](https://en.wikipedia.org/wiki/Purely_functional_programming), cum ar fi [Haskell](https://en.wikipedia.org/wiki/Haskell), care interzic schimbarea valorii variabilelor. +======= +```smart header="Functional languages" +It's interesting to note that there exist so-called [pure functional](https://en.wikipedia.org/wiki/Purely_functional_programming) programming languages, such as [Haskell](https://en.wikipedia.org/wiki/Haskell), that forbid changing variable values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 În limbaje ca acestea, odată ce valoarea este stocată "în cutie", există acolo pentru totdeauna. Dacă avem nevoie să stocăm altceva, limbajul ne forțează să creăm o nouă cutie (declarăm o nouă variabilă). Nu o putem refolosi pe cea veche. +<<<<<<< HEAD Deși pare un pic ciudat la prima vedere, aceste limbaje sunt destul de capabile de dezvoltare serioasă. Mai mult decât atât, sunt zone ca de exemplu calcule paralele unde această limitare conferă beneficii sigure. +======= +Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Denumirea variabilelor [#variable-naming] @@ -193,19 +221,32 @@ let 1a; // nu poate începe cu o cifră let my-name; // liniuțele '-' nu sunt permise în nume ``` +<<<<<<< HEAD ```smart header="Uscula contează" Variabilele numite `apple` și `APPLE` sunt două variabile diferite. ``` ````smart header="Literele non-latine sunt permise, dar nu sunt recomandate" Este posibil să folosim orice limbă, inclusiv litere chirilice, caractere chinezești și așa mai departe, precum acestea: +======= +```smart header="Case matters" +Variables named `apple` and `APPLE` are two different variables. +``` + +````smart header="Non-Latin letters are allowed, but not recommended" +It is possible to use any language, including Cyrillic letters, Chinese logograms and so on, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let имя = '...'; let 我 = '...'; ``` +<<<<<<< HEAD Technic, aici nu este nicio eroare. Astfel de nume sunt permise, dar există o convenție internațională de a folosi limba engleză în denumirile variabilelor. Chiar dacă scriem un script mic, poate avea viață lungă. Oamenii din alte țări ar putea avea nevoie să le citescă uneori. +======= +Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ````warn header="Nume rezervate" @@ -260,13 +301,21 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // eroare, nu putem realoca constanta! ``` +<<<<<<< HEAD Când un programator este sigur că acea variabilă nu se va schimba niciodată, o poate declara folosind `const` pentru a garanta și a comunica clar acest fapt tuturor. ### Constante cu majuscule +======= +When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Este o practica răspândită de a folosi constante drept așa-zise valori greu de reținut care sunt cunoscute înainte de execuție. +<<<<<<< HEAD Aceste constante sunt denumite folosind majuscule și bară jos. +======= +There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu, să facem o constantă pentru culori în așa-numitul "web" (hexadecimal) format: @@ -289,17 +338,30 @@ Beneficii: Când ar trebui să folosim majuscule pentru o constantă și când ar trebui să o denumim în mod obișnuit? Haideți să clarificăm asta. +<<<<<<< HEAD A fi o "constantă" înseamnă doar a fi o valoare a unei variabile care nu se schimbă. Dar există și constante care sunt cunoscute înainte de execuție (ca de exemplu valoarea hexadecimală pentru roșu) și există constante care sunt *calculate* în timpul execuției, dar nu se schimbă după ce sunt alocate inițial. De exemplu: +======= +Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment. + +For instance: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js const pageLoadTime = /* durata timpului de încărcare a unei pagini web */; ``` +<<<<<<< HEAD Valoarea lui `pageLoadTime` nu este cunoscută înainte ca pagina să se încarce, astfel că este denumită obișnuit. Dar este totuși o constantă pentru că nu se schimbă după ce este alocată. În alte cuvinte, constantele cu majuscule sunt folosite doar ca așa-zise valori "hard-coded". +======= +The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment. + +In other words, capital-named constants are only used as aliases for "hard-coded" values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Denumirea corectă a lucrurilor @@ -307,18 +369,31 @@ Vorbind despre variabile, mai este un lucru extrem de important. Numele unei variabile trebuie să aibă o semnificație clară, evidentă, care să descrie datele pe care le stochează. +<<<<<<< HEAD Numirea variabilelor este una dintre cele mai importante și mai complexe abilități în programare. O privire rapidă asupra numelor variabilelor poate dezvălui ce cod a fost scris de un începător față de un programator experimentat. Într-un proiect real, cea mai mare parte a timpului se petrece modificând și extinzând o bază de cod existentă, mai degrabă decât scriind ceva complet separat de la zero. Atunci când ne întoarcem la un cod după ce am făcut altceva pentru o vreme, este mult mai ușor să găsim informații care sunt bine etichetate. Sau, cu alte cuvinte, când variabilele au nume bune. +======= +Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer. + +In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Te rog petrece timp gândindu-te la denumirea corectă pentru o variabilă înainte să o declari. Făcând acest lucru te răsplătești frumos. Câteva reguli bune de urmat sunt: +<<<<<<< HEAD - Folosește nume uman-reproductibile ca `userName` sau `shoppingCart`. - Stai departe de abrevieri sau nume scurte ca `a`, `b`, `c`, decât dacă știi cu adevărat ceea ce faci. - Fă Fă ca numele să fie cât mai descriptiv și concis. Exemple de nume proaste sunt `data` și `value`. Astfel de nume nu spun nimic. Este în regulă să le folosiți doar dacă contextul codului face extrem de evident la ce date sau valoare face referire variabila. - Pune-te de acord cu echipa și ține minte. Dacă vizitatorul unui site este denumit ca "user" atunci ar trebui ca numele variabilelor legate să fie `currentUser` sau `newUser` în loc de `currentVisitor` sau `newManInTown`. +======= +- Use human-readable names like `userName` or `shoppingCart`. +- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing. +- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing. +- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Sună simplu? Chiar este, dar crearea de variabile descriptive și concise în practică nu este. Încearcă. diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 26f3bcd53..04e8b2450 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -94,13 +94,6 @@ const bigInt = 1234567890123456789012345678901234567890n; As `BigInt` numbers are rarely needed, we don't cover them here, but devoted them a separate chapter . Read it when you need such big numbers. - -```smart header="Compatibility issues" -Right now, `BigInt` is supported in Firefox/Chrome/Edge/Safari, but not in IE. -``` - -You can check [*MDN* BigInt compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) to know which versions of a browser are supported. - ## String A string in JavaScript must be surrounded by quotes. 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 943627c64..5e85e301c 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 @@ De exemplu, `alert` convertește automat orice valoare către un șir pentru a o De asemenea există și cazuri unde este nevoie să convertim explicit o valoare către tipul așteptat. +<<<<<<< HEAD ```smart header="Nu vorbim despre obiecte încă" În acest captiol, nu vom acoperi obiectele. Pentru moment vom vorbi doar despre primitive. +======= +```smart header="Not talking about objects yet" +In this chapter, we won't cover objects. For now, we'll just be talking about primitives. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Mai târziu, dupa ce vom învăța despre obiecte, în acest capitol vom vedea cum se incadrează obiectele. ``` @@ -34,7 +39,11 @@ Conversia unui șir este în mare parte evidentă. Un `false` devine `"false"`, ## Conversia Numerică +<<<<<<< HEAD Conversia numerică se petrece în funcții și expresii matematice automat. +======= +Numeric conversion in mathematical functions and expressions happens automatically. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu, când împărțirea `/` este aplicată non-numerelor: @@ -70,7 +79,11 @@ Regulile conversiei numerice: |`undefined`|`NaN`| |`null`|`0`| |true and false | `1` and `0` | +<<<<<<< HEAD | `string` | Spațiile goale de la ïnceput și sfârșit sunt eliminate. Dacă șirul rămas este gol, rezultatul este `0`. Altfel numărul este "citit" din șir. O eroare transmite `NaN`. | +======= +| `string` | Whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Exemple: @@ -131,7 +144,11 @@ Conversia urmează regulile: |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | +<<<<<<< HEAD | `string` | Șirul este citit "așa cum este", spațiile goale din ambele părți sunt ignorate. Un șir gol devine `0`. O eroare transmite `NaN`. | +======= +| `string` | The string is read "as is", whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **`Conversia Boolean`** -- Apare în operațiile logice. Poate fi executată cu `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 cb4006759..2d54c2dab 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -16,6 +16,7 @@ undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) ``` +<<<<<<< HEAD 1. Adunarea cu un șir `"" + 1` convertește `1` într-un șir: `"" + 1 = "1"`, iar apoi avem `"1" + 0`, aceeași regulă este aplicată 2. Scăderea `-` (ca și majoritatea operațiilor matematica) funcționează doar cu numere, convertește un sir gol `""` în `0`. 3. Adunarea cu un șir alipește numărul `5` la șir @@ -23,3 +24,12 @@ undefined + 1 = NaN // (6) 5. `null` devine `0` după conversia numerică. 6. `undefined` devine `NaN` după conversia numerică. 7. Caracterele spații sunt tăiate din începutul si sfârșitului un șir când un șir este convertit într-un număr. Aici tot șirul constă în caractere spații ca și `\t`, `\n` și un spațiu "normal" între ele. Deci, similar cu un șir gol, devine `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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index b02821a4e..f59794627 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -68,7 +68,11 @@ if (cond) { ## Clauza "else" +<<<<<<< HEAD Instrucțiunea `if` poate conține un bloc opțional "else". Acesta se execută atunci când condiția este falsy. +======= +The `if` statement may contain an optional `else` block. It executes when the condition is falsy. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu: ```js run @@ -180,10 +184,17 @@ alert( message ); Ar putea fi dificil la început să înțelegem ce se întâmplă. Dar după o privire mai atentă, putem vedea că este doar o secvență obișnuită de teste: +<<<<<<< HEAD 1. Primul semn de întrebare verifică dacă `age < 3`. 2. Dacă este adevărat -- se returnează `'Bună, puiule!'`. În caz contrar, continuă spre expresia de după două puncte '":"', verificând `age < 18`. 3. Dacă este adevărat -- returnează `'Bună!'`. În caz contrar, continuă cu expresia de după următoarele două puncte '":"', verificând `age < 100`. 4. Dacă acest lucru este adevărat -- returnează `'Salutări!'`. În caz contrar, continuă cu expresia de după ultimele două puncte '":"', returnând `'Ce vârstă neobișnuită!'`. +======= +1. The first question mark checks whether `age < 3`. +2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon ":", checking `age < 18`. +3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon ":", checking `age < 100`. +4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon ":", returning `'What an unusual age!'`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Iată cum arată acest lucru folosind `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 d8e19deee..5f3b650ec 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 @@ Răspunsul: `null`, deoarece este prima valoare falsy din listă. ```js run -alert( 1 && null && 2 ); +alert(1 && null && 2); ``` 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 b2bc64f83..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 @@ -76,7 +76,7 @@ alert(firstName || lastName || nickName || "Anonymous"); // Supercoder */!* ``` -Historically, the OR `||` operator was there first. It exists since the beginning of JavaScript, so developers were using it for such purposes for a long time. +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 `||`. 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 a7a211569..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: @@ -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 ``` @@ -321,6 +331,7 @@ 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! 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,6 +374,7 @@ 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; // jump to the label below (doesn't work) @@ -369,6 +382,7 @@ label: for (...) ``` A `break` directive must be inside a code block. Technically, any labelled code block will do, e.g.: + ```js label: { // ... diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md index 554c2f3b9..76d7e3425 100644 --- a/1-js/02-first-steps/14-switch/article.md +++ b/1-js/02-first-steps/14-switch/article.md @@ -139,7 +139,11 @@ switch (a) { Acum, atât `3` cât și `5` arată același mesaj. +<<<<<<< HEAD Posibilitatea de a "grupa" cazuri este un efect secundar al modului în care funcționează `switch/case` fără `break`. Execuția blocului `case 3` începe de la linia marcată `(*)` și continuă în `case 5`, pentru că nu există niciun `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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Tipul contează 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 b952d5943..c6dd891bd 100644 --- a/1-js/02-first-steps/16-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -82,7 +82,7 @@ let sayHi = function() { // (1) create alert( "Hello" ); }; -let func = sayHi; +let func = sayHi; //(2) // ... ``` diff --git a/1-js/02-first-steps/18-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md index 016214e3b..7a99e66be 100644 --- a/1-js/02-first-steps/18-javascript-specials/article.md +++ b/1-js/02-first-steps/18-javascript-specials/article.md @@ -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,11 @@ Assignments : There is a simple assignment: `a = b` and combined ones like `a *= 2`. Bitwise +<<<<<<< HEAD : Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed. +======= +: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Conditional : The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`. diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg index a3c7db6ec..5fc6dce3a 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg @@ -1 +1 @@ -open sources \ No newline at end of file +open sources \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg index 6e7b60f85..63bf4966e 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg @@ -1 +1 @@ -here's the listbreakpoints \ No newline at end of file +here's the listbreakpoints \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg index d5d2a0b93..3fe5f124f 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg index 83468fddb..0147c2e0a 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.svg @@ -1 +1 @@ -213see the outer call detailswatch expressionscurrent variables \ No newline at end of file +213see the outer call detailswatch expressionscurrent variables \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg index 23937e0d6..9fa1b3b8c 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg @@ -1 +1 @@ -nested calls \ No newline at end of file +nested calls \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg index 41a3d8784..016708256 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.svg @@ -1 +1 @@ -213 \ No newline at end of file +213 \ No newline at end of file diff --git a/1-js/03-code-quality/02-coding-style/code-style.svg b/1-js/03-code-quality/02-coding-style/code-style.svg index 12a755c97..739d9f1ed 100644 --- a/1-js/03-code-quality/02-coding-style/code-style.svg +++ b/1-js/03-code-quality/02-coding-style/code-style.svg @@ -1 +1 @@ -2No space between the function name and parentheses between the parentheses and the parameterIndentation 2 spacesA space after for/if/while…} else { without a line breakSpaces around a nested callAn empty line between logical blocksLines are not very longA semicolon ; is mandatorySpaces around operatorsCurly brace { on the same line, after a spaceA space between argumentsA space between parameters \ No newline at end of file +2No space between the function name and parentheses between the parentheses and the parameterIndentation 2 spacesA space after for/if/while…} else { without a line breakSpaces around a nested callAn empty line between logical blocksLines are not very longA semicolon ; is mandatorySpaces around operatorsCurly brace { on the same line, after a spaceA space between argumentsA space between parameters \ No newline at end of file diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index 5a4b90cfe..e837cfae6 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -148,12 +148,16 @@ De ce este rezolvată sarcina în acest mod? Dacă sunt multe moduri de a rezolva sarcina, de ce tocmai acesta? În special când nu este chiar cel mai evident. +<<<<<<< HEAD Fără asemenea comentarii următoarea situație este posibilă: 1. Tu (sau colegul tău) deschizi codul scris acum ceva timp, și vezi că este "suboptimizat". 2. Te gândești: "Ce stupid am fost atunci, și cât de deștept sunt acum", și rescrii codul folosind varianta "mult mai evidentă și corectă". 3. ...Impulsul de a rescrie codul a fost bun. Dar în continuarea dezvoltării observi că "cea mai evidentă soluție" este defapt insuficientă. Îți amintești cu greu de ce, deoarece ai testat-o acum mult timp. Te întorci la varianta corectă, dar timpul a fost pierdut. Comentariile care explică soluția sunt foarte importante. Te ajută să continui dezvoltarea în direcția cea bună. +======= +Also, there are tools like [JSDoc 3](https://github.com/jsdoc/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Orice caracteristici folosite ale codului ? Unde sunt folosite ? : În cazul în care codul are ceva subtil sau contraintuitiv, atunci este necesară comentarea. diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 42dccbebf..8eb681049 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -7,7 +7,7 @@ Teams behind JavaScript engines have their own ideas about what to implement fir 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). As programmers, we'd like to use most recent features. The more good stuff - the better! @@ -70,11 +70,15 @@ if (!Math.trunc) { // if no such function ``` JavaScript is a highly dynamic language. Scripts may add/modify any function, even built-in ones. +<<<<<<< HEAD Two interesting polyfill libraries are: - [core js](https://github.com/zloirock/core-js) that supports a lot, allows to include only needed features. - [polyfill.io](https://polyfill.io/) service that provides a script with polyfills, depending on the features and user's browser. +======= +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 +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 @@ -85,7 +89,7 @@ Just don't forget to use a transpiler (if using modern syntax or operators) and 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 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/article.md b/1-js/04-object-basics/01-object/article.md index d3f580e92..6b1416dcf 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -44,7 +44,11 @@ Obiectul rezultat `user` poate fi imaginat ca un sertar cu două dosare marcate ![user object](object-user.svg) +<<<<<<< HEAD Putem adăuga, șterge sau citi dosare din sertar la orice moment. +======= +We can add, remove and read files from it at any time. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Valorile proprietăților sunt accesibile folosind notația cu punct. @@ -62,7 +66,11 @@ user.isAdmin = true; ![user object 2](object-user-isadmin.svg) +<<<<<<< HEAD Pentru a șterge o proprietate, putem folosi operatorul `delete`: +======= +To remove a property, we can use the `delete` operator: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js delete user.age; @@ -201,13 +209,21 @@ let bag = { }; ``` +<<<<<<< HEAD Parantezele pătrate sunt mult mai puternice decât notația cu punct. Ele permit orice fel de nume și de variabile pentru proprietăți, dar sunt mai dificil de scris. +======= +Square brackets are much more powerful than dot notation. They allow any property names and variables. But they are also more cumbersome to write. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Deci, de cele mai multe ori, când numele proprietăților sunt cunoscute și simple se folosește punctul, iar daca avem nevoie de ceva mai complex, trecem la parantezele pătrate. ## Prescurtare (shorthand) pentru valoarea proprietății +<<<<<<< HEAD În cod real folosim des variabile existente ca valori pentru numele proprietăților. +======= +In real code, we often use existing variables as values for property names. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu: @@ -251,7 +267,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: @@ -324,7 +340,11 @@ alert( "blabla" in user ); // false, user.blabla nu există Luați aminte că, la stânga operatorului `in` trebuie sa fie un *nume de proprietate*. Uzual numele este un șir de caractere între ghilimele. +<<<<<<< HEAD Dacă omitem ghilimelele, adica o variabilă, ar trebui sa conțină numele actual pe care îl testăm. Spre exemplu: +======= +If we omit quotes, that means a variable should contain the actual name to be tested. For instance: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let user = { age: 30 }; @@ -356,7 +376,11 @@ Situații de genul ăsta apar foarte rar, deoarece `undefined` nu este assigned ## Bucla "for..in" [#forin] +<<<<<<< HEAD Pentru a traversa toate cheile unui obiect, există o formă specială de buclă: `for..in`. Aceasta este complet diferită de construcția buclei `for(;;)` pe care am studiat-o anterior. +======= +## The "for..in" loop [#forin] +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Sintaxa: @@ -411,7 +435,11 @@ for (let code in codes) { */!* ``` +<<<<<<< HEAD Obiectul poate fi folosit pentru a sugera o listă de opțiuni utilizatorilor. Dacă construim un site pentru audiență germană, atunci probabil dorim ca `49` să fie primul. +======= +The object may be used to suggest a list of options to the user. If we're making a site mainly for a German audience then we probably want `49` to be the first. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Dar dacă rulăm codul, vedem o cu totul altă imagine: @@ -423,6 +451,7 @@ Prefixele sunt sortate in ordine crescătoare, deoarece sunt de tip integer. Dec ````smart header="Proprietăți tip Integer? Ce-i aia?" Termenul "Proprietate integer" de aici, semnifică un string care poate fi convertit în-și-din integer fără nicio modificare. +<<<<<<< HEAD Deci, "49" este un nume de proprietate de tip integer, deoarece când este transformat într-un integer și înapoi, el rămâne neschimbat. Dar "+49" și "1.2" nu: ```js run @@ -430,6 +459,16 @@ Deci, "49" este un nume de proprietate de tip integer, deoarece când este trans alert( String(Math.trunc(Number("49"))) ); // "49", la fel, proprietate de tip integer alert( String(Math.trunc(Number("+49"))) ); // "49", nu este la fel ca "+49" ⇒ proprietatea nu este de tip integer alert( String(Math.trunc(Number("1.2"))) ); // "1", nu este la fel ca "1.2" ⇒ proprietatea nu este de tip integer +======= +So, `"49"` is an integer property name, because when it's transformed to an integer number and back, it's still the same. But `"+49"` and `"1.2"` are not: + +```js run +// Number(...) explicitly converts to a number +// Math.trunc is a built-in function that removes the decimal part +alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property +alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property +alert( String(Math.trunc(Number("1.2"))) ); // "1", not same "1.2" ⇒ not integer property +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ```` @@ -478,9 +517,15 @@ Ele stochează proprietăți (perechi cheie-valoare), unde: - Proprietățile chei trebuie să fie șiruri sau simboluri (de obicei șiruri). - Valorile pot fi de orice tip. +<<<<<<< HEAD Pentru a accesa o proprietate putem folosi: - Notația cu punct: `obj.property`. - Notația cu paranteze pătrate `obj["property"]`. Parantezele pătrate permit preluarea cheii dintr-o variabilă, precum `obj[varWithKey]`. +======= +To access a property, we can use: +- The dot notation: `obj.property`. +- Square brackets notation `obj["property"]`. Square brackets allow taking the key from a variable, like `obj[varWithKey]`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Operatori adiționali: - Pentru a șterge o proprietate: `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 b56a8034b..e80f748ab 100644 --- a/1-js/04-object-basics/02-object-copy/article.md +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -37,7 +37,7 @@ And here's how it's actually stored in memory: The object is stored somewhere in memory (at the right of the picture), while the `user` variable (at the left) has a "reference" to it. -We may think of an object variable, such as `user`, as like a sheet of paper with the address of the object on it. +We may think of an object variable, such as `user`, like a sheet of paper with the address of the object on it. When we perform actions with the object, e.g. take a property `user.name`, the JavaScript engine looks at what's at that address and performs the operation on the actual object. @@ -100,15 +100,37 @@ alert( a == b ); // false For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake. +````smart header="Const objects can be modified" +An important side effect of storing objects as references is that an object declared as `const` *can* be modified. + +For instance: + +```js run +const user = { + name: "John" +}; + +*!* +user.name = "Pete"; // (*) +*/!* + +alert(user.name); // Pete +``` + +It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change. + +In other words, the `const user` gives an error only if we try to set `user=...` as a whole. + +That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter . +```` + ## Cloning and merging, Object.assign [#cloning-and-merging-object-assign] So, copying an object variable creates one more reference to the same object. -But what if we need to duplicate an object? Create an independent copy, a clone? - -That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. But there is rarely a need -- copying by reference is good most of the time. +But what if we need to duplicate an object? -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. +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: @@ -133,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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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 }; @@ -159,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: @@ -171,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 @@ -182,15 +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 @@ -205,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 = { @@ -223,37 +250,71 @@ let clone = Object.assign({}, user); alert( user.sizes === clone.sizes ); // true, same object // user and clone share sizes -user.sizes.width++; // change a property from one place -alert(clone.sizes.width); // 51, 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, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning". +To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning. -We can use recursion to implement it. Or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). -````smart header="Const objects can be modified" -An important side effect of storing objects as references is that an object declared as `const` *can* be modified. +### structuredClone -For instance: +The call `structuredClone(object)` clones the `object` with all nested properties. + +Here's how we can use it in our example: ```js run -const user = { - name: "John" +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } }; *!* -user.name = "Pete"; // (*) +let clone = structuredClone(user); */!* -alert(user.name); // Pete +alert( user.sizes === clone.sizes ); // false, different objects + +// user and clone are totally unrelated now +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 50, not related ``` -It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change. +The `structuredClone` method can clone most data types, such as objects, arrays, primitive values. -In other words, the `const user` gives an error only if we try to set `user=...` as a whole. +It also supports circular references, when an object property references the object itself (directly or via a chain or references). -That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter . -```` +For instance: + +```js run +let user = {}; +// let's create a circular reference: +// user.me references the user itself +user.me = user; + +let clone = structuredClone(user); +alert(clone.me === clone); // true +``` + +As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well. + +Although, there are cases when `structuredClone` fails. + +For instance, when an object has a function property: + +```js run +// error +structuredClone({ + f: function() {} +}); +``` + +Function properties aren't supported. + +To handle such complex cases we may need to use a combination of cloning methods, write custom code or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). ## Summary @@ -261,4 +322,4 @@ Objects are assigned and copied by reference. In other words, a variable stores All operations via copied references (like adding/removing properties) are performed on the same single object. -To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function `structuredClone` or use a custom cloning implementation, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md index 02c9dcb74..72525dfe9 100644 --- a/1-js/04-object-basics/03-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -74,7 +74,11 @@ Acum dacă procedăm la fel: user = null; ``` +<<<<<<< HEAD ...Atunci obiectul este încă accesibil prin intermediul variabilei globale `admin`, deci este în memorie. Dacă rescriem și `admin`, atunci poate fi indepărtat. +======= +...Then the object is still reachable via `admin` global variable, so it must stay in memory. If we overwrite `admin` too, then it can be removed. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Obiecte interconectate @@ -169,11 +173,19 @@ Primul pas marchează rădăcinile: ![](garbage-collection-2.svg) +<<<<<<< HEAD Apoi sunt marcate referințele lor: ![](garbage-collection-3.svg) ...Și referințele acestora, atât cât este posibil: +======= +Then we follow their references and mark referenced objects: + +![](garbage-collection-3.svg) + +...And continue to follow further references, while possible: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](garbage-collection-4.svg) @@ -183,13 +195,23 @@ Acum, obiectele care nu au putut fi vizitate pe parcursul procesului sunt consid 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. +<<<<<<< HEAD Acesta este conceptul de funcționare a colectării reziduurilor. Motoarele limbajului JavaScript aplică numeroase optimizări pentru a-l face să ruleze mai rapid și să nu afecteze execuția. +======= +That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not introduce any delays into the code execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Câteva dintre optimizări: +<<<<<<< HEAD - **Colectare generațională** -- obiectele sunt împărțite în două seturi: "cele noi" și "cele vechi". Multe obiecte apar, își fac treaba și mor repede, pot fi curățate agresiv. Cele care supraviețuiesc suficient de mult devin "vechi" și sunt examinate mai rar. - **Colectare incrementală** -- dacă există multe obiecte și încercăm să parcurgem simultan întregul set obiect, poate dura mai mult și poate introduce întârzieri vizibile în execuție. Astfel încât, motorul încearcă să împartă în bucăți colectarea reziduurilor. Apoi bucățile sunt executate separat, una câte una. Asta necesită o mai bună contabilitate între ele pentru a urmări modificările, dar avem mai multe întârzieri micuțe în loc de una mare. - **Colectare în timp-inactiv** -- colectorul de reziduuri încearcă să ruleze numai în timp ce procesorul este inactiv, pentru a reduce posibilul efect asupra execuției. +======= +- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". In typical code, many objects have a short life span: they appear, do their job and die fast, so it makes sense to track new objects and clear the memory from them if that's the case. Those that survive for long enough, become "old" and are examined less often. +- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine splits the whole set of existing objects into multiple parts. And then clear these parts one after another. There are many small garbage collections instead of a total one. That requires some extra bookkeeping between them to track changes, but we get many tiny delays instead of a big one. +- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Există și alte optimzări și modele ale algoritmilor de colectare a reziduurilor. Oricât de mult mi-ar plăcea să le descriu aici, trebuie să mă abțin, întrucât diferite motoare implementează ajustări și tehnici diferite. Și, ceea ce este și mai important, lucrurile se schimbă pe măsură ce motoarele se dezvoltă, deci aprofundarea "în avans", fără o nevoie reală probabil că nu merită acest lucru. Cu excepția cazului în care, bineînțeles, este o chestiune de interes pur, atunci vor fi câteva link-uri pentru tine mai jos. @@ -197,16 +219,30 @@ Există și alte optimzări și modele ale algoritmilor de colectare a reziduuri Lucrurile principale de știut: +<<<<<<< HEAD - Colectarea reziduurilor se face automat. Nu o putem forța sau preveni. - Obiectele sunt menținute în memorie atât timp cât sunt accesibile. - A fi referențiat nu este același lucru cu a fi accesibil (dintr-o rădăcină): un pachet de obiecte interconectate poate deveni inaccesibil în ansamblu. +======= +- Garbage collection is performed automatically. We cannot force or prevent it. +- Objects are retained in memory while they are reachable. +- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole, as we've seen in the example above. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Motoarele moderne implementează algoritmi avansați de colectare a reziduurilor. O carte generală "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al) acoperă câteva dintre ele. +<<<<<<< HEAD Dacă sunteți familiari cu programarea low-level, informații detaliate despre V8 garbage collector se află în articolul [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). [V8 blog](https://v8.dev/) publică, deasemenea, articole din timp în timp despre schimbările din gestionarea memoriei. Desigur, pentru a învăța colectarea reziduurilor, ar fi bine să vă pregătiți învățând despre structurile interne ale V8-ului în general și să citiți blogul lui [Vyacheslav Egorov](http://mrale.ph) care a lucrat ca unul dintre inginerii V8-ului. Spun: "V8", deoarece este cel mai bine acoperit cu articole din internet. Pentru alte motoare, multe abordări sunt similare, dar colectarea reziduurilor diferă în multe aspecte. Cunoașterea în profunzime a motoarelor este folositoare atunci când aveți nevoie de optimizări low-level. Ar fi înțelept să planificați acest lucru ca pas următor după ce vă familiarizați cu limbajul. +======= +If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). + +The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](https://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. + +In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 0c54fac3f..7dfd775e7 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/task.md +++ b/1-js/04-object-basics/04-object-methods/7-calculator/task.md @@ -6,9 +6,15 @@ importance: 5 Creați un obiect `calculator` cu trei metode: +<<<<<<< HEAD - `read()` solicită două valori și le salvează ca proprietăți ale obiectului cu numele `a` respectiv `b`. - `sum()` returnează suma valorilor salvate. - `mul()` multiplică valorile salvate și returnează rezultatul. +======= +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. +- `sum()` returns the sum of saved values. +- `mul()` multiplies saved values and returns the result. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let calculator = { 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 a8b7dc033..a6da81988 100644 --- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md +++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md @@ -4,7 +4,11 @@ importance: 2 # Înlănțuirea +<<<<<<< HEAD Există un obiect `ladder` (scară) care permite urcarea și coborârea: +======= +There's a `ladder` object that allows you to go up and down: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js let ladder = { @@ -21,7 +25,11 @@ let ladder = { }; ``` +<<<<<<< HEAD În acest moment, dacă avem nevoie să facem mai multe apelări în ordine, le putem face astfel: +======= +Now, if we need to make several calls in sequence, we can do it like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js ladder.up(); @@ -32,10 +40,18 @@ ladder.down(); ladder.showStep(); // 0 ``` +<<<<<<< HEAD Modificați codul pentru `up`, `down` și `showStep` pentru a face apelările înlănțuibile, astfel: +======= +Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0 ``` +<<<<<<< HEAD Asemenea abordare este utilizată pe scară largă în bibliotecile JavaScript. +======= +Such an approach is widely used across JavaScript libraries. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md index 7abfb567f..3302d2af2 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -50,8 +50,13 @@ let user = { *!* // first, declare function sayHi() { +<<<<<<< HEAD alert("Salut!"); }; +======= + alert("Hello!"); +} +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 // then add as a method user.sayHi = sayHi; @@ -90,7 +95,11 @@ user = { După cum s-a demonstrat, putem omite `"function"` și scriem doar `sayHi()`. +<<<<<<< HEAD Să spunem adevărul, notațiile nu sunt complet identice. Există diferențe subtile legate de moștenirea obiectelor (care vor fi acoperite ulterior), dar deocamdată nu contează. În aproape toate cazurile, se preferă sintaxa mai scurtă. +======= +To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases, the shorter syntax is preferred. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## "this" în metode diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md index d80113acc..e932a201a 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md @@ -10,8 +10,8 @@ Is it possible to create functions `A` and `B` so that `new A() == new B()`? function A() { ... } function B() { ... } -let a = new A; -let b = new B; +let a = new A(); +let b = new B(); alert( a == b ); // true ``` diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md index 60e7c373e..c862bec40 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md @@ -6,7 +6,7 @@ importance: 5 Create a constructor function `Calculator` that creates objects with 3 methods: -- `read()` asks for two values using `prompt` and remembers them in object properties. +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. - `sum()` returns the sum of these properties. - `mul()` returns the multiplication product of these properties. diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index f3e9c3ec0..a335464f1 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -171,7 +171,7 @@ alert( new SmallUser().name ); // John Usually constructors don't have a `return` statement. Here we mention the special behavior with returning objects mainly for the sake of completeness. ````smart header="Omitting parentheses" -By the way, we can omit parentheses after `new`, if it has no arguments: +By the way, we can omit parentheses after `new`: ```js let user = new User; // <-- no parentheses diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md index f27f7d1d2..4c6029423 100644 --- a/1-js/04-object-basics/07-optional-chaining/article.md +++ b/1-js/04-object-basics/07-optional-chaining/article.md @@ -32,7 +32,7 @@ In many practical cases we'd prefer to get `undefined` instead of an error here let html = document.querySelector('.elem').innerHTML; // error if it's null ``` -Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result. +Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` property of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result. How can we do this? @@ -44,11 +44,19 @@ let user = {}; alert(user.address ? user.address.street : undefined); ``` -It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. For more deeply nested properties, that becomes a problem as more repetitions are required. +It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. -E.g. let's try getting `user.address.street.name`. +Here's how the same would look for `document.querySelector`: -We need to check both `user.address` and `user.address.street`: +```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 @@ -58,7 +66,7 @@ alert(user.address ? user.address.street ? user.address.street.name : null : nul That's just awful, one may even have problems understanding such code. -Don't even care to, as there's a better way to write it, using the `&&` operator: +There's a little better way to write it, using the `&&` operator: ```js run let user = {}; // user has no address @@ -92,6 +100,12 @@ 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 @@ -108,9 +122,9 @@ E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/un ```warn header="Don't overuse the optional chaining" We should use `?.` only where it's ok that something doesn't exist. -For example, if according to our coding logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`. +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 see a programming error 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" @@ -127,7 +141,7 @@ The variable must be declared (e.g. `let/const/var user` or as a function parame 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: @@ -135,7 +149,7 @@ For instance: let user = null; let x = 0; -user?.sayHi(x++); // no "sayHi", so the execution doesn't reach x++ +user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++ alert(x); // 0, value not incremented ``` @@ -162,13 +176,13 @@ userAdmin.admin?.(); // I am admin */!* *!* -userGuest.admin?.(); // nothing (no such method) +userGuest.admin?.(); // nothing happens (no such method) */!* ``` -Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the user object exists, so it's safe read from it. +Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the `user` object exists, so it's safe read from it. -Then `?.()` checks the left part: if the admin function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors. +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. @@ -179,7 +193,7 @@ let user1 = { firstName: "John" }; -let user2 = null; +let user2 = null; alert( user1?.[key] ); // John alert( user2?.[key] ); // undefined @@ -192,17 +206,16 @@ 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. +The optional chaining `?.` has no use on the left side of an assignment. For example: ```js run let user = null; user?.name = "John"; // Error, doesn't work -// because it evaluates to undefined = "John" +// because it evaluates to: undefined = "John" ``` -It's just not that smart. ```` ## Summary @@ -217,4 +230,4 @@ As we can see, all of them are straightforward and simple to use. The `?.` check A chain of `?.` allows to safely access nested properties. -Still, we should apply `?.` carefully, only where it's acceptable that the left part doesn't exist. So that it won't hide programming errors from us, if they occur. +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 f4fda85b9..5a0377bcf 100644 --- a/1-js/04-object-basics/08-symbol/article.md +++ b/1-js/04-object-basics/08-symbol/article.md @@ -1,10 +1,23 @@ # Tipul simbol ("symbol") +<<<<<<< HEAD Conform specificațiilor, doar două tipuri primitive pot servi drept chei de proprietăți ale obiectului: - tipul string, sau - tipul symbol. +======= +By specification, only two primitive types may serve as object property keys: + +- string type, or +- symbol type. + +Otherwise, if one uses another type, such as number, it's autoconverted to string. So that `obj[1]` is the same as `obj["1"]`, and `obj[true]` is the same as `obj["true"]`. + +Until now we've been using only strings. + +Now let's explore symbols, see what they can do for us. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 În caz contrar, dacă cineva folosește alt tip, cum ar fi număr, este autoconvertit în string. Astfel `obj[1]` este același cu `obj["1"]`, și `obj[true]` este același cu `obj["true"]`. @@ -22,14 +35,22 @@ O valoare de acest tip poate fi creată folosind `Symbol()`: let id = Symbol(); ``` +<<<<<<< HEAD După creare, putem aloca o descriere simbolului (numită și symbol name), folositoare îndeosebi pentru scopuri de depanare: +======= +Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js // id este un simbol cu descrierea "id" let id = Symbol("id"); ``` +<<<<<<< HEAD Simbolurile sunt garantate a fi unice. Chiar dacă creăm mai multe simboluri cu aceeași descriere, ele reprezintă valori diferite. Descrierea este doar o etichetă care nu afectează nimic. +======= +Symbols are guaranteed to be unique. Even if we create many symbols with exactly the same description, they are different values. The description is just a label that doesn't affect anything. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu, mai jos sunt două simboluri cu aceeași descriere -- ele nu sunt egale: @@ -44,7 +65,14 @@ alert(id1 == id2); // false Dacă sunteți familiari cu Ruby sau alt limbaj de programare ce are de asemenea orice fel de "simboluri" -- nu vă lăsați păcălit. Simbolurile JavaScript sunt diferite. +<<<<<<< HEAD Așadar, pentru a rezuma, un simbol este o "valoare unică primitive" cu o descriere opțională. Haideți să vedem unde le putem folosi. +======= +So, to summarize, a symbol is a "primitive unique value" with an optional description. Let's see where we can use them. + +````warn header="Symbols don't auto-convert to a string" +Most values in JavaScript support implicit conversion to a string. For instance, we can `alert` almost any value, and it will work. Symbols are special. They don't auto-convert. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ````warn header="Simbolurile nu se auto-convertesc la un șir" Cele mai multe valori în JavaScript suportă conversie implicită la un șir. De exemplu, putem `alert`-a aproape orice valoare și va funcționa. Simbolurile sunt speciale. Ele nu se auto-convertesc. @@ -62,6 +90,11 @@ Aceasta este un "language guard" împotriva încurcăturilor, deoarece string ș Dacă vrem neapărat să afișăm un simbol, trebuie să apelăm explicit metoda `.toString()` a acestuia, ca aici: +<<<<<<< HEAD +======= +If we really want to show a symbol, we need to explicitly call `.toString()` on it, like here: + +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let id = Symbol("id"); *!* @@ -69,7 +102,11 @@ alert(id.toString()); // Symbol(id), acum funcționează */!* ``` +<<<<<<< HEAD Sau să obținem proprietatea `symbol.description` pentru a afișa numai descrierea: +======= +Or get `symbol.description` property to show the description only: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let id = Symbol("id"); @@ -82,7 +119,12 @@ alert(id.description); // id ## Proprietăți "ascunse" +<<<<<<< HEAD Simbolurile ne permit să creăm proprietăți "ascunse" ale unui obiect, pe care nicio altă parte a codului nu le poate accesa sau suprascrie din greșeală. +======= + +Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu, dacă lucrăm cu obiecte `user`, ce fac parte din cod extern, am dori să le adăugăm identificatori. @@ -102,9 +144,15 @@ alert( user[id] ); // putem accesa informația folosind simbolul ca și cheie Care sunt avantajele folosirii `Symbol("id")` față de un șir `"id"`? +<<<<<<< HEAD Deoarece obiectele `user` aparțin altui cod, nu este sigur să le adăugăm câmpuri, deoarece am putea afecta comportamentul predefinit în acel codebase. Cu toate acestea, simbolurile nu pot fi accesate din greșeală. Codul terților nu va fi conștient de simbolurile nou definite, deci este sigur să adăugăm symbol la obiectele `user`. De asemenea, imaginați-vă că un alt script dorește să aibă propriul său identificator în interiorul obiectului `user`, pentru propriile sale scopuri. +======= +As `user` objects belong to another codebase, it's unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code won't be aware of newly defined symbols, so it's safe to add symbols to the `user` objects. + +Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Apoi acel script își poate crea propriul `Symbol("id")`, ca aici: @@ -167,7 +215,11 @@ let user = { for (let key in user) alert(key); // name, age (niciun simbol) */!* +<<<<<<< HEAD // accesul direct către simbol funcționează +======= +// the direct access by the symbol works +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 alert( "Direct: " + user[id] ); // Direct: 123 ``` @@ -216,12 +268,20 @@ Simbolurile din interiorul registrului sunt denumite *simboluri globale*. Dacă ```smart header="Asta seamănă cu Ruby" În unele limbaje de programare, precum Ruby, există un singur simbol pentru un nume. +<<<<<<< HEAD În JavaScript, după cum putem vedea, acest fapt este adevărat pentru simbolurile globale. +======= +In JavaScript, as we can see, that's true for global symbols. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ### Symbol.keyFor +<<<<<<< HEAD Am văzut că pentru simbolurile globale, `Symbol.for(key)` returnează un simbol după nume. Pentru a face opusul -- să returnăm un nume pentru un simbol global -- putem folosi: `Symbol.keyFor(sym)`: +======= +We have seen that for global symbols, `Symbol.for(key)` returns a symbol by name. To do the opposite -- return a name by global symbol -- we can use: `Symbol.keyFor(sym)`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu: @@ -237,7 +297,11 @@ alert( Symbol.keyFor(sym2) ); // id Acest `Symbol.keyFor` utilizează în mod intern registrul global de simboluri pentru a căuta cheia simbolului. Deci nu funcționează pentru simboluri non-globale. Dacă simbolul nu este global, nu îl va putea găsi și va returna `undefined`. +<<<<<<< HEAD Acestea fiind spuse, orice simbol poate avea proprietatea `description`. +======= +That said, all symbols have the `description` property. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu: @@ -277,12 +341,22 @@ Simbolurile sunt întotdeauna valori diferite, chiar dacă au același nume. Dac Simbolurile au două cazuri principale de folosire: +<<<<<<< HEAD 1. Proprietăți "ascunse" ale unui obiect. +======= +1. "Hidden" object properties. + + If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Dacă vrem să adăugăm o proprietate într-un obiect care "aparține" altui script sau un library, putem crea un simbol și îl putem folosi ca și property key. O proprietate simbolică nu apare în `for..in`, așa că nu va fi procesată accidental împreună cu alte proprietăți. De asemenea nu va fi accesată direct, deoarece un alt script nu are simbolul nostru. Așa că proprietatea va fi protejată împotriva utilizării sau suprascrierii accidentale. Deci putem ascunde "după cortină" ceva în obiectele de care avem nevoie, dar pe care alții nu ar trebui să le vadă, folosind proprietăți simbolice. +<<<<<<< HEAD 2. Există multe simboluri sistem folosite de JavaScript care sunt accesibile prin `Symbol.*`. Le putem folosi pentru a altera câteva comportamente incluse în limbaj. De exemplu, mai târziu în tutorial vom folosi `Symbol.iterator` pentru [iterabile](info:iterable), `Symbol.toPrimitive` pentru a configura [conversia object-to-primitive](info:object-toprimitive) ș.a.m.d. Tehnic, simbolurile nu sunt 100% ascunse. Există o metodă built-in [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) care ne permit sa accesăm toate simbolurile. Mai există o metodă numită [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) care returnează *toate* cheile unui obiect incluzându-le și pe cele simbolice. Dar cele mai multe librării, funcții built-in și construcții de sintaxă nu folosesc aceste metode. +======= +Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. But most libraries, built-in functions and syntax constructs don't use these methods. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 1b136135d..54c1c47f1 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -3,15 +3,27 @@ Ce se întâmplă atunci când obiectele sunt adunate `obj1 + obj2`, scăzute `obj1 - obj2` sau tipărite folosind `alert(obj)`? +<<<<<<< HEAD JavaScript nu vă permite să personalizați modul în care operatorii lucrează pe obiecte. Spre deosebire de alte limbaje de programare, cum ar fi Ruby sau C++, nu putem implementa o metodă specială de obiect pentru a gestiona o adunare (sau alți operatori). +======= +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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 În cazul unor astfel de operații, obiectele sunt convertite automat în primitive, iar apoi operația este efectuată asupra acestor primitive și are ca rezultat o valoare primitivă. +<<<<<<< HEAD Aceasta este o limitare importantă: rezultatul lui `obj1 + obj2` (sau altă operație matematică) nu poate fi un alt obiect! +======= +That's an important limitation: the result of `obj1 + obj2` (or another math operation) can't be another object! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 E.g. nu putem crea obiecte reprezentând vectori sau matrici (sau realizări sau orice altceva), să le adunăm și să ne așteptăm ca rezultat un obiect "însumat". Astfel de realizări arhitecturale sunt automat "în afara discuției". +<<<<<<< HEAD Așadar, pentru că nu putem face mare lucru aici, nu există matematică cu obiecte în proiectele reale. Atunci când se întâmplă, de obicei, este din cauza unei greșeli de codare. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 În acest capitol vom acoperi modul în care un obiect se convertește în primitivă și cum să îl personalizăm. @@ -24,6 +36,7 @@ Avem două scopuri: În capitolul am văzut regulile pentru conversiile numerice, string și booleane ale primitivelor. Dar am lăsat un gol pentru obiecte. Acum, după cum știm despre metode și simboluri, devine posibil să o completăm. +<<<<<<< HEAD 1. Într-un context boolean toate obiectele sunt `true` (adevărate). Există doar conversii numerice și conversii de șiruri. 2. Conversia numerică are loc atunci când scădem obiecte sau aplicăm funcții matematice. De exemplu, obiectele de tip `Date` (ce vor fi studiate în capitolul ) pot fi scăzute, iar rezultatul `date1 - date2` este diferența de timp dintre cele două date. 3. În ceea ce privește conversia șirului -- se întâmplă de obicei atunci când afișăm un obiect precum `alert(obj)` și în contexte similare. @@ -37,6 +50,21 @@ Acum să intrăm în detalii tehnice, pentru că este singura modalitate de a ac Cum decide JavaScript ce conversie să aplice? Există trei variante de conversie de tip, care se întâmplă în diverse situații. Acestea se numesc "indicii", așa cum sunt descrise în [specificație](https://tc39.github.io/ecma262/#sec-toprimitive): +======= +1. There's no conversion to boolean. All objects are `true` in a boolean context, as simple as that. There exist only numeric and string conversions. +2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates. +3. As for the string conversion -- it usually happens when we output an object with `alert(obj)` and in similar contexts. + +We can implement string and numeric conversion by ourselves, using special object methods. + +Now let's get into technical details, because it's the only way to cover the topic in-depth. + +## Hints + +How does JavaScript decide which conversion to apply? + +There are three variants of type conversion, that happen in various situations. They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `"string"` : Pentru o conversie obiect-la-șir, atunci când efectuăm o operație asupra unui obiect care se așteaptă la un șir, cum ar fi `alert`: @@ -64,12 +92,20 @@ Există trei variante de conversie de tip, care se întâmplă în diverse situa let greater = user1 > user2; ``` +<<<<<<< HEAD Majoritatea funcțiilor matematice încorporate includ, de asemenea, o astfel de conversie. +======= + Most built-in mathematical functions also include such conversion. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 `"default"` : Se întâmplă în cazuri rare când operatorul nu este „sigur” la ce tip să se aștepte. +<<<<<<< HEAD De exemplu, operatorul binar plus `+` poate funcționa atât cu șiruri (le concatenează) cât și cu numere (le adună), deci atât șirurile cât și numerele se acceptă. Așadar dacă un plus binar obține un obiect ca argument, acesta folosește indiciul `"default"` pentru a-l converti. +======= + For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them). So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De asemenea, dacă un obiect este comparat folosind `==` cu un șir, un număr sau un simbol, nu este clar ce conversie ar trebui efectuată, deci se utilizează indiciul `"default"`. @@ -83,19 +119,35 @@ Există trei variante de conversie de tip, care se întâmplă în diverse situa Operatorii de comparație mai mare și mai mic, cum ar fi `<` `>`, pot funcționa atât cu șiruri cât și cu numere. Totuși, ei folosesc indiciul `"number"`, nu `"default"`. Asta din motive istorice. +<<<<<<< HEAD În practică însă, lucrurile sunt puțin mai simple. Toate obiectele încorporate cu excepția unui singur caz (obiectul `Date`, îl vom învăța mai târziu), implementează conversia `"default"` în același mod ca și `"number"`. Și probabil că ar trebui să facem la fel. Totuși, este important să cunoaștem toate cele 3 indicații, în curând vom vedea de ce. +======= +In practice though, things are a bit simpler. + +All built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we probably should do the same. + +Still, it's important to know about all 3 hints, soon we'll see why. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 **Pentru a face conversia, JavaScript încearcă să găsească și să apeleze trei metode obiect:** +<<<<<<< HEAD 1. Apelează `obj[Symbol.toPrimitive](indiciu)` - metoda cu cheia simbolică `Symbol.toPrimitive` (simbol de sistem), dacă o astfel de metodă există, 2. În caz contrar, dacă indiciul este `"string"` - încearcă să apeleze `obj.toString()` sau `obj.valueOf()`, oricare dintre ele există. 3. Altfel dacă indiciul este `"number"` sau `"default"` - încearcă să apeleze `obj.valueOf()` sau `obj.toString()`, oricare dintre ele există. +======= +1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists, +2. Otherwise if hint is `"string"` + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. +3. Otherwise if hint is `"number"` or `"default"` + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Symbol.toPrimitive @@ -130,14 +182,23 @@ alert(+user); // indiciu: number -> 1000 alert(user + 500); // indiciu: default -> 1500 ``` +<<<<<<< HEAD După cum putem vedea din cod, obiectul `user` devine un șir autodescriptiv sau o sumă de bani în funcție de conversie. Metoda unică `user[Symbol.toPrimitive]` gestionează toate cazurile de conversie. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## toString/valueOf Dacă nu există `Symbol.toPrimitive`, atunci JavaScript încearcă să găsească metodele `toString` și `valueOf`: +<<<<<<< HEAD - Pentru indiciul "string": apelează metoda `toString`, și dacă nu există sau dacă returnează un obiect în loc de o valoare primitivă, atunci apelează `valueOf` (deci `toString` are prioritate pentru conversiile de șiruri). - Pentru alte indicii: apelează `valueOf`, iar dacă acesta nu există sau dacă returnează un obiect în loc de o valoare primitivă, atunci apelează `toString` (deci `valueOf` are prioritate pentru matematică). +======= +- 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). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Metodele `toString` și `valueOf` provin din timpuri străvechi. Ele nu sunt simboluri (simbolurile nu existau cu mult timp în urmă), ci mai degrabă metode "obișnuite" cu nume de șiruri. Ele oferă o modalitate alternativă "în stil vechi" de a implementa conversia. @@ -210,23 +271,37 @@ alert(user + 500); // toString -> John500 Lucrul important de știut despre toate metodele de conversie primitivă este că nu returnează neapărat primitiva "indicată". +<<<<<<< HEAD Nu se poate controla dacă `toString` returnează exact un șir, sau dacă metoda `Symbol.toPrimitive` returnează un număr pentru un indiciu `"number"`. +======= +There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for the hint `"number"`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Singurul lucru obligatoriu: aceste metode trebuie să returneze o primitivă, nu un obiect. ```smart header="Note istorice" Din motive istorice, dacă `toString` sau `valueOf` returnează un obiect, nu este o eroare, dar o astfel de valoare este ignorată (ca și cum metoda nu ar exista). Asta deoarece în vremuri străvechi nu exista un concept de "eroare" bun în JavaScript. +<<<<<<< HEAD În contrast, `Symbol.toPrimitive` *trebuie* să returneze o primitivă, altfel va exista o eroare. +======= +In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Alte conversii După cum știm deja, mulți operatori și funcții efectuează conversii de tip, e.g. înmulțirea `*` convertește operanzii în numere. +<<<<<<< HEAD Dacă transmitem un obiect ca argument, atunci există două etape de calcul: 1. Obiectul este convertit într-o primitivă (folosind regulile descrise mai sus). 2. Dacă este necesar pentru calcule ulterioare, primitivul rezultat este de asemenea convertit. +======= +If we pass an object as an argument, then there are two stages of calculations: +1. The object is converted to a primitive (using the rules described above). +2. If necessary for further calculations, the resulting primitive is also converted. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu: @@ -253,22 +328,36 @@ let obj = { } }; +<<<<<<< HEAD alert(obj + 2); // 22 ("2" + 2), conversia în primitivă a returnat un șir => concatenare +======= +alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Sumar Conversia obiect-la-primitivă este apelată automat de multe funcții și operatori încorporați care așteaptă ca valoare o primitivă. +<<<<<<< HEAD Există trei tipuri (indicii) ale acesteia: - `"string"` (pentru `alert` sau altă instrucțiune care are nevoie de un șir) - `"number"` (pentru matematică) - `"default"` (câțiva operatori, de obicei obiectele îl implementează în același mod ca și `"number"`.) Specificația descrie explicit ce operator folosește ce indiciu. +======= +There are 3 types (hints) of it: +- `"string"` (for `alert` and other operations that need a string) +- `"number"` (for maths) +- `"default"` (few operators, usually objects implement it the same way as `"number"`) + +The specification describes explicitly which operator uses which hint. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Algoritmul de conversie este: +<<<<<<< HEAD 1. Apelează `obj[Symbol.toPrimitive](indiciu)` dacă metoda există, 2. În caz contrar, dacă indiciul este `"string"` - încearcă să apeleze `obj.toString()` sau `obj.valueOf()`, oricare dintre ele există. @@ -278,3 +367,14 @@ Algoritmul de conversie este: Toate aceste metode trebuie să returneze o primitivă pentru a funcționa (dacă sunt definite). În practică, este adesea suficient să implementăm doar `obj.toString()` ca metodă "catch-all" pentru conversiile de șiruri care ar trebui să returneze o reprezentare "lizibilă pentru oameni" a unui obiect, în scopuri de logare sau de depanare. +======= +1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, +2. Otherwise if hint is `"string"` + - try calling `obj.toString()` or `obj.valueOf()`, whatever exists. +3. Otherwise if hint is `"number"` or `"default"` + - try calling `obj.valueOf()` or `obj.toString()`, whatever exists. + +All these methods must return a primitive to work (if defined). + +In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index c704bd980..7ea2e6342 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -4,7 +4,11 @@ In modern JavaScript, there are two types of numbers: 1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter. +<<<<<<< HEAD 2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in few special areas, we devote them a special chapter . +======= +2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1) or be less than -(253-1), as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 So here we'll talk about regular numbers. Let's expand our knowledge of them. @@ -41,7 +45,7 @@ In other words, `e` multiplies the number by `1` with the given zeroes count. 1.23e6 === 1.23 * 1000000; // e6 means *1000000 ``` -Now let's write something very small. Say, 1 microsecond (one millionth of a second): +Now let's write something very small. Say, 1 microsecond (one-millionth of a second): ```js let mсs = 0.000001; @@ -103,13 +107,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 @@ -118,7 +122,7 @@ 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)`. @@ -137,7 +141,7 @@ There are several built-in functions for rounding: : Rounds up: `3.1` becomes `4`, and `-1.1` becomes `-1`. `Math.round` -: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`, the middle case: `3.5` rounds up to `4` too. +: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`. `Math.trunc` (not supported by Internet Explorer) : Removes anything after the decimal point without rounding: `3.1` becomes `3`, `-1.1` becomes `-1`. @@ -147,8 +151,10 @@ Here's the table to summarize the differences between them: | | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` | |---|---------|--------|---------|---------| |`3.1`| `3` | `4` | `3` | `3` | +|`3.5`| `3` | `4` | `4` | `3` | |`3.6`| `3` | `4` | `4` | `3` | |`-1.1`| `-2` | `-1` | `-1` | `-1` | +|`-1.5`| `-2` | `-1` | `-1` | `-1` | |`-1.6`| `-2` | `-1` | `-2` | `-1` | @@ -188,7 +194,11 @@ There are two ways to do so: alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits ``` +<<<<<<< HEAD We can convert it to a number using the unary plus or a `Number()` call, e.g write `+num.toFixed(5)`. +======= + We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Imprecise calculations @@ -222,7 +232,17 @@ 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. +<<<<<<< HEAD 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)`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 So, division by powers `10` is guaranteed to work well in the decimal system, but division by `3` is not. For the same reason, in the binary numeral system, the division by powers of `2` is guaranteed to work, but `1/10` becomes an endless binary fraction. @@ -242,7 +262,7 @@ That's why `0.1 + 0.2` is not exactly `0.3`. ```smart header="Not only JavaScript" The same issue exists in many other programming languages. -PHP, Java, C, Perl, Ruby give exactly the same result, because they are based on the same numeric format. +PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format. ``` Can we work around the problem? Sure, the most reliable method is to round the result with the help of a method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed): @@ -266,7 +286,7 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 ``` -So, multiply/divide approach reduces the error, but doesn't remove it totally. +So, the multiply/divide approach reduces the error, but doesn't remove it totally. Sometimes we could try to evade fractions at all. Like if we're dealing with a shop, then we can store prices in cents instead of dollars. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely possible. Just round them to cut "tails" when needed. @@ -288,7 +308,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 @@ -337,7 +357,11 @@ Please note that an empty or a space-only string is treated as `0` in all numeri ````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. +<<<<<<< HEAD - `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case it returns `false`. +======= +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case, it returns `false`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Number.isNaN(NaN) ); // true @@ -348,7 +372,11 @@ Please note that an empty or a space-only string is treated as `0` in all numeri alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion ``` +<<<<<<< HEAD - `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`. +======= +- `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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run alert( Number.isFinite(123) ); // true @@ -367,7 +395,11 @@ In a way, `Number.isNaN` and `Number.isFinite` are simpler and more straightforw 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. +<<<<<<< HEAD 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. +======= +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct because internally the number has a sign bit that may be different even if all other bits are zeroes. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 In all other cases, `Object.is(a, b)` is the same as `a === b`. @@ -385,7 +417,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. @@ -479,4 +511,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/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/04-array/article.md b/1-js/05-data-types/04-array/article.md index ee2e3d713..e71e86a5b 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -426,7 +426,7 @@ let matrix = [ [7, 8, 9] ]; -alert( matrix[1][1] ); // 5, the central element +alert( matrix[0][1] ); // 2, the second value of the first inner array ``` ## toString diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 4db1a16b6..542134738 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -1,6 +1,6 @@ # Array methods -Arrays provide a lot of methods. To make things easier, in this chapter they are split into groups. +Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups. ## Add/remove items @@ -32,11 +32,11 @@ alert( arr.length ); // 3 The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`. -That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now. +That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now. So, special methods should be used. -The [arr.splice](mdn:js/Array/splice) method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements. +The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements. The syntax is: @@ -62,7 +62,7 @@ alert( arr ); // ["I", "JavaScript"] Easy, right? Starting from the index `1` it removed `1` element. -In the next example we remove 3 elements and replace them with the other two: +In the next example, we remove 3 elements and replace them with the other two: ```js run let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"]; @@ -84,7 +84,7 @@ let removed = arr.splice(0, 2); alert( removed ); // "I", "study" <-- array of removed elements ``` -The `splice` method is also able to insert the elements without any removals. For that we need to set `deleteCount` to `0`: +The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`: ```js run let arr = ["I", "study", "JavaScript"]; @@ -114,7 +114,7 @@ alert( arr ); // 1,2,3,4,5 ### slice -The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking `arr.splice`. +The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`. The syntax is: @@ -124,7 +124,7 @@ arr.slice([start], [end]) It returns a new array copying to it all items from index `start` to `end` (not including `end`). Both `start` and `end` can be negative, in that case position from array end is assumed. -It's similar to a string method `str.slice`, but instead of substrings it makes subarrays. +It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays. For instance: @@ -206,7 +206,7 @@ The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for ever The syntax: ```js arr.forEach(function(item, index, array) { - // ... do something with item + // ... do something with an item }); ``` @@ -239,7 +239,11 @@ The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/ - `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`. - `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found. +<<<<<<< HEAD Usually these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. +======= +Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 For instance: @@ -255,7 +259,11 @@ alert( arr.includes(1) ); // true Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero. +<<<<<<< HEAD If we want to check if `item` exists in the array, and don't need the 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left. @@ -274,12 +282,16 @@ const arr = [NaN]; alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0) alert( arr.includes(NaN) );// true (correct) ``` +<<<<<<< HEAD That's because `includes` was added to JavaScript much later and uses the more up to date comparison algorithm internally. +======= +That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ### find and findIndex/findLastIndex -Imagine we have an array of objects. How do we find an object with the specific condition? +Imagine we have an array of objects. How do we find an object with a specific condition? Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy. @@ -297,7 +309,7 @@ The function is called for elements of the array, one after another: - `index` is its index. - `array` is the array itself. -If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned. +If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned. For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`: @@ -313,11 +325,15 @@ 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. +<<<<<<< HEAD The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax, but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. +======= +The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`. @@ -450,11 +466,11 @@ alert(arr); // *!*1, 2, 15*/!* Now it works as intended. -Let's step aside and think what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. +Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. -By the way, if we ever want to know which elements are compared -- nothing prevents from alerting them: +By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them: ```js run [1, -2, 15, 2, 0, 8].sort(function(a, b) { @@ -526,7 +542,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'; @@ -593,9 +609,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? @@ -664,7 +680,11 @@ arr.reduce((sum, current) => sum + current); So it's advised to always specify the initial value. +<<<<<<< HEAD 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Array.isArray @@ -689,7 +709,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: @@ -749,7 +769,7 @@ A cheat sheet of array methods: - `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken. - To search among elements: - - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, return the index or `-1` if not found. + - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found. - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. - `findIndex` is like `find`, but returns the index instead of a value. @@ -795,7 +815,7 @@ These methods are the most used ones, they cover 99% of use cases. But there are For the full list, see the [manual](mdn:js/Array). -From the first sight it may seem that there are so many methods, quite difficult to remember. But actually that's much easier. +At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier. Look through the cheat sheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 76f74036c..e2c0d4f97 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -174,7 +174,7 @@ When we use JavaScript for practical tasks in a browser or any other environment For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`). -But an iterable may be not array-like. And vice versa an array-like may be not iterable. +But an iterable may not be array-like. And vice versa an array-like may not be iterable. For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`. 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 7b1c7d586..69897f8a2 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -10,10 +10,15 @@ Dar acest lucru nu este suficient pentru viața reală. De aceea există și `Ma ## Map +<<<<<<< HEAD [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) este o colecție de elemente de date cu chei, exact ca un `Object`. Dar diferența principală este că `Map` permite chei de orice tip. +======= +[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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Metodele și proprietățile sunt: +<<<<<<< HEAD - [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creează map. - [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stochează valoarea în funcție de cheie. - [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returnează valoarea în funcție de cheie, `undefined` dacă `key` nu există în map. @@ -21,6 +26,15 @@ Metodele și proprietățile sunt: - [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- elimină elementul (perechea cheie/valoare) în funcție de cheie. - [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- elimină totul din map. - [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returnează numărul curent de elemente. +======= +- [`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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu: @@ -100,15 +114,27 @@ map.set('1', 'str1') ``` ```` +<<<<<<< HEAD ## Iterare peste Map Pentru a parcurge un `map`, există 3 metode: +======= +## Iteration over Map +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 - [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- returnează un iterabil pentru chei, - [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- returnează un iterabil pentru valori, - [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- returnează un iterabil pentru intrările `[cheie, valoare]`, este utilizat în mod implicit în `for..of`. +<<<<<<< HEAD De exemplu: +======= +- [`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: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run let recipeMap = new Map([ @@ -161,7 +187,11 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` +<<<<<<< HEAD Dacă avem un obiect obișnuit, și dorim să creăm un `Map` din el, atunci putem folosi metoda încorporată [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) care returnează o matrice de perechi cheie/valoare pentru un obiect exact în acest 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Astfel putem crea un map dintr-un obiect în felul următor: @@ -232,16 +262,29 @@ Este același lucru, deoarece `Object.fromEntries` așteaptă ca argument un obi ## Set +<<<<<<< HEAD Un [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) este un tip special de colecție - "set de valori" (fără chei), în care fiecare valoare poate apărea o singură dată. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Principalele sale metode sunt: +<<<<<<< HEAD - [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creează setul și, dacă este furnizat un obiect `iterable` (de obicei o matrice), copiază valorile din acesta în set. - [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adaugă o valoare, returnează setul însuși. - [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- elimină valoarea, returnează `true` dacă `value` a existat la momentul apelului, altfel `false`. - [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returnează `true` dacă valoarea există în set, altfel `false`. - [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- elimină totul din set. - [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- este numărul de elemente. +======= +- [`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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Caracteristica principală este că apelurile repetate ale `set.add(value)` cu aceeași valoare nu fac nimic. Acesta este motivul pentru care fiecare valoare apare într-un `Set` doar o singură dată. @@ -271,7 +314,11 @@ for (let user of set) { } ``` +<<<<<<< HEAD Alternativa la `Set` ar putea fi o matrice de utilizatori, iar codul să verifice dacă există dubluri la fiecare inserare folosind [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). Dar performanța ar fi mult mai slabă, deoarece această metodă parcurge întreaga matrice verificând fiecare element. `Set` este mult mai bine optimizat la nivel intern pentru verificarea unicității. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Iterare peste Set @@ -290,20 +337,35 @@ set.forEach((value, valueAgain, set) => { Observați lucrul ciudat. Funcția callback transmisă în `forEach` are 3 argumente: un `value`, apoi *aceeași valoare* `valueAgain`, și apoi obiectul țintă. Într-adevăr, aceeași value apare de două ori în argumente. +<<<<<<< HEAD Acest lucru este pentru compatibilitate cu `Map` unde funcția callback transmisă către `forEach` are trei argumente. Arată un pic ciudat, cu siguranță. Dar acest lucru poate ajuta la înlocuirea cu ușurință a `Map` cu `Set` în anumite cazuri, și 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Aceleași metode pe care `Map` le are pentru iteratori sunt de asemenea suportate: +<<<<<<< HEAD - [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- returnează un obiect iterabil pentru valori, - [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- la fel ca `set.keys()`, pentru compatibilitate cu `Map`, - [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- returnează un obiect iterabil pentru intrările `[value, value]`, există pentru compatibilitate cu `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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## Sumar +<<<<<<< HEAD [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- este o colecție de valori cu cheie. +======= +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- is a collection of keyed values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Metode și proprietăți: +<<<<<<< HEAD - [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creează map-ul, cu `iterable` opțional (e.g. matrice) de perechi `[key,value]` pentru inițializare. - [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stochează valoarea în funcție de cheie, returnează map-ul însăși. - [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returnează valoarea în funcție de cheie, `undefined` dacă `key` nu există în map. @@ -311,21 +373,43 @@ Metode și proprietăți: - [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- elimină elementul în funcție de cheie, returnează `true` dacă `key` exista în momentul apelului, altfel `false`. - [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- șterge tot din map. - [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returnează numărul curent de elemente. +======= +- [`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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Diferențele față de un `Object` obișnuit: - Orice chei, obiectele pot fi chei. - Metode convenabile suplimentare, proprietatea `size`. +<<<<<<< HEAD [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- este o colecție de valori unice. +======= +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- is a collection of unique values. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Metode și proprietăți: +<<<<<<< HEAD - [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creează setul, cu un `iterable` (e.g. matrice) de valori opțional pentru inițializare. - [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adaugă o valoare (nu face nimic dacă `value` există), returnează setul însuși. - [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- elimină valoarea, returnează `true` dacă `valoarea` exista în momentul apelului, altfel `false`. - [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returnează `true` dacă valoarea există în set, altfel `false`. - [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- elimină totul din set. - [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- este numărul de elemente. +======= +- [`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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Iterarea peste `Map` și `Set` se face întotdeauna în ordinea de inserție, deci nu putem spune că aceste colecții nu sunt ordonate, dar nu putem reordona elementele sau obține direct un element după numărul său. diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md index 41e36db2c..0c52741d1 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -5,18 +5,18 @@ The two most used data structures in JavaScript are `Object` and `Array`. - Objects allow us to create a single entity that stores data items by key. - Arrays allow us to gather data items into an ordered list. -Although, when we pass those to a function, it may need not be an object/array as a whole. It may need individual pieces. +However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties. *Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient. -Destructuring also works great with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. +Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that. ## Array destructuring Here's an example of how an array is destructured into variables: ```js -// we have an array with the name and surname +// we have an array with a name and surname let arr = ["John", "Smith"] *!* @@ -40,10 +40,10 @@ alert(firstName); // John alert(surname); // Smith ``` -As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples, to better understand it. +As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better. ````smart header="\"Destructuring\" does not mean \"destructive\"." -It's called "destructuring assignment," because it "destructurizes" by copying items into variables. But the array itself is not modified. +It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified. It's just a shorter way to write: ```js @@ -65,7 +65,7 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic alert( title ); // Consul ``` -In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items is also skipped (as there are no variables for them). +In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them). ```` ````smart header="Works with any iterable on the right-side" @@ -95,9 +95,9 @@ alert(user.surname); // Smith ```` ````smart header="Looping with .entries()" -In the previous chapter we saw the [Object.entries(obj)](mdn:js/Object/entries) method. +In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method. -We can use it with destructuring to loop over keys-and-values of an object: +We can use it with destructuring to loop over the keys-and-values of an object: ```js run let user = { @@ -105,7 +105,7 @@ let user = { age: 30 }; -// loop over keys-and-values +// loop over the keys-and-values *!* for (let [key, value] of Object.entries(user)) { */!* @@ -169,7 +169,7 @@ If we'd like also to gather all that follows -- we can add one more parameter th let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*]; *!* -// rest is array of items, starting from the 3rd one +// rest is an array of items, starting from the 3rd one alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 @@ -187,7 +187,7 @@ let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Ro ### Default values -If the array is shorter than the list of variables at the left, there'll be no errors. Absent values are considered undefined: +If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined: ```js run *!* @@ -418,7 +418,7 @@ alert( title ); // Menu ## Nested destructuring -If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. +If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern on the left side of the assignment has the same structure to extract values from them: @@ -449,7 +449,7 @@ alert(item1); // Cake alert(item2); // Donut ``` -All properties of `options` object except `extra` that is absent in the left part, are assigned to corresponding variables: +All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables: ![](destructuring-complex.svg) @@ -459,9 +459,9 @@ Note that there are no variables for `size` and `items`, as we take their conten ## Smart function parameters -There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on. +There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, an item list and so on. -Here's a bad way to write such function: +Here's a bad way to write such a function: ```js function showMenu(title = "Untitled", width = 200, height = 100, items = []) { @@ -469,7 +469,7 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) { } ``` -In real-life, the problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. +In real-life, the problem is how to remember the order of arguments. Usually, IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. Like this? @@ -534,7 +534,7 @@ function({ }) ``` -Then, for an object of parameters, there will be a variable `varName` for property `incomingProperty`, with `defaultValue` by default. +Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default. Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object: @@ -561,7 +561,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw - Destructuring assignment allows for instantly mapping an object or array onto many variables. - The full object syntax: ```js - let {prop : varName = default, ...rest} = object + let {prop : varName = defaultValue, ...rest} = object ``` This means that property `prop` should go into the variable `varName` and, if no such property exists, then the `default` value should be used. @@ -571,9 +571,9 @@ In the code above, the whole arguments object is `{}` by default, so there's alw - The full array syntax: ```js - let [item1 = default, item2, ...rest] = array + let [item1 = defaultValue, item2, ...rest] = array ``` - The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`. + The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`. - It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one. diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md index 3e476c5b9..bfe7fe08c 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 @@ -13,6 +13,6 @@ Am putea de asemenea să creăm o dată dintr-un șir, în felul următor: ```js run //new Date(datastring) -let d2 = new Date("February 20, 2012 03:12:00"); +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 4167512bf..8e4e653bd 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -57,10 +57,17 @@ Pentru a crea un nou obiect `Date` apelați `new Date()` cu unul dintre următoa `new Date(an, lună, date, ore, minute, secunde, ms)` : Creează data cu componentele date în fusul orar local. Doar primele două argumente sunt obligatorii. +<<<<<<< HEAD - Anul `year` trebuie să aibă 4 cifre. Pentru compatibilitate, sunt acceptate și 2 cifre, care sunt considerate `19xx`, e.g.`98` este același lucru cu `1998` aici, dar este întotdeauna puternic încurajată folosirea a 4 cifre. - Numărătoarea "lunilor" începe cu `0` (Ian), până la `11` (Dec). - Parametrul `date` este de fapt ziua lunii, dacă nu există atunci se presupune `1`. - Dacă `ore/minute/secunde/ms` este absent, se presupune că sunt egale cu `0`. +======= + - 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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu: @@ -376,7 +383,11 @@ for (let i = 0; i < 10; i++) { ```warn header="Aveți grijă când faceți microbenchmarking" Motoarele JavaScript moderne efectuează multe optimizări. Acestea ar putea ajusta rezultatele "testelor artificiale" în comparație cu "utilizarea normală", în special atunci când efectuăm benchmark-uri pentru ceva foarte mic, cum ar fi modul în care funcționează un operator sau o funcție încorporată. Deci dacă vreți să înțelegeți în mod serios performanța, atunci vă rugăm să studiați modul în care funcționează motorul JavaScript. Și atunci probabil că nu veți avea nevoie deloc de microbenchmarks. +<<<<<<< HEAD Marele pachet de articole despre V8 poate fi găsit la . +======= +The great pack of articles about V8 can be found at . +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ``` ## Date.parse dintr-un șir diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index 25bb52fe3..133ffb353 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -405,7 +405,7 @@ To decode a JSON-string, we need another method named [JSON.parse](mdn:js/JSON/p The syntax: ```js -let value = JSON.parse(str, [reviver]); +let value = JSON.parse(str[, reviver]); ``` str 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 e9bba26f3..247ddecac 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,8 @@ P.S. Firește, formula este cea mai rapidă soluție. Ea folosește doar 3 opera Varianta cu bucle este a doua în ceea ce privește viteza. Atât în varianta recursivă cât și în varianta loop adunăm aceleași numere. Dar recursivitatea implică apeluri nested și gestionare de execution stack. Și asta necesită resurse, deci este mai lent. +<<<<<<< HEAD P.P.S. Unele motoare suportă optimizarea "tail call": dacă un apel recursiv este chiar ultimul din funcție, fără alte calcule efectuate, atunci funcția exterioară nu va trebui să reia execuția, astfel încât motorul nu trebuie să își amintească contextul de execuție. Asta elimină povara asupra memoriei. Dar dacă motorul JavaScript nu acceptă optimizarea tail call (majoritatea nu o fac), va apărea o eroare: maximum stack size exceeded, deoarece există de obicei o limitare a dimensiunii totale din stack. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index dae5daad3..96a40c63d 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -285,7 +285,11 @@ Iterativul `pow` utilizează un singur context care schimbă `i` și `result` î **Orice recursivitate poate fi rescrisă ca un loop. De obicei, varianta loop poate fi făcută mai eficientă.** +<<<<<<< HEAD ...Dar uneori rescrierea nu este trivială, în special atunci când o funcție utilizează diferite subapelări recursive în funcție de condiții și fuzionează rezultatele acestora sau când ramificarea este mai complexă. Iar optimizarea poate fi inutilă și nu merită în totalitate eforturile depuse. +======= +...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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Recursivitatea poate oferi un cod mai scurt, ușor de înțeles și de susținut. Optimizările nu sunt necesare în orice loc, de cele mai multe ori avem nevoie de un cod bun, de aceea este folosit. @@ -535,7 +539,11 @@ Termeni: list = { value, next -> list } ``` +<<<<<<< HEAD Arborii cum ar fi arborele elementelor HTML sau arborele departamentelor din acest capitol sunt de asemenea recursivi în mod natural: au ramuri și fiecare ramură poate avea alte ramuri. +======= + 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Funcțiile recursive pot fi folosite pentru a le parcurge așa cum am văzut în exemplul `sumSalary`. 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 e0eccd4ce..9b70b5819 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,7 +1,11 @@ importance: 5 --- +<<<<<<< HEAD # Funcție în if +======= +# Function in if +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Priviți codul. Care va fi rezultatul apelului de la ultima linie? diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index d531809c6..fa9de3a4e 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -7,7 +7,11 @@ JavaScript este un limbaj foarte orientat spre funcții. Acesta ne oferă o mare Dar ce se întâmplă dacă variabilele exterioare se schimbă de la crearea unei funcții? Va primi funcția valorile mai noi sau pe cele vechi? +<<<<<<< HEAD Și dacă o funcție este transmisă ca argument și apelată dintr-un alt loc din cod, va avea acces la variabilele exterioare din noul loc? +======= +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? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Haideți să ne extindem cunoștințele pentru a înțelege aceste scenarii și altele mai complexe. 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 eaf8f1853..820dab926 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -25,7 +25,11 @@ var gVar = 5; alert(window.gVar); // 5 (a devenit o proprietate a obiectului global) ``` +<<<<<<< HEAD Declarațiile de funcții au același efect (declarații folosind cuvântul cheie `function` în fluxul principal de cod, nu expresii de funcții). +======= +Function declarations have the same effect (statements with `function` keyword in the main code flow, not function expressions). +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Vă rugăm să nu vă bazați pe asta! Acest comportament există din motive de compatibilitate. Scripturile moderne folosesc [module JavaScript](info:modules) unde nu se întâmplă așa ceva. 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 177896503..92416cb23 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,11 @@ welcome(); // Bună ziua, Guest (apelul nested funcționează) Acum funcționează, deoarece numele `"func"` este local pentru funcții. Nu este preluat din exterior (și nu este vizibil acolo). Specificația garantează că va face întotdeauna referire la funcția curentă. +<<<<<<< HEAD Codul exterior are în continuare variabila `sayHi` sau `welcome`. Iar `func` este un "nume de funcție intern", modul în care funcția se poate apela singură în mod fiabil. +======= +The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to can call itself reliably. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```smart header="Nu există așa ceva pentru Function Declaration" Caracteristica "nume intern" descrisă aici este disponibilă doar pentru Function Expressions, nu și pentru Function Declarations. Pentru Function Declarations, nu există o sintaxă pentru adăugarea unui nume "intern". 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 fe545b741..c4a430131 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,11 @@ De obicei, este o funcție. Din motive istorice, poate fi trecut un șir de cod, : Întârzierea înainte de rulare, în milisecunde (1000 ms = 1 secundă), implicit 0. `arg1`, `arg2`... +<<<<<<< HEAD : Argumente pentru funcție +======= +: Arguments for the function +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu, acest cod apelează `sayHi()` după o secundă: @@ -102,7 +106,11 @@ După cum se poate observa din ieșirea `alert`, într-un browser identificatoru Din nou, nu există o specificație universală pentru aceste metode, așa că este în regulă. +<<<<<<< HEAD Pentru browsere, temporizatoarele sunt descrise în [secțiunea timers](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) din HTML Living 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ## setInterval @@ -232,7 +240,11 @@ setTimeout(function() {...}, 100); Pentru `setInterval`, funcția rămâne în memorie până când este apelată `clearInterval`. +<<<<<<< HEAD Există un efect secundar. O funcție face referire la mediul lexical extern, astfel încât, în timp ce ea trăiește, trăiesc și variabilele externe. Acestea pot ocupa mult mai multă memorie decât funcția însăși. Deci atunci când nu mai avem nevoie de funcția planificată, este mai bine să o anulăm, chiar dacă este foarte mică. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```` ## setTimeout cu Întârziere zero @@ -255,8 +267,13 @@ Prima linie "pune apelul în calendar după 0ms". Dar planificatorul va "verific Există de asemenea cazuri avansate de utilizare a timeout-ului cu întârziere zero legate de browser, pe care le vom discuta în capitolul . +<<<<<<< HEAD ````smart header="Întârzierea zero nu este de fapt zero (într-un browser)" În browser, este o limitare pentru cât de des pot rula temporizatoarele imbricate. [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) spune: "după cinci temporizatoare imbricate, intervalul este forțat să fie de cel puțin 4 milisecunde.". +======= +````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 [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.". +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Să demonstrăm ce înseamnă acest lucru cu exemplul de mai jos. Apelul `setTimeout` cuprins în el se replanifică singur cu întârziere zero. Fiecare apel reține întârzierea reală din cel precedent în matricea `times`. Cum arată întârzierile reale? Să vedem: @@ -294,9 +311,16 @@ Pentru JavaScript pe server, această limitare nu există și există alte modal Vă rugăm să rețineți că toate metodele de planificare nu *garantează* întârzierea exactă. +<<<<<<< HEAD De exemplu, temporizatorul din browser poate încetini din multe motive: - Procesorul este suprasolicitat. - Tab-ul browserului este în fundal. - Laptopul este în modul de economisire a bateriei. +======= +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 saving mode. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Toate acestea pot crește rezoluția minimă a temporizatorului (întârzierea minimă) la 300ms sau chiar 1000ms în funcție de setările de performanță la nivel de browser și de sistem de operare. 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/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index bdc693418..0a945b377 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name' Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours. ```smart header="Errors appear only in strict mode" -In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. ``` Here's the same example, but the property is created from scratch: diff --git a/1-js/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 8cb301c80..ef6c7ffeb 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -131,7 +131,6 @@ 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. diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index 6cf7aebb4..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 diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg deleted file mode 100644 index ede4e1227..000000000 --- a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg +++ /dev/null @@ -1 +0,0 @@ -eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file 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 a4ce2646c..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" @@ -370,13 +368,12 @@ In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As 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" 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 0b6982141..1112520d3 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] diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index c54d08ebb..652ed7df5 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -107,7 +107,7 @@ Here's the diagram (see the right part): That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above. -As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. +As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`. ## EventMixin diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index a928da289..bf548373a 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -632,7 +632,7 @@ For instance: The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers. -There are also web-services that provide error-logging for such cases, like or . +There are also web-services that provide error-logging for such cases, like or . They work like this: diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md index 918289319..d28b07439 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -38,7 +38,7 @@ class Error { Now let's inherit `ValidationError` from it and try it in action: -```js run untrusted +```js run *!* class ValidationError extends Error { */!* diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index a00c94b9d..bc2294190 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -77,9 +77,15 @@ function loadScript(src, *!*callback*/!*) { } ``` +<<<<<<< HEAD Evenimentul `onload` este descris în articolul , practic execută o funcție după ce scriptul este încărcat și executat. Acum dacă dorim să apelăm funcțiile noi din script, ar trebui să scrim acest lucru în callback: +======= +The `onload` event is described in the article , it basically executes a function after the script is loaded and executed. + +Now if we want to call new functions from the script, we should write that in the callback: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js loadScript('/my/script.js', function() { @@ -103,8 +109,13 @@ function loadScript(src, callback) { *!* loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { +<<<<<<< HEAD alert(`Cool, scriptul ${script.src} este încărcat`); alert( _ ); // funcție declarată în scriptul încărcat +======= + alert(`Cool, the script ${script.src} is loaded`); + alert( _ ); // _ is a function declared in the loaded script +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }); */!* ``` diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index a463a75bc..a78c24387 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -35,8 +35,13 @@ Deci, ca să rezumăm: executorul rulează automat și încearcă să îndepline Obiectul `promise` returnat de constructorul `new Promise` are următoarele proprietăți interne: +<<<<<<< HEAD - `state` — inițial `"pending"`, apoi se schimbă fie în `"fulfilled"` când este apelat `resolve` ori în `"rejected"` când este apelat `reject`. - `result` — inițial `undefined`, apoi se modifică în `value` atunci când se apelează `resolve(value)` ori `error` atunci când se apelează `reject(error)`. +======= +- `state` — initially `"pending"`, then changes to either `"fulfilled"` when `resolve` is called or `"rejected"` when `reject` is called. +- `result` — initially `undefined`, then changes to `value` when `resolve(value)` is called or `error` when `reject(error)` is called. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Deci, executorul mută în cele din urmă `promise` într-una din aceste stări: @@ -46,7 +51,7 @@ Mai târziu vom vedea cum "fanii" se pot abona la aceste modificări. Iată un exemplu de constructor de promisiuni și o funcție executor simplă cu "cod de producție" care necesită timp (prin `setTimeout`): -```js run +```js let promise = new Promise(function(resolve, reject) { // funcția rulează automat atunci când promisiunea este construită @@ -60,7 +65,11 @@ Putem observa două lucruri prin rularea codului de mai sus: 1. Executorul este apelat automat și imediat (de către `new Promise`). 2. Executorul primește două argumente: `resolve` și `reject`. Aceste funcții sunt predefinite de motorul JavaScript, deci nu trebuie să le creăm. Ar trebui să apelăm doar una dintre ele atunci când suntem gata. +<<<<<<< HEAD După o secundă de "procesare", executorul apelează `resolve("gata")` pentru a produce rezultatul. Acest lucru schimbă starea obiectului `promise`: +======= + After one second of "processing", the executor calls `resolve("done")` to produce the result. This changes the state of the `promise` object: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ![](promise-resolve-1.svg) @@ -127,9 +136,15 @@ Asta este în regulă. Avem imediat o promisiune rezolvată. Proprietățile `state` și `result` ale obiectului Promise sunt interne. Nu le putem accesa direct. Pentru aceasta putem folosi metodele `.then`/`.catch`/`.finally`. Acestea sunt descrise mai jos. ``` +<<<<<<< HEAD ## Consumatori: then, catch Un obiect Promise servește ca o legătură între executor (codul producător ori "cântărețul") și funcțiile consumatoare (fanii), care vor primi rezultatul ori eroarea. Funcțiile consumatoare pot fi înregistrate (abonate) cu ajutorul metodelor `.then` și `.catch`. +======= +## Consumers: then, catch + +A Promise object serves as a link between the executor (the "producing code" or "singer") and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using the methods `.then` and `.catch`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ### then @@ -144,9 +159,15 @@ promise.then( ); ``` +<<<<<<< HEAD Primul argument din `.then` este o funcție care rulează atunci când promisiunea este rezolvată și primește rezultatul. Al doilea argument al lui `.then` este o funcție care rulează atunci când promisiunea este respinsă și primește eroarea. +======= +The first argument of `.then` is a function that runs when the promise is resolved and receives the result. + +The second argument of `.then` is a function that runs when the promise is rejected and receives the error. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 De exemplu, iată o reacție la o promisiune rezolvată cu succes: @@ -212,10 +233,15 @@ promise.catch(alert); // afișează "Error: Uuups!" după 1 secundă Apelul `.catch(f)` este un analog complet al lui `.then(null, f)`, este doar o prescurtare. +<<<<<<< HEAD ## Curățare: finally +======= +## Cleanup: finally +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 La fel cum există o clauză `finally` într-un obișnuit `try {...} catch {...}`, există `finally` în promisiuni. +<<<<<<< HEAD Apelul `.finally(f)` este similar cu `.then(f, f)` în sensul că `f` rulează întotdeauna, atunci când promisiunea este soluționată: fie că este rezolvată sau respinsă. Ideea lui `finally` este de a stabili un gestionar pentru a efectua curățarea/finalizarea după ce operațiunile anterioare sunt finalizate. @@ -234,10 +260,31 @@ new Promise((resolve, reject) => { // rulează atunci când promisiunea este soluționată, nu contează dacă a fost cu succes sau nu .finally(() => oprește indicatorul de încărcare) // astfel încât indicatorul de încărcare să fie întotdeauna oprit înainte de a continua +======= +The call `.finally(f)` is similar to `.then(f, f)` in the sense that `f` runs always, when the promise is settled: be it resolve or reject. + +The idea of `finally` is to set up a handler for performing cleanup/finalizing after the previous operations are complete. + +E.g. stopping loading indicators, closing no longer needed connections, etc. + +Think of it as a party finisher. Irresepective of whether a party was good or bad, how many friends were in it, we still need (or at least should) do a cleanup after it. + +The code may look like this: + +```js +new Promise((resolve, reject) => { + /* do something that takes time, and then call resolve or maybe reject */ +}) +*!* + // runs when the promise is settled, doesn't matter successfully or not + .finally(() => stop loading indicator) + // so the loading indicator is always stopped before we go on +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 */!* .then(result => afișează rezultatul, err => afișează eroarea) ``` +<<<<<<< HEAD Vă rugăm să rețineți că `finally(f)` nu este chiar un alias al lui `then(f,f)`. Există diferențe importante: @@ -248,11 +295,38 @@ Există diferențe importante: 2. Un gestionar `finally` "trece" rezultatul sau eroarea către următorul gestionar adecvat. De exemplu, aici rezultatul este trecut prin `finally` către `then`: +======= +Please note that `finally(f)` isn't exactly an alias of `then(f,f)` though. + +There are important differences: + +1. A `finally` handler has no arguments. In `finally` we don't know whether the promise is successful or not. That's all right, as our task is usually to perform "general" finalizing procedures. + + Please take a look at the example above: as you can see, the `finally` handler has no arguments, and the promise outcome is handled by the next handler. +2. A `finally` handler "passes through" the result or error to the next suitable handler. + + For instance, here the result is passed through `finally` to `then`: + + ```js run + new Promise((resolve, reject) => { + setTimeout(() => resolve("value"), 2000); + }) + .finally(() => alert("Promise ready")) // triggers first + .then(result => alert(result)); // <-- .then shows "value" + ``` + + As you can see, the `value` returned by the first promise is passed through `finally` to the next `then`. + + That's very convenient, because `finally` is not meant to process a promise result. As said, it's a place to do generic cleanup, no matter what the outcome was. + + And here's an example of an error, for us to see how it's passed through `finally` to `catch`: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run new Promise((resolve, reject) => { setTimeout(() => resolve("value")), 2000); }) +<<<<<<< HEAD .finally(() => alert("Promise ready"))) // se declanșează primul .then(result => alert(rezultat)); // <-- .then arată "value" ``` @@ -289,6 +363,30 @@ Dacă o promisiune este în așteptare, gestionarii `.then/catch/finally` aștea Uneori, se poate întâmpla ca o promisiune să fie deja soluționată atunci când îi adăugăm un gestionar. Într-un astfel de caz, acești gestionari rulează imediat: +======= + .finally(() => alert("Promise ready")) // triggers first + .catch(err => alert(err)); // <-- .catch shows the error + ``` + +3. A `finally` handler also shouldn't return anything. If it does, the returned value is silently ignored. + + The only exception to this rule is when a `finally` handler throws an error. Then this error goes to the next handler, instead of any previous outcome. + +To summarize: + +- A `finally` handler doesn't get the outcome of the previous handler (it has no arguments). This outcome is passed through instead, to the next suitable handler. +- If a `finally` handler returns something, it's ignored. +- When `finally` throws an error, then the execution goes to the nearest error handler. + +These features are helpful and make things work just the right way if we use `finally` how it's supposed to be used: for generic cleanup procedures. + +````smart header="We can attach handlers to settled promises" +If a promise is pending, `.then/catch/finally` handlers wait for its outcome. + +Sometimes, it might be that a promise is already settled when we add a handler to it. + +In such case, these handlers just run immediately: +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js run // promisiunea devine soluționată imediat după creare @@ -302,11 +400,19 @@ Notați că acest lucru face ca promisiunile să fie mai puternice decât scenar Promisiunile sunt mai flexibile. Putem adăuga gestionari în orice moment: dacă rezultatul este deja acolo, acestea se execută pur și simplu. ```` +<<<<<<< HEAD ## Exemplu: loadScript [#loadscript] În continuare, să vedem mai multe exemple practice despre cum promisiunile ne pot ajuta să scriem cod asincron. Avem funcția `loadScript` pentru încărcarea unui script din capitolul anterior. +======= +## Example: loadScript [#loadscript] + +Next, let's see more practical examples of how promises can help us write asynchronous code. + +We've got the `loadScript` function for loading a script from the previous chapter. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Iată varianta bazată pe callback, doar pentru a ne reaminti de ea: diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md index 3bf58d958..7c1b290c2 100644 --- a/1-js/11-async/03-promise-chaining/article.md +++ b/1-js/11-async/03-promise-chaining/article.md @@ -72,7 +72,11 @@ promise.then(function(result) { }); ``` +<<<<<<< HEAD Ceea ce am făcut aici este doar mai mulți gestionari pentru o promisiune. Aceștia nu își transmit rezultatul unul altuia; în schimb îl procesează independent. +======= +What we did here is just adding several handlers to one promise. They don't pass the result to each other; instead they process it independently. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Iată imaginea (comparați-o cu înlănțuirea de mai sus): @@ -224,7 +228,11 @@ Această caracteristică ne permite să integrăm obiecte personalizate în lan ## Exemplu mai mare: fetch +<<<<<<< HEAD În programarea frontend, promisiunile sunt adesea folosite pentru network requests. Să vedem deci un exemplu extins despre asta. +======= +In frontend programming, promises are often used for network requests. So let's see an extended example of that. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Vom folosi metoda [fetch](info:fetch) pentru a încărca informațiile despre utilizator de pe remote server. Aceasta are o mulțime de parametri opționali acoperiți în [capitole separate](info:fetch), dar sintaxa de bază este destul de simplă: diff --git a/1-js/11-async/04-promise-error-handling/article.md b/1-js/11-async/04-promise-error-handling/article.md index 9f7159af9..c5b4206ab 100644 --- a/1-js/11-async/04-promise-error-handling/article.md +++ b/1-js/11-async/04-promise-error-handling/article.md @@ -199,6 +199,7 @@ In non-browser environments like Node.js there are other ways to track unhandled ## Summary - `.catch` handles errors in promises of all kinds: be it a `reject()` call, or an error thrown in a handler. +- `.then` also catches errors in the same manner, if given the second argument (which is the error handler). - We should place `.catch` exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones (maybe they are programming mistakes). - It's ok not to use `.catch` at all, if there's no way to recover from an error. - In any case we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments) to track unhandled errors and inform the user (and probably our server) about them, so that our app never "just dies". diff --git a/1-js/11-async/05-promise-api/article.md b/1-js/11-async/05-promise-api/article.md index 7a3b27de7..1cb2c7ed2 100644 --- a/1-js/11-async/05-promise-api/article.md +++ b/1-js/11-async/05-promise-api/article.md @@ -247,8 +247,13 @@ Promise.any([ new Promise((resolve, reject) => setTimeout(() => reject(new Error("Eroare!")), 2000)) ]).catch(error => { console.log(error.constructor.name); // AggregateError +<<<<<<< HEAD console.log(error.errors[0]); // Error: Au! console.log(error.errors[1]); // Error: Eroare! +======= + console.log(error.errors[0]); // Error: Ouch! + console.log(error.errors[1]); // Error: Error! +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 }); ``` diff --git a/1-js/11-async/06-promisify/article.md b/1-js/11-async/06-promisify/article.md index 1d81b31a6..855678e5b 100644 --- a/1-js/11-async/06-promisify/article.md +++ b/1-js/11-async/06-promisify/article.md @@ -25,7 +25,7 @@ function loadScript(src, callback) { The function loads a script with the given `src`, and then calls `callback(err)` in case of an error, or `callback(null, script)` in case of successful loading. That's a widespread agreement for using callbacks, we saw it before. -Let's promisify it. +Let's promisify it. We'll make a new function `loadScriptPromise(src)`, that does the same (loads the script), but returns a promise instead of using callbacks. @@ -124,7 +124,7 @@ For more exotic callback formats, like those without `err` at all: `callback(res There are also modules with a bit more flexible promisification functions, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). In Node.js, there's a built-in `util.promisify` function for that. ```smart -Promisification is a great approach, especially when you use `async/await` (see the next chapter), but not a total replacement for callbacks. +Promisification is a great approach, especially when you use `async/await` (covered later in the chapter ), but not a total replacement for callbacks. Remember, a promise may have only one result, but a callback may technically be called many times. diff --git a/1-js/11-async/08-async-await/04-promise-all-failure/solution.md b/1-js/11-async/08-async-await/04-promise-all-failure/solution.md new file mode 100644 index 000000000..9fda8e000 --- /dev/null +++ b/1-js/11-async/08-async-await/04-promise-all-failure/solution.md @@ -0,0 +1,113 @@ + +The root of the problem is that `Promise.all` immediately rejects when one of its promises rejects, but it do nothing to cancel the other promises. + +In our case, the second query fails, so `Promise.all` rejects, and the `try...catch` block catches this error.Meanwhile, other promises are *not affected* - they independently continue their execution. In our case, the third query throws an error of its own after a bit of time. And that error is never caught, we can see it in the console. + +The problem is especially dangerous in server-side environments, such as Node.js, when an uncaught error may cause the process to crash. + +How to fix it? + +An ideal solution would be to cancel all unfinished queries when one of them fails. This way we avoid any potential errors. + +However, the bad news is that service calls (such as `database.query`) are often implemented by a 3rd-party library which doesn't support cancellation. Then there's no way to cancel a call. + +As an alternative, we can write our own wrapper function around `Promise.all` which adds a custom `then/catch` handler to each promise to track them: results are gathered and, if an error occurs, all subsequent promises are ignored. + +```js +function customPromiseAll(promises) { + return new Promise((resolve, reject) => { + const results = []; + let resultsCount = 0; + let hasError = false; // we'll set it to true upon first error + + promises.forEach((promise, index) => { + promise + .then(result => { + if (hasError) return; // ignore the promise if already errored + results[index] = result; + resultsCount++; + if (resultsCount === promises.length) { + resolve(results); // when all results are ready - successs + } + }) + .catch(error => { + if (hasError) return; // ignore the promise if already errored + hasError = true; // wops, error! + reject(error); // fail with rejection + }); + }); + }); +} +``` + +This approach has an issue of its own - it's often undesirable to `disconnect()` when queries are still in the process. + +It may be important that all queries complete, especially if some of them make important updates. + +So we should wait until all promises are settled before going further with the execution and eventually disconnecting. + +Here's another implementation. It behaves similar to `Promise.all` - also resolves with the first error, but waits until all promises are settled. + +```js +function customPromiseAllWait(promises) { + return new Promise((resolve, reject) => { + const results = new Array(promises.length); + let settledCount = 0; + let firstError = null; + + promises.forEach((promise, index) => { + Promise.resolve(promise) + .then(result => { + results[index] = result; + }) + .catch(error => { + if (firstError === null) { + firstError = error; + } + }) + .finally(() => { + settledCount++; + if (settledCount === promises.length) { + if (firstError !== null) { + reject(firstError); + } else { + resolve(results); + } + } + }); + }); + }); +} +``` + +Now `await customPromiseAllWait(...)` will stall the execution until all queries are processed. + +This is a more reliable approach, as it guarantees a predictable execution flow. + +Lastly, if we'd like to process all errors, we can use either use `Promise.allSettled` or write a wrapper around it to gathers all errors in a single [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) object and rejects with it. + +```js +// wait for all promises to settle +// return results if no errors +// throw AggregateError with all errors if any +function allOrAggregateError(promises) { + return Promise.allSettled(promises).then(results => { + const errors = []; + const values = []; + + results.forEach((res, i) => { + if (res.status === 'fulfilled') { + values[i] = res.value; + } else { + errors.push(res.reason); + } + }); + + if (errors.length > 0) { + throw new AggregateError(errors, 'One or more promises failed'); + } + + return values; + }); +} +``` diff --git a/1-js/11-async/08-async-await/04-promise-all-failure/task.md b/1-js/11-async/08-async-await/04-promise-all-failure/task.md new file mode 100644 index 000000000..74571c43e --- /dev/null +++ b/1-js/11-async/08-async-await/04-promise-all-failure/task.md @@ -0,0 +1,79 @@ + +# Dangerous Promise.all + +`Promise.all` is a great way to parallelize multiple operations. It's especially useful when we need to make parallel requests to multiple services. + +However, there's a hidden danger. We'll see an example in this task and explore how to avoid it. + +Let's say we have a connection to a remote service, such as a database. + +There're two functions: `connect()` and `disconnect()`. + +When connected, we can send requests using `database.query(...)` - an async function which usually returns the result but also may throw an error. + +Here's a simple implementation: + +```js +let database; + +function connect() { + database = { + async query(isOk) { + if (!isOk) throw new Error('Query failed'); + } + }; +} + +function disconnect() { + database = null; +} + +// intended usage: +// connect() +// ... +// database.query(true) to emulate a successful call +// database.query(false) to emulate a failed call +// ... +// disconnect() +``` + +Now here's the problem. + +We wrote the code to connect and send 3 queries in parallel (all of them take different time, e.g. 100, 200 and 300ms), then disconnect: + +```js +// Helper function to call async function `fn` after `ms` milliseconds +function delay(fn, ms) { + return new Promise((resolve, reject) => { + setTimeout(() => fn().then(resolve, reject), ms); + }); +} + +async function run() { + connect(); + + try { + await Promise.all([ + // these 3 parallel jobs take different time: 100, 200 and 300 ms + // we use the `delay` helper to achieve this effect +*!* + delay(() => database.query(true), 100), + delay(() => database.query(false), 200), + delay(() => database.query(false), 300) +*/!* + ]); + } catch(error) { + console.log('Error handled (or was it?)'); + } + + disconnect(); +} + +run(); +``` + +Two of these queries happen to be unsuccessful, but we're smart enough to wrap the `Promise.all` call into a `try..catch` block. + +However, this doesn't help! This script actually leads to an uncaught error in console! + +Why? How to avoid it? \ No newline at end of file diff --git a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md index af2ad0eed..4355d0cfc 100644 --- a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md +++ b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md @@ -3,7 +3,7 @@ function* pseudoRandom(seed) { let value = seed; while(true) { - value = value * 16807 % 2147483647 + value = value * 16807 % 2147483647; yield value; } diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index 6568c06f1..5ad70d151 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -9,11 +9,11 @@ But eventually scripts became more and more complex, so the community invented a To name some (for historical reasons): -- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](http://requirejs.org/). -- [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.js server. +- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](https://requirejs.org/). +- [CommonJS](https://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.js server. - [UMD](https://github.com/umdjs/umd) -- one more module system, suggested as a universal one, compatible with AMD and CommonJS. -Now all these slowly become a part of history, but we still can find them in old scripts. +Now these all slowly became a part of history, but we still can find them in old scripts. The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js. So we'll study the modern JavaScript modules from now on. @@ -182,7 +182,7 @@ alert(admin.name); // Pete 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 modules will see that. +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 is actually very convenient, because it allows us to *configure* modules.** @@ -272,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: diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 10e47820f..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 @@ -326,7 +315,7 @@ Imagine, we're writing a "package": a folder with a lot of modules, with some of The file structure could be like this: ``` auth/ - index.js + index.js user.js helpers.js tests/ @@ -372,7 +361,7 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo ```js // 📁 auth/index.js -// re-export login/logout +// re-export login/logout export {login, logout} from './helpers.js'; // re-export the default export as User @@ -380,7 +369,7 @@ export {default as User} from './user.js'; ... ``` -The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. +The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. ### Re-exporting the default export @@ -399,11 +388,11 @@ 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 @@ -430,7 +419,7 @@ Import: - Importing named exports: - `import {x [as y], ...} from "module"` -- Importing the default export: +- Importing the default export: - `import x from "module"` - `import {default as x} from "module"` - Import all: diff --git a/1-js/99-js-misc/01-proxy/proxy.svg b/1-js/99-js-misc/01-proxy/proxy.svg index 157e350f4..6b2224cfd 100644 --- a/1-js/99-js-misc/01-proxy/proxy.svg +++ b/1-js/99-js-misc/01-proxy/proxy.svg @@ -1 +1 @@ -test: 5proxytargetget proxy.test5 \ No newline at end of file +test: 5proxytargetget proxy.test5 \ No newline at end of file diff --git a/1-js/99-js-misc/06-unicode/article.md b/1-js/99-js-misc/06-unicode/article.md new file mode 100644 index 000000000..4f144f824 --- /dev/null +++ b/1-js/99-js-misc/06-unicode/article.md @@ -0,0 +1,172 @@ + +# Unicode, String internals + +```warn header="Advanced knowledge" +The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters, or other rare symbols. +``` + +As we already know, JavaScript strings are based on [Unicode](https://en.wikipedia.org/wiki/Unicode): each character is represented by a byte sequence of 1-4 bytes. + +JavaScript allows us to insert a character into a string by specifying its hexadecimal Unicode code with one of these three notations: + +- `\xXX` + + `XX` must be two hexadecimal digits with a value between `00` and `FF`, then `\xXX` is the character whose Unicode code is `XX`. + + Because the `\xXX` notation supports only two hexadecimal digits, it can be used only for the first 256 Unicode characters. + + These first 256 characters include the Latin alphabet, most basic syntax characters, and some others. For example, `"\x7A"` is the same as `"z"` (Unicode `U+007A`). + + ```js run + alert( "\x7A" ); // z + alert( "\xA9" ); // ©, the copyright symbol + ``` + +- `\uXXXX` + `XXXX` must be exactly 4 hex digits with the value between `0000` and `FFFF`, then `\uXXXX` is the character whose Unicode code is `XXXX`. + + Characters with Unicode values greater than `U+FFFF` can also be represented with this notation, but in this case, we will need to use a so called surrogate pair (we will talk about surrogate pairs later in this chapter). + + ```js run + alert( "\u00A9" ); // ©, the same as \xA9, using the 4-digit hex notation + alert( "\u044F" ); // я, the Cyrillic alphabet letter + alert( "\u2191" ); // ↑, the arrow up symbol + ``` + +- `\u{X…XXXXXX}` + + `X…XXXXXX` must be a hexadecimal value of 1 to 6 bytes between `0` and `10FFFF` (the highest code point defined by Unicode). This notation allows us to easily represent all existing Unicode characters. + + ```js run + alert( "\u{20331}" ); // 佫, a rare Chinese character (long Unicode) + alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) + ``` + +## Surrogate pairs + +All frequently used characters have 2-byte codes (4 hex digits). Letters in most European languages, numbers, and the basic unified CJK ideographic sets (CJK -- from Chinese, Japanese, and Korean writing systems), have a 2-byte representation. + +Initially, JavaScript was based on UTF-16 encoding that only allowed 2 bytes per character. But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol of Unicode. + +So rare symbols that require more than 2 bytes are encoded with a pair of 2-byte characters called "a surrogate pair". + +As a side effect, the length of such symbols is `2`: + +```js run +alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X +alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY +alert( '𩷶'.length ); // 2, a rare Chinese character +``` + +That's because surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! + +We actually have a single symbol in each of the strings above, but the `length` property shows a length of `2`. + +Getting a symbol can also be tricky, because most language features treat surrogate pairs as two characters. + +For example, here we can see two odd characters in the output: + +```js run +alert( '𝒳'[0] ); // shows strange symbols... +alert( '𝒳'[1] ); // ...pieces of the surrogate pair +``` + +Pieces of a surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. + +Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. + +So the methods [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) and [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) were added in JavaScript to deal with surrogate pairs. + +They are essentially the same as [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt), but they treat surrogate pairs correctly. + +One can see the difference here: + +```js run +// charCodeAt is not surrogate-pair aware, so it gives codes for the 1st part of 𝒳: + +alert( '𝒳'.charCodeAt(0).toString(16) ); // d835 + +// codePointAt is surrogate-pair aware +alert( '𝒳'.codePointAt(0).toString(16) ); // 1d4b3, reads both parts of the surrogate pair +``` + +That said, if we take from position 1 (and that's rather incorrect here), then they both return only the 2nd part of the pair: + +```js run +alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3 +alert( '𝒳'.codePointAt(1).toString(16) ); // dcb3 +// meaningless 2nd half of the pair +``` + +You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. + +````warn header="Takeaway: splitting strings at an arbitrary point is dangerous" +We can't just split a string at an arbitrary position, e.g. take `str.slice(0, 4)` and expect it to be a valid string, e.g.: + +```js run +alert( 'hi 😂'.slice(0, 4) ); // hi [?] +``` + +Here we can see a garbage character (first half of the smile surrogate pair) in the output. + +Just be aware of it if you intend to reliably work with surrogate pairs. May not be a big problem, but at least you should understand what happens. +```` + +## Diacritical marks and normalization + +In many languages, there are symbols that are composed of the base character with a mark above/under it. + +For instance, the letter `a` can be the base character for these characters: `àáâäãåā`. + +Most common "composite" characters have their own code in the Unicode table. But not all of them, because there are too many possible combinations. + +To support arbitrary compositions, the Unicode standard allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it. + +For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. + +```js run +alert( 'S\u0307' ); // Ṡ +``` + +If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. + +For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. + +For example: + +```js run +alert( 'S\u0307\u0323' ); // Ṩ +``` + +This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. + +For instance: + +```js run +let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below +let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above + +alert( `s1: ${s1}, s2: ${s2}` ); + +alert( s1 == s2 ); // false though the characters look identical (?!) +``` + +To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. + +It is implemented by [str.normalize()](mdn:js/String/normalize). + +```js run +alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true +``` + +It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). + +```js run +alert( "S\u0307\u0323".normalize().length ); // 1 + +alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true +``` + +In reality, this is not always the case. The reason is that the symbol `Ṩ` is "common enough", so Unicode creators included it in the main table and gave it the code. + +If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](https://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/article.md b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md new file mode 100644 index 000000000..777bf703c --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/article.md @@ -0,0 +1,483 @@ + +# WeakRef and FinalizationRegistry + +```warn header="\"Hidden\" features of the language" +This article covers a very narrowly focused topic, that most developers extremely rarely encounter in practice (and may not even be aware of its existence). + +We recommend skipping this chapter if you have just started learning JavaScript. +``` + +Recalling the basic concept of the *reachability principle* from the chapter, +we can note that the JavaScript engine is guaranteed to keep values in memory that are accessible or in use. + +For example: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// let's overwrite the value of the user variable +user = null; + +// the reference is lost and the object will be deleted from memory + +``` + +Or a similar, but slightly more complicated code with two strong references: + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// copied the strong reference to the object into the admin variable +*!* +let admin = user; +*/!* + +// let's overwrite the value of the user variable +user = null; + +// the object is still reachable through the admin variable +``` +The object `{ name: "John" }` would only be deleted from memory if there were no strong references to it (if we also overwrote the value of the `admin` variable). + +In JavaScript, there is a concept called `WeakRef`, which behaves slightly differently in this case. + + +````smart header="Terms: \"Strong reference\", \"Weak reference\"" +**Strong reference** - is a reference to an object or value, that prevents them from being deleted by the garbage collector. Thereby, keeping the object or value in memory, to which it points. + +This means, that the object or value remains in memory and is not collected by the garbage collector as long, as there are active strong references to it. + +In JavaScript, ordinary references to objects are strong references. For example: + +```js +// the user variable holds a strong reference to this object +let user = { name: "John" }; +``` +**Weak reference** - is a reference to an object or value, that does *not* prevent them from being deleted by the garbage collector. +An object or value can be deleted by the garbage collector if, the only remaining references to them are weak references. +```` + +## WeakRef + + +````warn header="Note of caution" +Before we dive into it, it is worth noting that the correct use of the structures discussed in this article requires very careful thought, and they are best avoided if possible. +```` + +`WeakRef` - is an object, that contains a weak reference to another object, called `target` or `referent`. + +The peculiarity of `WeakRef` is that it does not prevent the garbage collector from deleting its referent-object. In other words, a `WeakRef` object does not keep the `referent` object alive. + +Now let's take the `user` variable as the "referent" and create a weak reference from it to the `admin` variable. +To create a weak reference, you need to use the `WeakRef` constructor, passing in the target object (the object you want a weak reference to). + +In our case — this is the `user` variable: + + +```js +// the user variable holds a strong reference to the object +let user = { name: "John" }; + +// the admin variable holds a weak reference to the object +*!* +let admin = new WeakRef(user); +*/!* + +``` + +The diagram below depicts two types of references: a strong reference using the `user` variable and a weak reference using the `admin` variable: + +![](weakref-finalizationregistry-01.svg) + +Then, at some point, we stop using the `user` variable - it gets overwritten, goes out of scope, etc., while keeping the `WeakRef` instance in the `admin` variable: + +```js +// let's overwrite the value of the user variable +user = null; +``` + +A weak reference to an object is not enough to keep it "alive". When the only remaining references to a referent-object are weak references, the garbage collector is free to destroy this object and use its memory for something else. + +However, until the object is actually destroyed, the weak reference may return it, even if there are no more strong references to this object. +That is, our object becomes a kind of "[Schrödinger's cat](https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat)" – we cannot know for sure whether it's "alive" or "dead": + +![](weakref-finalizationregistry-02.svg) + +At this point, to get the object from the `WeakRef` instance, we will use its `deref()` method. + +The `deref()` method returns the referent-object that the `WeakRef` points to, if the object is still in memory. If the object has been deleted by the garbage collector, then the `deref()` method will return `undefined`: + + +```js +let ref = admin.deref(); + +if (ref) { + // the object is still accessible: we can perform any manipulations with it +} else { + // the object has been collected by the garbage collector +} +``` + +## WeakRef use cases + +`WeakRef` is typically used to create caches or [associative arrays](https://en.wikipedia.org/wiki/Associative_array) that store resource-intensive objects. +This allows one to avoid preventing these objects from being collected by the garbage collector solely based on their presence in the cache or associative array. + +One of the primary examples - is a situation when we have numerous binary image objects (for instance, represented as `ArrayBuffer` or `Blob`), and we want to associate a name or path with each image. +Existing data structures are not quite suitable for these purposes: + +- Using `Map` to create associations between names and images, or vice versa, will keep the image objects in memory since they are present in the `Map` as keys or values. +- `WeakMap` is ineligible for this goal either: because the objects represented as `WeakMap` keys use weak references, and are not protected from deletion by the garbage collector. + +But, in this situation, we need a data structure that would use weak references in its values. + +For this purpose, we can use a `Map` collection, whose values are `WeakRef` instances referring to the large objects we need. +Consequently, we will not keep these large and unnecessary objects in memory longer than they should be. + +Otherwise, this is a way to get the image object from the cache if it is still reachable. +If it has been garbage collected, we will re-generate or re-download it again. + +This way, less memory is used in some situations. + +## Example №1: using WeakRef for caching + +Below is a code snippet that demonstrates the technique of using `WeakRef`. + +In short, we use a `Map` with string keys and `WeakRef` objects as their values. +If the `WeakRef` object has not been collected by the garbage collector, we get it from the cache. +Otherwise, we re-download it again and put it in the cache for further possible reuse: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { // (1) + const imgCache = new Map(); // (2) + + return (imgName) => { // (3) + const cachedImg = imgCache.get(imgName); // (4) + + if (cachedImg?.deref()) { // (5) + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); // (6) + imgCache.set(imgName, new WeakRef(newImg)); // (7) + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +Let's delve into the details of what happened here: +1. `weakRefCache` - is a higher-order function that takes another function, `fetchImg`, as an argument. In this example, we can neglect a detailed description of the `fetchImg` function, since it can be any logic for downloading images. +2. `imgCache` - is a cache of images, that stores cached results of the `fetchImg` function, in the form of string keys (image name) and `WeakRef` objects as their values. +3. Return an anonymous function that takes the image name as an argument. This argument will be used as a key for the cached image. +4. Trying to get the cached result from the cache, using the provided key (image name). +5. If the cache contains a value for the specified key, and the `WeakRef` object has not been deleted by the garbage collector, return the cached result. +6. If there is no entry in the cache with the requested key, or `deref()` method returns `undefined` (meaning that the `WeakRef` object has been garbage collected), the `fetchImg` function downloads the image again. +7. Put the downloaded image into the cache as a `WeakRef` object. + +Now we have a `Map` collection, where the keys - are image names as strings, and values - are `WeakRef` objects containing the images themselves. + +This technique helps to avoid allocating a large amount of memory for resource-intensive objects, that nobody uses anymore. +It also saves memory and time in case of reusing cached objects. + +Here is a visual representation of what this code looks like: + +![](weakref-finalizationregistry-03.svg) + +But, this implementation has its drawbacks: over time, `Map` will be filled with strings as keys, that point to a `WeakRef`, whose referent-object has already been garbage collected: + +![](weakref-finalizationregistry-04.svg) + +One way to handle this problem - is to periodically scavenge the cache and clear out "dead" entries. +Another way - is to use finalizers, which we will explore next. + + +## Example №2: Using WeakRef to track DOM objects + +Another use case for `WeakRef` - is tracking DOM objects. + +Let's imagine a scenario where some third-party code or library interacts with elements on our page as long as they exist in the DOM. +For example, it could be an external utility for monitoring and notifying about the system's state (commonly so-called "logger" – a program that sends informational messages called "logs"). + +Interactive example: + +[codetabs height=420 src="weakref-dom"] + +When the "Start sending messages" button is clicked, in the so-called "logs display window" (an element with the `.window__body` class), messages (logs) start to appear. + +But, as soon as this element is deleted from the DOM, the logger should stop sending messages. +To reproduce the removal of this element, just click the "Close" button in the top right corner. + +In order not to complicate our work, and not to notify third-party code every time our DOM-element is available, and when it is not, it will be enough to create a weak reference to it using `WeakRef`. + +Once the element is removed from the DOM, the logger will notice it and stop sending messages. + +Now let's take a closer look at the source code (*tab `index.js`*): + +1. Get the DOM-element of the "Start sending messages" button. +2. Get the DOM-element of the "Close" button. +3. Get the DOM-element of the logs display window using the `new WeakRef()` constructor. This way, the `windowElementRef` variable holds a weak reference to the DOM-element. +4. Add an event listener on the "Start sending messages" button, responsible for starting the logger when clicked. +5. Add an event listener on the "Close" button, responsible for closing the logs display window when clicked. +6. Use `setInterval` to start displaying a new message every second. +7. If the DOM-element of the logs display window is still accessible and kept in memory, create and send a new message. +8. If the `deref()` method returns `undefined`, it means that the DOM-element has been deleted from memory. In this case, the logger stops displaying messages and clears the timer. +9. `alert`, which will be called, after the DOM-element of the logs display window is deleted from memory (i.e. after clicking the "Close" button). **Note, that deletion from memory may not happen immediately, as it depends only on the internal mechanisms of the garbage collector.** + + We cannot control this process directly from the code. However, despite this, we still have the option to force garbage collection from the browser. + + In Google Chrome, for example, to do this, you need to open the developer tools (`key:Ctrl` + `key:Shift` + `key:J` on Windows/Linux or `key:Option` + `key:⌘` + `key:J` on macOS), go to the "Performance" tab, and click on the bin icon button – "Collect garbage": + + ![](google-chrome-developer-tools.png) + +
+ This functionality is supported in most modern browsers. After the actions are taken, the alert will trigger immediately. + +## FinalizationRegistry + +Now it is time to talk about finalizers. Before we move on, let's clarify the terminology: + +**Cleanup callback (finalizer)** - is a function that is executed, when an object, registered in the `FinalizationRegistry`, is deleted from memory by the garbage collector. + +Its purpose - is to provide the ability to perform additional operations, related to the object, after it has been finally deleted from memory. + +**Registry** (or `FinalizationRegistry`) - is a special object in JavaScript that manages the registration and unregistration of objects and their cleanup callbacks. + +This mechanism allows registering an object to track and associate a cleanup callback with it. +Essentially it is a structure that stores information about registered objects and their cleanup callbacks, and then automatically invokes those callbacks when the objects are deleted from memory. + +To create an instance of the `FinalizationRegistry`, it needs to call its constructor, which takes a single argument - the cleanup callback (finalizer). + +Syntax: + +```js +function cleanupCallback(heldValue) { + // cleanup callback code +} + +const registry = new FinalizationRegistry(cleanupCallback); +``` + +Here: + +- `cleanupCallback` - a cleanup callback that will be automatically called when a registered object is deleted from memory. +- `heldValue` - the value that is passed as an argument to the cleanup callback. If `heldValue` is an object, the registry keeps a strong reference to it. +- `registry` - an instance of `FinalizationRegistry`. + +`FinalizationRegistry` methods: + +- `register(target, heldValue [, unregisterToken])` - used to register objects in the registry. + + `target` - the object being registered for tracking. If the `target` is garbage collected, the cleanup callback will be called with `heldValue` as its argument. + + Optional `unregisterToken` – an unregistration token. It can be passed to unregister an object before the garbage collector deletes it. Typically, the `target` object is used as `unregisterToken`, which is the standard practice. +- `unregister(unregisterToken)` - the `unregister` method is used to unregister an object from the registry. It takes one argument - `unregisterToken` (the unregister token that was obtained when registering the object). + +Now let's move on to a simple example. Let's use the already-known `user` object and create an instance of `FinalizationRegistry`: + +```js +let user = { name: "John" }; + +const registry = new FinalizationRegistry((heldValue) => { + console.log(`${heldValue} has been collected by the garbage collector.`); +}); +``` + +Then, we will register the object, that requires a cleanup callback by calling the `register` method: + +```js +registry.register(user, user.name); +``` + +The registry does not keep a strong reference to the object being registered, as this would defeat its purpose. If the registry kept a strong reference, then the object would never be garbage collected. + +If the object is deleted by the garbage collector, our cleanup callback may be called at some point in the future, with the `heldValue` passed to it: + +```js +// When the user object is deleted by the garbage collector, the following message will be printed in the console: +"John has been collected by the garbage collector." +``` + +There are also situations where, even in implementations that use a cleanup callback, there is a chance that it will not be called. + +For example: +- When the program fully terminates its operation (for example, when closing a tab in a browser). +- When the `FinalizationRegistry` instance itself is no longer reachable to JavaScript code. + If the object that creates the `FinalizationRegistry` instance goes out of scope or is deleted, the cleanup callbacks registered in that registry might also not be invoked. + +## Caching with FinalizationRegistry + +Returning to our *weak* cache example, we can notice the following: +- Even though the values wrapped in the `WeakRef` have been collected by the garbage collector, there is still an issue of "memory leakage" in the form of the remaining keys, whose values have been collected by the garbage collector. + +Here is an improved caching example using `FinalizationRegistry`: + +```js +function fetchImg() { + // abstract function for downloading images... +} + +function weakRefCache(fetchImg) { + const imgCache = new Map(); + + *!* + const registry = new FinalizationRegistry((imgName) => { // (1) + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) imgCache.delete(imgName); + }); + */!* + + return (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref()) { + return cachedImg?.deref(); + } + + const newImg = fetchImg(imgName); + imgCache.set(imgName, new WeakRef(newImg)); + *!* + registry.register(newImg, imgName); // (2) + */!* + + return newImg; + }; +} + +const getCachedImg = weakRefCache(fetchImg); +``` + +1. To manage the cleanup of "dead" cache entries, when the associated `WeakRef` objects are collected by the garbage collector, we create a `FinalizationRegistry` cleanup registry. + + The important point here is, that in the cleanup callback, it should be checked, if the entry was deleted by the garbage collector and not re-added, in order not to delete a "live" entry. +2. Once the new value (image) is downloaded and put into the cache, we register it in the finalizer registry to track the `WeakRef` object. + +This implementation contains only actual or "live" key/value pairs. +In this case, each `WeakRef` object is registered in the `FinalizationRegistry`. +And after the objects are cleaned up by the garbage collector, the cleanup callback will delete all `undefined` values. + +Here is a visual representation of the updated code: + +![](weakref-finalizationregistry-05.svg) + +A key aspect of the updated implementation is that finalizers allow parallel processes to be created between the "main" program and cleanup callbacks. +In the context of JavaScript, the "main" program - is our JavaScript-code, that runs and executes in our application or web page. + +Hence, from the moment an object is marked for deletion by the garbage collector, and to the actual execution of the cleanup callback, there may be a certain time gap. +It is important to understand that during this time gap, the main program can make any changes to the object or even bring it back to memory. + +That's why, in the cleanup callback, we must check to see if an entry has been added back to the cache by the main program to avoid deleting "live" entries. +Similarly, when searching for a key in the cache, there is a chance that the value has been deleted by the garbage collector, but the cleanup callback has not been executed yet. + +Such situations require special attention if you are working with `FinalizationRegistry`. + +## Using WeakRef and FinalizationRegistry in practice + +Moving from theory to practice, imagine a real-life scenario, where a user synchronizes their photos on a mobile device with some cloud service +(such as [iCloud](https://en.wikipedia.org/wiki/ICloud) or [Google Photos](https://en.wikipedia.org/wiki/Google_Photos)), +and wants to view them from other devices. In addition to the basic functionality of viewing photos, such services offer a lot of additional features, for example: + +- Photo editing and video effects. +- Creating "memories" and albums. +- Video montage from a series of photos. +- ...and much more. + +Here, as an example, we will use a fairly primitive implementation of such a service. +The main point - is to show a possible scenario of using `WeakRef` and `FinalizationRegistry` together in real life. + +Here is what it looks like: + +![](weakref-finalizationregistry-demo-01.png) + +
+On the left side, there is a cloud library of photos (they are displayed as thumbnails). +We can select the images we need and create a collage, by clicking the "Create collage" button on the right side of the page. +Then, the resulting collage can be downloaded as an image. +

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

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

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

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

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

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

Messages:

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

+ Photo Library Collage +

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

+
+ +
+
+

Logger:

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

Logger:

'; +}; + +const downloadCollage = () => { + const date = new Date(); + const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`; + const img = canvasEl.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = fileName; + link.href = img; + link.click(); + link.remove(); +}; + +const changeLayout = ({ target }) => { + state.currentLayout = JSON.parse(target.value); +}; + +// Listeners. +selectEl.addEventListener("change", changeLayout); +createCollageBtn.addEventListener("click", createCollage); +startOverBtn.addEventListener("click", startOver); +downloadBtn.addEventListener("click", downloadCollage); diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 000000000..f0140c116 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js @@ -0,0 +1,321 @@ +const loggerContainerEl = document.querySelector(".loggerContainer"); + +export const images = [ + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + }, + { + img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60", + }, + { + img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552", + }, + { + img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b", + }, + { + img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470", + }, + { + img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318", + }, + { + img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9", + }, + { + img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3", + }, + { + img: "https://images.unsplash.com/photo-1533105079780-92b9be482077", + }, + { + img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963", + }, + { + img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7", + }, + { + img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007", + }, + { + img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd", + }, + { + img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd", + }, + { + img: "https://images.unsplash.com/photo-1518684079-3c830dcef090", + }, + { + img: "https://images.unsplash.com/photo-1505832018823-50331d70d237", + }, + { + img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1", + }, + { + img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17", + }, + { + img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af", + }, + { + img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a", + }, + { + img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1", + }, + { + img: "https://images.unsplash.com/photo-1499363536502-87642509e31b", + }, + { + img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7", + }, + { + img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5", + }, + { + img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48", + }, + { + img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6", + }, + { + img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f", + }, + { + img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053", + }, + { + img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc", + }, + { + img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d", + }, + { + img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6", + }, + { + img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169", + }, + { + img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96", + }, + { + img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f", + }, + { + img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3", + }, + { + img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af", + }, + { + img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226", + }, + { + img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45", + }, + { + img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83", + }, + { + img: "https://images.unsplash.com/photo-1545389336-cf090694435e", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4", + }, + { + img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a", + }, + { + img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40", + }, + { + img: "https://images.unsplash.com/photo-1506125840744-167167210587", + }, + { + img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b", + }, + { + img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535", + }, + { + img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7", + }, + { + img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c", + }, + { + img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7", + }, + { + img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59", + }, + { + img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f", + }, + { + img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc", + }, + { + img: "https://images.unsplash.com/photo-1510662145379-13537db782dc", + }, + { + img: "https://images.unsplash.com/photo-1573790387438-4da905039392", + }, + { + img: "https://images.unsplash.com/photo-1512757776214-26d36777b513", + }, + { + img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b", + }, + { + img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7", + }, + { + img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a", + }, +]; +export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format"; + +// Console styles. +export const CONSOLE_BASE_STYLES = [ + "font-size: 12px", + "padding: 4px", + "border: 2px solid #5a5a5a", + "color: white", +].join(";"); +export const CONSOLE_PRIMARY = [ + CONSOLE_BASE_STYLES, + "background-color: #13315a", +].join(";"); +export const CONSOLE_SUCCESS = [ + CONSOLE_BASE_STYLES, + "background-color: #385a4e", +].join(";"); +export const CONSOLE_ERROR = [ + CONSOLE_BASE_STYLES, + "background-color: #5a1a24", +].join(";"); + +// Layouts. +export const LAYOUT_4_COLUMNS = { + name: "Layout 4 columns", + columns: 4, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUT_8_COLUMNS = { + name: "Layout 8 columns", + columns: 8, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS]; + +export const createImageFile = async (src) => + new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to construct image.")); + }); + +export const loadImage = async (url) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(String(response.status)); + } + + return await response.blob(); + } catch (e) { + console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR); + } +}; + +export const weakRefCache = (fetchImg) => { + const imgCache = new Map(); + const registry = new FinalizationRegistry(({ imgName, size, type }) => { + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) { + imgCache.delete(imgName); + console.log( + `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`, + CONSOLE_ERROR, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--error"); + logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + } + }); + + return async (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref() !== undefined) { + console.log( + `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`, + CONSOLE_SUCCESS, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--success"); + logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + return cachedImg?.deref(); + } + + const newImg = await fetchImg(imgName); + console.log( + `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`, + CONSOLE_PRIMARY, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--primary"); + logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + imgCache.set(imgName, new WeakRef(newImg)); + registry.register(newImg, { + imgName, + size: newImg.size, + type: newImg.type, + }); + + return newImg; + }; +}; + +export const stateObj = { + loading: false, + drawing: true, + collageRendered: false, + currentLayout: LAYOUTS[0], + selectedImages: new Set(), +}; diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index 43dec976a..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,7 +15,7 @@ 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 global function sayHi() { @@ -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 e18335f38..f7f2be91d 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -212,7 +212,7 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu ## See it for yourself -To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.svg b/2-ui/1-document/02-dom-nodes/domconsole0.svg index ea0d9141c..eb99f193f 100644 --- a/2-ui/1-document/02-dom-nodes/domconsole0.svg +++ b/2-ui/1-document/02-dom-nodes/domconsole0.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.svg b/2-ui/1-document/02-dom-nodes/domconsole1.svg index d7f32debb..02ef5f0a6 100644 --- a/2-ui/1-document/02-dom-nodes/domconsole1.svg +++ b/2-ui/1-document/02-dom-nodes/domconsole1.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/elk.svg b/2-ui/1-document/02-dom-nodes/elk.svg index 1797a099f..448eea9d1 100644 --- a/2-ui/1-document/02-dom-nodes/elk.svg +++ b/2-ui/1-document/02-dom-nodes/elk.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/inspect.svg b/2-ui/1-document/02-dom-nodes/inspect.svg index a894a5c0e..60696ec0d 100644 --- a/2-ui/1-document/02-dom-nodes/inspect.svg +++ b/2-ui/1-document/02-dom-nodes/inspect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file 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 5af6435ce..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. @@ -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. @@ -154,7 +154,7 @@ For instance:
  • Chapter 1
  • -
  • Chapter 1
  • +
  • Chapter 2
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 b1d6486f4..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,10 +18,31 @@ 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, @@ -29,7 +50,7 @@ The classes are: 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. @@ -133,10 +154,10 @@ For instance: ``` -But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back: +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: ```html run diff --git a/2-ui/1-document/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/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/4-put-ball-in-center/field.svg b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg index 4ae90b1c7..f5bd9f4f9 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file 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 e4433d912..71bdf2097 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; } ``` @@ -110,7 +110,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: diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.svg b/2-ui/1-document/09-size-and-scroll/metric-all.svg index a5dadb47f..20a59e18d 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-all.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-all.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg index dd9e17cf8..e8dd3d60a 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg @@ -1 +1 @@ -clientTop: 25px = borderclientLeft: 41px \ No newline at end of file +clientTop: 25px = borderclientLeft: 41px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg index 968590466..8097afa78 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.clientTop: 25px = borderclientLeft: 25px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.clientTop: 25px = borderclientLeft: 25px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg index 83864b4c5..2603b05fb 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-css.svg b/2-ui/1-document/09-size-and-scroll/metric-css.svg index 13aa62afd..1f2e5f780 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-css.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-css.svg @@ -1 +1 @@ -padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg index 9e247639b..2d108473e 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg @@ -1 +1 @@ -offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file +offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg index 49bdccda7..4d30d90cc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg index c6d14d0f3..7f72de422 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg index 0c3d29952..75a24e3bc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index 35be678c0..495922590 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: -

+

``` @@ -265,7 +265,7 @@ Here's a document with ` - + diff --git a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html index a4685a716..27df70939 100644 --- a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html +++ b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html @@ -9,8 +9,8 @@ [20] readyState:interactive [21] DOMContentLoaded [30] iframe onload - [40] readyState:complete [40] img onload + [40] readyState:complete [40] window onload --> diff --git a/2-ui/99-ui-misc/02-selection-range/article.md b/2-ui/99-ui-misc/02-selection-range/article.md index c5ab3fbdd..09a20bc67 100644 --- a/2-ui/99-ui-misc/02-selection-range/article.md +++ b/2-ui/99-ui-misc/02-selection-range/article.md @@ -217,7 +217,7 @@ The range object that we created in the example above has following properties: ## Range selection methods -There are many convenience methods to manipulate ranges. +There are many convenient methods to manipulate ranges. We've already seen `setStart` and `setEnd`, here are other similar methods. @@ -354,7 +354,7 @@ The main selection properties are: ```smart header="Selection end/start vs Range" -There's an important differences of a selection anchor/focus compared with a `Range` start/end. +There's an important difference between a selection anchor/focus compared with a `Range` start/end. As we know, `Range` objects always have their start before the end. @@ -408,7 +408,7 @@ From – To There are two approaches to copying the selected content: 1. We can use `document.getSelection().toString()` to get it as text. -2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangesAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. +2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangeAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. Here's the demo of copying the selected content both as text and as DOM nodes: @@ -438,7 +438,7 @@ As text: ## Selection methods -We can work with the selection by addding/removing 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. - `addRange(range)` -- add `range` to selection. All browsers except Firefox ignore the call, if the selection already has an associated range. diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg index 9ebcffaac..a97d1b47a 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-0-1.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg index 088c71c20..2a8f9aca3 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-1-3.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg index f13c6d74a..32843436d 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3-range.svg @@ -1 +1 @@ -startContainer (<p>.firstChild)startOffset (=2)commonAncestorContainer (<p>)endContainer (<b>.firstChild)endOffset (=3) \ No newline at end of file +startContainer (<p>.firstChild)startOffset (=2)commonAncestorContainer (<p>)endContainer (<b>.firstChild)endOffset (=3) \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg index 4bf5b00b0..859f755ce 100644 --- a/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg +++ b/2-ui/99-ui-misc/02-selection-range/range-example-p-2-b-3.svg @@ -1 +1 @@ -0123 \ No newline at end of file +0123 \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg b/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg index 6399f9d5e..85615d38f 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-direction-backward.svg @@ -1 +1 @@ -focusanchormouse move direction \ No newline at end of file +focusanchormouse move direction \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg b/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg index 03c6fc5c6..511b00a26 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-direction-forward.svg @@ -1 +1 @@ -anchorfocusmouse move direction \ No newline at end of file +anchorfocusmouse move direction \ No newline at end of file diff --git a/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg b/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg index 050852d3d..aa7ff1eb7 100644 --- a/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg +++ b/2-ui/99-ui-misc/02-selection-range/selection-firefox.svg @@ -1 +1 @@ -selection \ No newline at end of file +selection \ 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 index f32161e22..2a5a59ca1 100644 --- a/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md +++ b/2-ui/99-ui-misc/03-event-loop/2-micro-macro-queue/solution.md @@ -1,3 +1,4 @@ +<<<<<<< HEAD Ieșirea din consolă este: 1 7 3 5 2 6 4. Sarcina este destul de simplă, trebuie doar să știm cum funcționează microtask și macrotask queues. @@ -47,4 +48,56 @@ console.log(7); - Macrotask queue este acum: `console.log(2); console.log(6); console.log(4)`. 3. După ce microtask queue devine goală, macrotask queue se execută. Aceasta scoate `2`, `6`, `4`. -În cele din urmă, avem ieșirea: `1 7 3 5 2 6 4`. \ No newline at end of file +În cele din urmă, avem ieșirea: `1 7 3 5 2 6 4`. +======= +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`. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 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 index 013f37b07..db0099ade 100644 --- 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 @@ -2,7 +2,11 @@ importance: 5 --- +<<<<<<< HEAD # Care va fi ieșirea acestui cod? +======= +# What will be the output of this code? +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 ```js console.log(1); 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 f863a59f7..e385465e8 100644 --- a/2-ui/99-ui-misc/03-event-loop/article.md +++ b/2-ui/99-ui-misc/03-event-loop/article.md @@ -17,7 +17,11 @@ Algoritmul general al motorului: - le execută, începând cu cea mai veche sarcină. 2. Doarme până când apare o sarcină, apoi treceți la 1. +<<<<<<< HEAD Aceasta este o formalizare pentru ceea ce vedem atunci când navigăm pe o pagină. Motorul JavaScript nu face nimic în cea mai mare parte a timpului, se execută doar dacă se activează un script/gestionar/eveniment. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Exemple de sarcini: @@ -30,6 +34,7 @@ Sarcinile sunt setate -- motorul le gestionează -- apoi așteaptă mai multe sa Se poate întâmpla ca o sarcină să vină în timp ce motorul este ocupat, atunci este pusă la coadă. +<<<<<<< HEAD Sarcinile formează o coadă, așa-numita "macrotask queue" (termen v8): ![](eventLoop.svg) @@ -37,12 +42,27 @@ Sarcinile formează o coadă, așa-numita "macrotask queue" (termen v8): De exemplu, în timp ce motorul este ocupat cu execuția unui `script`, un utilizator poate muta mouse-ul provocând `mousemove`, iar `setTimeout` poate fi scadent și așa mai departe, aceste sarcini formează o coadă, așa cum este ilustrat în imaginea de mai sus. Sarcinile din coadă sunt procesate în baza "primul venit -- primul servit". Atunci când motorul browserului a terminat cu `script`, acesta se ocupă de evenimentul `mousemove`, apoi gestionarul `setTimeout`, și așa mai departe. +======= +The tasks form a queue, the so-called "macrotask queue" ([v8](https://v8.dev/) term): + +![](eventLoop.svg) + +For instance, while the engine is busy executing a `script`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, these tasks form a queue, as illustrated in the picture above. + +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Până acum, destul de simplu, nu? +<<<<<<< HEAD Încă două detalii: 1. Randarea nu are loc niciodată în timp ce motorul execută o sarcină. Nu contează dacă sarcina durează mult timp. Modificările aduse la DOM sunt pictate numai după ce sarcina este finalizată. 2. Dacă o sarcină durează prea mult, browserul nu poate efectua alte sarcini, cum ar fi procesarea evenimentelor utilizatorului. Deci după un timp, acesta lansează o alertă ca "Page Unresponsive", sugerând uciderea sarcinii împreună cu întreaga pagină. Asta se întâmplă atunci când au loc o mulțime de calcule complexe sau când o eroare de programare duce la o buclă infinită. +======= +Two more details: +1. Rendering never happens while the engine executes a task. It doesn't matter if the task takes a long time. Changes to the DOM are painted only after the task is complete. +2. If a task takes too long, the browser can't do other tasks, such as processing user events. So after 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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Aceasta a fost teoria. Acum să vedem cum putem aplica aceste cunoștințe. @@ -54,7 +74,11 @@ De exemplu, evidențierea sintaxei (utilizată pentru a colora exemplele de cod În timp ce motorul este ocupat cu evidențierea sintaxei, nu poate face alte lucruri legate de DOM, procesa evenimentele utilizatorului etc. Poate chiar să provoace browserul să "sughițe" sau chiar să se "blocheze" pentru un timp, ceea ce este inacceptabil. +<<<<<<< HEAD Putem evita problemele împărțind sarcina mare în bucăți. Evidențiază primele 100 de rânduri, apoi programa `setTimeout` (cu întârziere zero) pentru următoarele 100 de rânduri, și așa mai departe. +======= +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. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Pentru a demonstra această abordare, de dragul simplității, în loc de evidențierea textului, să luăm o funcție care numără de la `1` la `1000000000`. diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index d32715f87..f2c87d1e0 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -38,26 +38,6 @@ button.onclick = () => { This way users are somewhat protected from unwanted popups, but the functionality is not disabled totally. -What if the popup opens from `onclick`, but after `setTimeout`? That's a bit tricky. - -Try this code: - -```js run -// open after 3 seconds -setTimeout(() => window.open('http://google.com'), 3000); -``` - -The popup opens in Chrome, but gets blocked in Firefox. - -...If we decrease the delay, the popup works in Firefox too: - -```js run -// open after 1 seconds -setTimeout(() => window.open('http://google.com'), 1000); -``` - -The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it -- removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not. - ## window.open The syntax to open a popup is: `window.open(url, name, params)`: @@ -87,7 +67,7 @@ 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: @@ -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: @@ -239,7 +219,7 @@ There's also `window.onscroll` event. 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. +Although, in practice they are severely limited, because in the past evil pages abused them. For instance, look at this code: @@ -257,10 +237,10 @@ 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 0aa9abfe7..4d4e320e4 100644 --- a/3-frames-and-windows/03-cross-window-communication/article.md +++ b/3-frames-and-windows/03-cross-window-communication/article.md @@ -275,7 +275,7 @@ Arguments: `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 95c18dda9..2827e277e 100644 --- a/4-binary/01-arraybuffer-binary-arrays/article.md +++ b/4-binary/01-arraybuffer-binary-arrays/article.md @@ -30,7 +30,7 @@ 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: @@ -71,7 +71,7 @@ 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. diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index e3bd32b38..fc0150577 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -101,7 +101,7 @@ For each URL generated by `URL.createObjectURL` the browser stores a URL -> `Blo 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. @@ -237,8 +237,8 @@ const readableStream = blob.stream(); const stream = readableStream.getReader(); while (true) { - // for each iteration: data is the next blob fragment - let { done, data } = await stream.read(); + // 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.'); @@ -246,7 +246,7 @@ while (true) { } // do something with the data portion we've just read from the blob - console.log(data); + console.log(value); } ``` diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index 51f31232d..b2ea3c85e 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -192,12 +192,21 @@ Aceste anteturi asigură un protocol HTTP corect și sigur, așa că sunt contro Pentru a face un `POST` request, sau o cerere cu o altă metodă, trebuie să folosim opțiunile `fetch`: +<<<<<<< HEAD - **`method`** -- Metoda HTTP e.g. `POST`, - **`body`** -- request body, unul dintre: - un șir (e.g. codificat JSON), - obiect `FormData`, pentru a trimite datele ca `multipart/form-data`, - `Blob`/`BufferSource` pentru a trimite date binare, - [URLSearchParams](info:url), pentru a trimite datele în codificare `x-www-form-urlencoded`, rar utilizat. +======= +- **`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 `multipart/form-data`, + - `Blob`/`BufferSource` to send binary data, + - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Formatul JSON este utilizat de cele mai multe ori. @@ -296,6 +305,7 @@ fetch(url, options) .then(result => /* procesează result */) ``` +<<<<<<< HEAD Proprietăți de răspuns: - `response.status` -- Codul HTTP al răspunsului, - `response.ok` -- `true` dacă status este 200-299. @@ -307,6 +317,19 @@ Metode de obținere response body: - **`response.formData()`** -- returnează răspunsul ca obiect `FormData` (codificare `multipart/form-data`, vezi capitolul următor), - **`response.blob()`** -- returnează răspunsul ca [Blob](info:blob) (date binare cu tip), - **`response.arrayBuffer()`** -- returnează răspunsul ca [ArrayBuffer](info:arraybuffer-binary-arrays) (date binare low-level), +======= +Response properties: +- `response.status` -- HTTP code of the response, +- `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 (`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), +>>>>>>> 035c5267ba80fa7b55878f7213cbde449b4092d9 Opțiunile fetch de până acum: - `method` -- metodă HTTP, diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md index af66c37cc..eadc5aac2 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -24,7 +24,7 @@ When `abort()` is called: - `controller.signal` emits the `"abort"` event. - `controller.signal.aborted` property becomes `true`. -Generally, we have two parties in the process: +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. @@ -34,7 +34,7 @@ Here's the full example (without `fetch` yet): let controller = new AbortController(); let signal = controller.signal; -// The party that performs a cancelable operation +// 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!")); @@ -143,6 +143,6 @@ let results = await Promise.all([...fetchJobs, ourJob]); ## Summary -- `AbortController` is a simple object that generates an `abort` event on it's `signal` property when the `abort()` method is called (and also sets `signal.aborted` to `true`). +- `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 d45ee391d..4420f43c7 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -44,7 +44,7 @@ One way to communicate with another server was to submit a `
` there. Peopl */!* - + *!* */!* @@ -169,6 +169,7 @@ For cross-origin request, by default JavaScript may only access so-called "safe" - `Cache-Control` - `Content-Language` +- `Content-Length` - `Content-Type` - `Expires` - `Last-Modified` @@ -176,12 +177,6 @@ For cross-origin request, by default JavaScript may only access so-called "safe" Accessing any other response header causes an error. -```smart -There's no `Content-Length` header in the list! - -This header contains the full response length. So, if we're downloading something and would like to track the percentage of progress, then an additional permission is required to access that header (see below). -``` - To grant JavaScript access to any other response header, the server must send the `Access-Control-Expose-Headers` header. It contains a comma-separated list of unsafe header names that should be made accessible. For example: @@ -190,14 +185,15 @@ For example: 200 OK Content-Type:text/html; charset=UTF-8 Content-Length: 12345 +Content-Encoding: gzip API-Key: 2c9de507f2c54aa1 Access-Control-Allow-Origin: https://javascript.info *!* -Access-Control-Expose-Headers: Content-Length,API-Key +Access-Control-Expose-Headers: Content-Encoding,API-Key */!* ``` -With such an `Access-Control-Expose-Headers` header, the script is allowed to read the `Content-Length` and `API-Key` headers of the response. +With such an `Access-Control-Expose-Headers` header, the script is allowed to read the `Content-Encoding` and `API-Key` headers of the response. ## "Unsafe" requests diff --git a/5-network/06-fetch-api/article.md b/5-network/06-fetch-api/article.md index e7c3fbe61..5f55c78ef 100644 --- a/5-network/06-fetch-api/article.md +++ b/5-network/06-fetch-api/article.md @@ -21,10 +21,10 @@ let promise = fetch(url, { // depending on the request body "Content-Type": "text/plain;charset=UTF-8" }, - body: undefined // string, FormData, Blob, BufferSource, or URLSearchParams + body: undefined, // string, FormData, Blob, BufferSource, or URLSearchParams referrer: "about:client", // or "" to send no Referer header, // or an url from the current origin - referrerPolicy: "no-referrer-when-downgrade", // no-referrer, origin, same-origin... + referrerPolicy: "strict-origin-when-cross-origin", // no-referrer-when-downgrade, no-referrer, origin, same-origin... mode: "cors", // same-origin, no-cors credentials: "same-origin", // omit, include cache: "default", // no-store, reload, no-cache, force-cache, or only-if-cached @@ -52,7 +52,7 @@ Usually that header is set automatically and contains the url of the page that m **The `referrer` option allows to set any `Referer` (within the current origin) or remove it.** -To send no referer, set an empty string: +To send no referrer, set an empty string: ```js fetch('/page', { *!* @@ -85,13 +85,13 @@ Unlike the `referrer` option that allows to set the exact `Referer` value, `refe Possible values are described in the [Referrer Policy specification](https://w3c.github.io/webappsec-referrer-policy/): -- **`"no-referrer-when-downgrade"`** -- the default value: full `Referer` is always sent, unless we send a request from HTTPS to HTTP (to the less secure protocol). +- **`"strict-origin-when-cross-origin"`** -- the default value: for same-origin send the full `Referer`, for cross-origin send only the origin, unless it's HTTPS→HTTP request, then send nothing. +- **`"no-referrer-when-downgrade"`** -- full `Referer` is always sent, unless we send a request from HTTPS to HTTP (to the less secure protocol). - **`"no-referrer"`** -- never send `Referer`. - **`"origin"`** -- only send the origin in `Referer`, not the full page URL, e.g. only `http://site.com` instead of `http://site.com/path`. - **`"origin-when-cross-origin"`** -- send the full `Referer` to the same origin, but only the origin part for cross-origin requests (as above). - **`"same-origin"`** -- send the full `Referer` to the same origin, but no `Referer` for cross-origin requests. - **`"strict-origin"`** -- send only the origin, not the `Referer` for HTTPS→HTTP requests. -- **`"strict-origin-when-cross-origin"`** -- for same-origin send the full `Referer`, for cross-origin send only the origin, unless it's HTTPS→HTTP request, then send nothing. - **`"unsafe-url"`** -- always send the full url in `Referer`, even for HTTPS→HTTP requests. Here's a table with all combinations: @@ -99,12 +99,12 @@ Here's a table with all combinations: | Value | To same origin | To another origin | HTTPS→HTTP | |-------|----------------|-------------------|------------| | `"no-referrer"` | - | - | - | -| `"no-referrer-when-downgrade"` or `""` (default) | full | full | - | +| `"no-referrer-when-downgrade"` | full | full | - | | `"origin"` | origin | origin | origin | | `"origin-when-cross-origin"` | full | origin | origin | | `"same-origin"` | full | - | - | | `"strict-origin"` | origin | origin | - | -| `"strict-origin-when-cross-origin"` | full | origin | - | +| `"strict-origin-when-cross-origin"` or `""` (default) | full | origin | - | | `"unsafe-url"` | full | full | full | Let's say we have an admin zone with a URL structure that shouldn't be known from outside of the site. @@ -179,7 +179,7 @@ The `integrity` option allows to check if the response matches the known-ahead c As described in the [specification](https://w3c.github.io/webappsec-subresource-integrity/), supported hash-functions are SHA-256, SHA-384, and SHA-512, there might be others depending on the browser. -For example, we're downloading a file, and we know that it's SHA-256 checksum is "abcdef" (a real checksum is longer, of course). +For example, we're downloading a file, and we know that its SHA-256 checksum is "abcdef" (a real checksum is longer, of course). We can put it in the `integrity` option, like this: diff --git a/5-network/08-xmlhttprequest/article.md b/5-network/08-xmlhttprequest/article.md index 7dbc405a0..43d816cab 100644 --- a/5-network/08-xmlhttprequest/article.md +++ b/5-network/08-xmlhttprequest/article.md @@ -2,7 +2,7 @@ `XMLHttpRequest` is a built-in browser object that allows to make HTTP requests in JavaScript. -Despite of having the word "XML" in its name, it can operate on any data, not only in XML format. We can upload/download files, track progress and much more. +Despite having the word "XML" in its name, it can operate on any data, not only in XML format. We can upload/download files, track progress and much more. Right now, there's another, more modern method `fetch`, that somewhat deprecates `XMLHttpRequest`. diff --git a/5-network/09-resume-upload/article.md b/5-network/09-resume-upload/article.md index 7eedc3fbd..b0aa447d6 100644 --- a/5-network/09-resume-upload/article.md +++ b/5-network/09-resume-upload/article.md @@ -48,7 +48,7 @@ To resume upload, we need to know *exactly* the number of bytes received by the 3. Then, we can use `Blob` method `slice` to send the file from `startByte`: ```js - xhr.open("POST", "upload", true); + xhr.open("POST", "upload"); // File id, so that the server knows which file we upload xhr.setRequestHeader('X-File-Id', fileId); diff --git a/5-network/10-long-polling/article.md b/5-network/10-long-polling/article.md index be367cee7..e9d8abe39 100644 --- a/5-network/10-long-polling/article.md +++ b/5-network/10-long-polling/article.md @@ -1,6 +1,6 @@ # Long polling -Long polling is the simplest way of having persistent connection with server, that doesn't use any specific protocol like WebSocket or Server Side Events. +Long polling is the simplest way of having persistent connection with server, that doesn't use any specific protocol like WebSocket or Server Sent Events. Being very easy to implement, it's also good enough in a lot of cases. @@ -29,7 +29,7 @@ The flow: 3. When a message appears - the server responds to the request with it. 4. The browser makes a new request immediately. -The situation when the browser sent a request and has a pending connection with the server, is standard for this method. Only when a message is delivered, the connection is reestablished. +This situation, where the browser has sent a request and keeps a pending connection with the server, is standard for this method. Only when a message is delivered, the connection is closed and reestablished. ![](long-polling.svg) diff --git a/5-network/11-websocket/article.md b/5-network/11-websocket/article.md index f7e3b096c..268b674f0 100644 --- a/5-network/11-websocket/article.md +++ b/5-network/11-websocket/article.md @@ -1,6 +1,6 @@ # WebSocket -The `WebSocket` protocol, described in the specification [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455) provides a way to exchange data between browser and server via a persistent connection. The data can be passed in both directions as "packets", without breaking the connection and additional HTTP-requests. +The `WebSocket` protocol, described in the specification [RFC 6455](https://datatracker.ietf.org/doc/html/rfc6455), provides a way to exchange data between browser and server via a persistent connection. The data can be passed in both directions as "packets", without breaking the connection and the need of additional HTTP-requests. WebSocket is especially great for services that require continuous data exchange, e.g. online games, real-time trading systems and so on. @@ -19,7 +19,7 @@ The `wss://` protocol is not only encrypted, but also more reliable. That's because `ws://` data is not encrypted, visible for any intermediary. Old proxy servers do not know about WebSocket, they may see "strange" headers and abort the connection. -On the other hand, `wss://` is WebSocket over TLS, (same as HTTPS is HTTP over TLS), the transport security layer encrypts the data at sender and decrypts at the receiver. So data packets are passed encrypted through proxies. They can't see what's inside and let them through. +On the other hand, `wss://` is WebSocket over TLS, (same as HTTPS is HTTP over TLS), the transport security layer encrypts the data at the sender and decrypts it at the receiver. So data packets are passed encrypted through proxies. They can't see what's inside and let them through. ``` Once the socket is created, we should listen to events on it. There are totally 4 events: @@ -56,7 +56,7 @@ socket.onclose = function(event) { }; socket.onerror = function(error) { - alert(`[error] ${error.message}`); + alert(`[error]`); }; ``` @@ -72,11 +72,11 @@ Now let's talk more in-depth. When `new WebSocket(url)` is created, it starts connecting immediately. -During the connection the browser (using headers) asks the server: "Do you support Websocket?" And if the server replies "yes", then the talk continues in WebSocket protocol, which is not HTTP at all. +During the connection, the browser (using headers) asks the server: "Do you support Websocket?" And if the server replies "yes", then the talk continues in WebSocket protocol, which is not HTTP at all. ![](websocket-handshake.svg) -Here's an example of browser headers for request made by `new WebSocket("wss://javascript.info/chat")`. +Here's an example of browser headers for a request made by `new WebSocket("wss://javascript.info/chat")`. ``` GET /chat @@ -88,10 +88,10 @@ Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q== Sec-WebSocket-Version: 13 ``` -- `Origin` -- the origin of the client page, e.g. `https://javascript.info`. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compatibility issues. But `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. +- `Origin` -- the origin of the client page, e.g. `https://javascript.info`. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compatibility issues. But the `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. - `Connection: Upgrade` -- signals that the client would like to change the protocol. - `Upgrade: websocket` -- the requested protocol is "websocket". -- `Sec-WebSocket-Key` -- a random browser-generated key for security. +- `Sec-WebSocket-Key` -- a random browser-generated key, used to ensure that the server supports WebSocket protocol. It's random to prevent proxies from caching any following communication. - `Sec-WebSocket-Version` -- WebSocket protocol version, 13 is the current one. ```smart header="WebSocket handshake can't be emulated" @@ -107,9 +107,9 @@ Connection: Upgrade Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g= ``` -Here `Sec-WebSocket-Accept` is `Sec-WebSocket-Key`, recoded using a special algorithm. The browser uses it to make sure that the response corresponds to the request. +Here `Sec-WebSocket-Accept` is `Sec-WebSocket-Key`, recoded using a special algorithm. Upon seeing it, the browser understands that the server really does support the WebSocket protocol. -Afterwards, the data is transfered using WebSocket protocol, we'll see its structure ("frames") soon. And that's not HTTP at all. +Afterwards, the data is transferred using the WebSocket protocol, we'll see its structure ("frames") soon. And that's not HTTP at all. ### Extensions and subprotocols @@ -117,9 +117,9 @@ There may be additional headers `Sec-WebSocket-Extensions` and `Sec-WebSocket-Pr For instance: -- `Sec-WebSocket-Extensions: deflate-frame` means that the browser supports data compression. An extension is something related to transferring the data, functionality that extends WebSocket protocol. The header `Sec-WebSocket-Extensions` is sent automatically by the browser, with the list of all extensions it supports. +- `Sec-WebSocket-Extensions: deflate-frame` means that the browser supports data compression. An extension is something related to transferring the data, functionality that extends the WebSocket protocol. The header `Sec-WebSocket-Extensions` is sent automatically by the browser, with the list of all extensions it supports. -- `Sec-WebSocket-Protocol: soap, wamp` means that we'd like to transfer not just any data, but the data in [SOAP](http://en.wikipedia.org/wiki/SOAP) or WAMP ("The WebSocket Application Messaging Protocol") protocols. WebSocket subprotocols are registered in the [IANA catalogue](http://www.iana.org/assignments/websocket/websocket.xml). So, this header describes data formats that we're going to use. +- `Sec-WebSocket-Protocol: soap, wamp` means that we'd like to transfer not just any data, but the data in [SOAP](https://en.wikipedia.org/wiki/SOAP) or WAMP ("The WebSocket Application Messaging Protocol") protocols. WebSocket subprotocols are registered in the [IANA catalogue](https://www.iana.org/assignments/websocket/websocket.xml). So, this header describes the data formats that we're going to use. This optional header is set using the second parameter of `new WebSocket`. That's the array of subprotocols, e.g. if we'd like to use SOAP or WAMP: @@ -173,7 +173,7 @@ In the browser, we directly work only with text or binary frames. **WebSocket `.send()` method can send either text or binary data.** -A call `socket.send(body)` allows `body` in string or a binary format, including `Blob`, `ArrayBuffer`, etc. No settings required: just send it out in any format. +A call `socket.send(body)` allows `body` in string or a binary format, including `Blob`, `ArrayBuffer`, etc. No settings are required: just send it out in any format. **When we receive the data, text always comes as string. And for binary data, we can choose between `Blob` and `ArrayBuffer` formats.** @@ -221,7 +221,7 @@ socket.close([code], [reason]); - `code` is a special WebSocket closing code (optional) - `reason` is a string that describes the reason of closing (optional) -Then the other party in `close` event handler gets the code and the reason, e.g.: +Then the other party in the `close` event handler gets the code and the reason, e.g.: ```js // closing party: @@ -249,7 +249,7 @@ There are other codes like: The full list can be found in [RFC6455, §7.4.1](https://tools.ietf.org/html/rfc6455#section-7.4.1). -WebSocket codes are somewhat like HTTP codes, but different. In particular, any codes less than `1000` are reserved, there'll be an error if we try to set such a code. +WebSocket codes are somewhat like HTTP codes, but different. In particular, codes lower than `1000` are reserved, there'll be an error if we try to set such a code. ```js // in case connection is broken @@ -321,8 +321,8 @@ Server-side code is a little bit beyond our scope. Here we'll use Node.js, but y The server-side algorithm will be: 1. Create `clients = new Set()` -- a set of sockets. -2. For each accepted websocket, add it to the set `clients.add(socket)` and setup `message` event listener to get its messages. -3. When a message received: iterate over clients and send it to everyone. +2. For each accepted websocket, add it to the set `clients.add(socket)` and set `message` event listener to get its messages. +3. When a message is received: iterate over clients and send it to everyone. 4. When a connection is closed: `clients.delete(socket)`. ```js @@ -359,7 +359,7 @@ Here's the working example: [iframe src="chat" height="100" zip] -You can also download it (upper-right button in the iframe) and run locally. Just don't forget to install [Node.js](https://nodejs.org/en/) and `npm install ws` before running. +You can also download it (upper-right button in the iframe) and run it locally. Just don't forget to install [Node.js](https://nodejs.org/en/) and `npm install ws` before running. ## Summary @@ -383,6 +383,6 @@ Events: WebSocket by itself does not include reconnection, authentication and many other high-level mechanisms. So there are client/server libraries for that, and it's also possible to implement these capabilities manually. -Sometimes, to integrate WebSocket into existing project, people run WebSocket server in parallel with the main HTTP-server, and they share a single database. Requests to WebSocket use `wss://ws.site.com`, a subdomain that leads to WebSocket server, while `https://site.com` goes to the main HTTP-server. +Sometimes, to integrate WebSocket into existing projects, people run a WebSocket server in parallel with the main HTTP-server, and they share a single database. Requests to WebSocket use `wss://ws.site.com`, a subdomain that leads to the WebSocket server, while `https://site.com` goes to the main HTTP-server. Surely, other ways of integration are also possible. diff --git a/5-network/11-websocket/demo.view/server.js b/5-network/11-websocket/demo.view/server.js index 111a7ce75..bff739938 100644 --- a/5-network/11-websocket/demo.view/server.js +++ b/5-network/11-websocket/demo.view/server.js @@ -21,6 +21,7 @@ function accept(req, res) { function onConnect(ws) { ws.on('message', function (message) { + message = message.toString(); let name = message.match(/([\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]+)$/gu) || "Guest"; ws.send(`Hello from server, ${name}!`); diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index 2667b6948..1b9e93414 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -2,17 +2,17 @@ Cookies are small strings of data that are stored directly in the browser. They are a part of the HTTP protocol, defined by the [RFC 6265](https://tools.ietf.org/html/rfc6265) specification. -Cookies are usually set by a web-server using the response `Set-Cookie` HTTP-header. Then, the browser automatically adds them to (almost) every request to the same domain using the `Cookie` HTTP-header. +Cookies are usually set by a web server using the response `Set-Cookie` HTTP header. Then, the browser automatically adds them to (almost) every request to the same domain using the `Cookie` HTTP header. One of the most widespread use cases is authentication: -1. Upon sign in, the server uses the `Set-Cookie` HTTP-header in the response to set a cookie with a unique "session identifier". -2. Next time when the request is sent to the same domain, the browser sends the cookie over the net using the `Cookie` HTTP-header. +1. Upon sign-in, the server uses the `Set-Cookie` HTTP header in the response to set a cookie with a unique "session identifier". +2. Next time the request is sent to the same domain, the browser sends the cookie over the net using the `Cookie` HTTP header. 3. So the server knows who made the request. We can also access cookies from the browser, using `document.cookie` property. -There are many tricky things about cookies and their options. In this chapter we'll cover them in detail. +There are many tricky things about cookies and their attributes. In this chapter, we'll cover them in detail. ## Reading from document.cookie @@ -31,17 +31,17 @@ alert( document.cookie ); // cookie1=value1; cookie2=value2;... ``` -The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie. +The value of `document.cookie` consists of `name=value` pairs, delimited by `; `. Each one is a separate cookie. -To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that. +To find a particular cookie, we can split `document.cookie` by `; `, and then find the right name. We can use either a regular expression or array functions to do that. -We leave it as an exercise for the reader. Also, at the end of the chapter you'll find helper functions to manipulate cookies. +We leave it as an exercise for the reader. Also, at the end of the chapter, you'll find helper functions to manipulate cookies. ## Writing to document.cookie We can write to `document.cookie`. But it's not a data property, it's an [accessor (getter/setter)](info:property-accessors). An assignment to it is treated specially. -**A write operation to `document.cookie` updates only cookies mentioned in it, but doesn't touch other cookies.** +**A write operation to `document.cookie` updates only the cookie mentioned in it and doesn't touch other cookies.** For instance, this call sets a cookie with the name `user` and value `John`: @@ -50,12 +50,12 @@ document.cookie = "user=John"; // update only cookie named 'user' alert(document.cookie); // show all cookies ``` -If you run it, then probably you'll see multiple cookies. That's because the `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`. +If you run it, you will likely see multiple cookies. That's because the `document.cookie=` operation does not overwrite all cookies. It only sets the mentioned cookie `user`. Technically, name and value can have any characters. To keep the valid formatting, they should be escaped using a built-in `encodeURIComponent` function: ```js run -// special characters (spaces), need encoding +// special characters (spaces) need encoding let name = "my name"; let value = "John Smith" @@ -67,29 +67,20 @@ alert(document.cookie); // ...; my%20name=John%20Smith ```warn header="Limitations" -There are few limitations: +There are a few limitations: +- You can only set/update a single cookie at a time using `document.cookie`. - The `name=value` pair, after `encodeURIComponent`, should not exceed 4KB. So we can't store anything huge in a cookie. - The total number of cookies per domain is limited to around 20+, the exact limit depends on the browser. ``` -Cookies have several options, many of them are important and should be set. +Cookies have several attributes, many of which are important and should be set. -The options are listed after `key=value`, delimited by `;`, like this: +The attributes are listed after `key=value`, delimited by `;`, like this: ```js run document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT" ``` -## path - -- **`path=/mypath`** - -The url path prefix must be absolute. It makes the cookie accessible for pages under that path. By default, it's the current path. - -If a cookie is set with `path=/admin`, it's visible at pages `/admin` and `/admin/something`, but not at `/home` or `/adminpage`. - -Usually, we should set `path` to the root: `path=/` to make the cookie accessible from all website pages. - ## domain - **`domain=site.com`** @@ -102,7 +93,7 @@ It's a safety restriction, to allow us to store sensitive data in cookies that s By default, a cookie is accessible only at the domain that set it. -Please note, by default a cookie is also not shared to a subdomain as well, such as `forum.site.com`. +Please note, by default, a cookie is not shared with a subdomain, such as `forum.site.com`. ```js // if we set a cookie at site.com website... @@ -114,7 +105,7 @@ alert(document.cookie); // no user ...But this can be changed. If we'd like to allow subdomains like `forum.site.com` to get a cookie set at `site.com`, that's possible. -For that to happen, when setting a cookie at `site.com`, we should explicitly set the `domain` option to the root domain: `domain=site.com`. Then all subdomains will see such cookie. +For that to happen, when setting a cookie at `site.com`, we should explicitly set the `domain` attribute to the root domain: `domain=site.com`. Then all subdomains will see such a cookie. For example: @@ -129,19 +120,31 @@ document.cookie = "user=John; *!*domain=site.com*/!*" alert(document.cookie); // has cookie user=John ``` -For historical reasons, `domain=.site.com` (with a dot before `site.com`) also works the same way, allowing access to the cookie from subdomains. That's an old notation and should be used if we need to support very old browsers. +```warn header="Legacy syntax" +Historically, `domain=.site.com` (with a dot before `site.com`) used to work the same way, allowing access to the cookie from subdomains. Leading dots in domain names are now ignored, but some browsers may decline to set the cookie containing such dots. +``` + +To summarize, the `domain` attribute allows to make a cookie accessible at subdomains. + +## path + +- **`path=/mypath`** + +The URL path prefix must be absolute. It makes the cookie accessible for pages under that path. By default, it's the current path. -To summarize, the `domain` option allows to make a cookie accessible at subdomains. +If a cookie is set with `path=/admin`, it's visible on pages `/admin` and `/admin/something`, but not at `/home`, `/home/admin` or `/`. + +Usually, we should set `path` to the root: `path=/` to make the cookie accessible from all website pages. If this attribute is not set the default is calculated using [this method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#path_default_value). ## expires, max-age -By default, if a cookie doesn't have one of these options, it disappears when the browser is closed. Such cookies are called "session cookies" +By default, if a cookie doesn't have one of these attributes, it disappears when the browser/tab is closed. Such cookies are called "session cookies" -To let cookies survive a browser close, we can set either the `expires` or `max-age` option. +To let cookies survive a browser close, we can set either the `expires` or `max-age` attribute. `max-Age` has precedence if both are set. - **`expires=Tue, 19 Jan 2038 03:14:07 GMT`** -The cookie expiration date defines the time, when the browser will automatically delete it. +The cookie expiration date defines the time when the browser will automatically delete it (according to the browser's time zone). The date must be exactly in this format, in the GMT timezone. We can use `date.toUTCString` to get it. For instance, we can set the cookie to expire in 1 day: @@ -156,7 +159,7 @@ If we set `expires` to a date in the past, the cookie is deleted. - **`max-age=3600`** -Is an alternative to `expires` and specifies the cookie's expiration in seconds from the current moment. +It's an alternative to `expires` and specifies the cookie's expiration in seconds from the current moment. If set to zero or a negative value, the cookie is deleted: @@ -178,7 +181,7 @@ The cookie should be transferred only over HTTPS. That is, cookies are domain-based, they do not distinguish between the protocols. -With this option, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, the `secure` flag is the right thing. +With this attribute, if a cookie is set by `https://site.com`, then it doesn't appear when the same site is accessed by HTTP, as `http://site.com`. So if a cookie has sensitive content that should never be sent over unencrypted HTTP, the `secure` flag is the right thing. ```js // assuming we're on https:// now @@ -188,49 +191,49 @@ document.cookie = "user=John; secure"; ## samesite -That's another security attribute `samesite`. It's designed to protect from so-called XSRF (cross-site request forgery) attacks. +This is another security attribute `samesite`. It's designed to protect from so-called XSRF (cross-site request forgery) attacks. To understand how it works and when it's useful, let's take a look at XSRF attacks. ### XSRF attack -Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request, so that it recognizes you and performs all sensitive financial operations. +Imagine, you are logged into the site `bank.com`. That is: you have an authentication cookie from that site. Your browser sends it to `bank.com` with every request so that it recognizes you and performs all sensitive financial operations. Now, while browsing the web in another window, you accidentally come to another site `evil.com`. That site has JavaScript code that submits a form `` to `bank.com` with fields that initiate a transaction to the hacker's account. -The browser sends cookies every time you visit the site `bank.com`, even if the form was submitted from `evil.com`. So the bank recognizes you and actually performs the payment. +The browser sends cookies every time you visit the site `bank.com`, even if the form was submitted from `evil.com`. So the bank recognizes you and performs the payment. ![](cookie-xsrf.svg) -That's a so-called "Cross-Site Request Forgery" (in short, XSRF) attack. +This is a so-called "Cross-Site Request Forgery" (in short, XSRF) attack. -Real banks are protected from it of course. All forms generated by `bank.com` have a special field, a so-called "XSRF protection token", that an evil page can't generate or extract from a remote page. It can submit a form there, but can't get the data back. The site `bank.com` checks for such token in every form it receives. +Real banks are protected from it of course. All forms generated by `bank.com` have a special field, a so-called "XSRF protection token", that an evil page can't generate or extract from a remote page. It can submit a form there, but can't get the data back. The site `bank.com` checks for such a token in every form it receives. Such a protection takes time to implement though. We need to ensure that every form has the required token field, and we must also check all requests. -### Enter cookie samesite option +### Use cookie samesite attribute -The cookie `samesite` option provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". +The cookie `samesite` attribute provides another way to protect from such attacks, that (in theory) should not require "xsrf protection tokens". It has two possible values: -- **`samesite=strict` (same as `samesite` without value)** +- **`samesite=strict`** A cookie with `samesite=strict` is never sent if the user comes from outside the same site. -In other words, whether a user follows a link from their mail or submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent. +In other words, whether a user follows a link from their email, submits a form from `evil.com`, or does any operation that originates from another domain, the cookie is not sent. -If authentication cookies have the `samesite` option, then a XSRF attack has no chances to succeed, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment. +If authentication cookies have the `samesite=strict` attribute, then an XSRF attack has no chance of succeeding, because a submission from `evil.com` comes without cookies. So `bank.com` will not recognize the user and will not proceed with the payment. -The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite` cookie, e.g. a form submission from another page at `bank.com`. +The protection is quite reliable. Only operations that come from `bank.com` will send the `samesite=strict` cookie, e.g. a form submission from another page at `bank.com`. Although, there's a small inconvenience. -When a user follows a legitimate link to `bank.com`, like from their own notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case. +When a user follows a legitimate link to `bank.com`, like from their notes, they'll be surprised that `bank.com` does not recognize them. Indeed, `samesite=strict` cookies are not sent in that case. -We could work around that by using two cookies: one for "general recognition", only for the purposes of saying: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then, a person coming from outside of the site will see a welcome, but payments must be initiated from the bank's website, for the second cookie to be sent. +We could work around that by using two cookies: one for "general recognition", only to say: "Hello, John", and the other one for data-changing operations with `samesite=strict`. Then, a person coming from outside of the site will see a welcome, but payments must be initiated from the bank's website, for the second cookie to be sent. -- **`samesite=lax`** +- **`samesite=lax` (same as `samesite` without value)** A more relaxed approach that also protects from XSRF and doesn't break the user experience. @@ -239,40 +242,40 @@ Lax mode, just like `strict`, forbids the browser to send cookies when coming fr A `samesite=lax` cookie is sent if both of these conditions are true: 1. The HTTP method is "safe" (e.g. GET, but not POST). - The full list of safe HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231). Basically, these are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method. + The full list of safe HTTP methods is in the [RFC7231 specification](https://tools.ietf.org/html/rfc7231#section-4.2.1). These are the methods that should be used for reading, but not writing the data. They must not perform any data-changing operations. Following a link is always GET, the safe method. 2. The operation performs a top-level navigation (changes URL in the browser address bar). - That's usually true, but if the navigation is performed in an `