diff --git a/.gitattributes b/.gitattributes index 6313b56c5..d3877a538 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ * text=auto eol=lf +*.svg binary diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index 53ea36942..50735bd3c 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -12,8 +12,13 @@ Σε αυτό το σημείο, να συμπληρώσω οτι η JavaScript έχει μεγάλη διάφορα με την [Java](https://en.wikipedia.org/wiki/Java_(programming_language)). +<<<<<<< HEAD '''smart header="Γιατί JavaScript?" Όταν είχε φτιαχτεί η JavaScript, είχε ένα διαφορετικό όνομά, λεγότανε "LiveScript". Αλά η Java ήταν πολύ γνωστή εκείνη την εποχή, οπότε αποφασίστηκε οτι θα βοηθούσε αν έκαναν την γλωσσά προγραμματισμού τον "μικρο αδελφό" της Java. +======= +```smart header="Why is it called JavaScript?" +When JavaScript was created, it initially had another name: "LiveScript". But Java was very popular at that time, so it was decided that positioning a new language as a "younger brother" of Java would help. +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac Αλλά καθώς εξελίχθηκε, η JavaScript έγινε μια πλήρως ανεξάρτητη γλώσσα με τη δική της προδιαγραφή που ονομάζεται [ECMAScript] (http://en.wikipedia.org/wiki/ECMAScript), και τώρα δεν έχει καμία σχέση με την Java @@ -90,7 +95,11 @@ JavaScript is the only browser technology that combines these three things. That's what makes JavaScript unique. That's why it's the most widespread tool for creating browser interfaces. +<<<<<<< HEAD While planning to learn a new technology, it's beneficial to check its perspectives. So let's move on to the modern trends affecting it, including new languages and browser abilities. +======= +That said, JavaScript also allows to create servers, mobile applications, etc. +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac ## Languages "over" JavaScript 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 737ad4786..85a7737cb 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -5,11 +5,11 @@ This book is a *tutorial*. It aims to help you gradually learn the language. But ## Specification -**The ECMA-262 specification** contains the most in-depth, detailed and formalized information about JavaScript. It defines the language. +[The ECMA-262 specification](https://www.ecma-international.org/publications/standards/Ecma-262.htm) contains the most in-depth, detailed and formalized information about JavaScript. It defines the language. But being that formalized, it's difficult to understand at first. So if you need the most trustworthy source of information about the language details, the specification is the right place. But it's not for everyday use. -The latest draft is at . +A new specification version is released every year. In-between these releases, the latest specification draft is at . To read about new bleeding-edge features, including those that are "almost standard" (so-called "stage 3"), see proposals at . @@ -24,7 +24,7 @@ Also, if you're in developing for the browser, then there are other specs covere Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for `parseInt` function. -- **MSDN** – Microsoft manual with a lot of information, including JavaScript (often referrerd to as JScript). If one needs something specific to Internet Explorer, better go there: . +- **MSDN** – Microsoft manual with a lot of information, including JavaScript (often referred to as JScript). If one needs something specific to Internet Explorer, better go there: . Also, we can use an internet search with phrases such as "RegExp MSDN" or "RegExp MSDN jscript". 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 6532e54a3..d03f03def 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -32,6 +32,7 @@ In practice, lightweight editors may have a lot of plugins including directory-l The following options deserve your attention: - [Atom](https://atom.io/) (cross-platform, free). +- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). - [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). - [Notepad++](https://notepad-plus-plus.org/) (Windows, free). - [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index ae5b3845d..c84d92704 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -50,11 +50,11 @@ Open Preferences and go to the "Advanced" pane. There's a checkbox at the bottom Now `key:Cmd+Opt+C` can toggle the console. Also, note that the new top menu item named "Develop" has appeared. It has many commands and options. -## Multi-line input - +```smart header="Multi-line input" Usually, when we put a line of code into the console, and then press `key:Enter`, it executes. -To insert multiple lines, press `key:Shift+Enter`. +To insert multiple lines, press `key:Shift+Enter`. This way one can enter long fragments of JavaScript code. +``` ## Summary diff --git a/1-js/01-getting-started/4-devtools/safari.png b/1-js/01-getting-started/4-devtools/safari.png index 37598a261..64c7a3f6c 100644 Binary files a/1-js/01-getting-started/4-devtools/safari.png and b/1-js/01-getting-started/4-devtools/safari.png differ diff --git a/1-js/01-getting-started/4-devtools/safari@2x.png b/1-js/01-getting-started/4-devtools/safari@2x.png index c59cebef2..27def4d09 100644 Binary files a/1-js/01-getting-started/4-devtools/safari@2x.png and b/1-js/01-getting-started/4-devtools/safari@2x.png differ diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md index 4c384da19..f5487b915 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -1,6 +1,6 @@ # Hello, world! -This part of the tutorial is about core JavaScript, the language itself. Later on, you'll learn about Node.js and other platforms that use it. +This part of the tutorial is about core JavaScript, the language itself. But we need a working environment to run our scripts and, since this book is online, the browser is a good choice. We'll keep the amount of browser-specific commands (like `alert`) to a minimum so that you don't spend time on them if you plan to concentrate on another environment (like Node.js). We'll focus on JavaScript in the browser in the [next part](/ui) of the tutorial. @@ -46,7 +46,7 @@ The ` ``` - This trick isn't used in modern JavaScript. These comments hid JavaScript code from old browsers that didn't know how to process the ` ``` -Here, `/path/to/script.js` is an absolute path to the script file (from the site root). - -You can also provide a relative path from the current page. For instance, `src="script.js"` would mean a file `"script.js"` in the current folder. +Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="script.js"` would mean a file `"script.js"` in the current folder. We can give a full URL as well. For instance: diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index 891b8fba3..573d76bc5 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -4,7 +4,7 @@ For a long time, JavaScript evolved without compatibility issues. New features w That had the benefit of never breaking existing code. But the downside was that any mistake or an imperfect decision made by JavaScript's creators got stuck in the language forever. -This was the case until 2009 when ECMAScript 5 (ES5) appeared. It added new features to the language and modified some of the existing ones. To keep the old code working, most modifications are off by default. You need to explicitly enable them with a special directive: `"use strict"`. +This was the case until 2009 when ECMAScript 5 (ES5) appeared. It added new features to the language and modified some of the existing ones. To keep the old code working, most such modifications are off by default. You need to explicitly enable them with a special directive: `"use strict"`. ## "use strict" @@ -19,9 +19,7 @@ For example: ... ``` -We will learn functions (a way to group commands) soon. - -Looking ahead, let's just note that `"use strict"` can be put at the start of most kinds of functions instead of the whole script. Doing that enables strict mode in that function only. But usually, people use it for the whole script. +We will learn functions (a way to group commands) soon. Looking ahead, let's note that `"use strict"` can be put at the beginning of the function body instead of the whole script. Doing that enables strict mode in that function only. But usually, people use it for the whole script. ````warn header="Ensure that \"use strict\" is at the top" diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md index 9ffc3efca..d56e54d28 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md @@ -1,4 +1,4 @@ -First, the variable for the name of our planet. +## The variable for our planet That's simple: @@ -8,7 +8,7 @@ let ourPlanetName = "Earth"; Note, we could use a shorter name `planet`, but it might be not obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong. -Second, the name of the current visitor: +## The name of the current visitor ```js let currentUserName = "John"; diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md index f3a96c692..acd643fde 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md @@ -2,4 +2,4 @@ We generally use upper case for constants that are "hard-coded". Or, in other wo In this code, `birthday` is exactly like that. So we could use the upper case for it. -In contrast, `age` is evaluated in run-time. Today we have one age, a year after we'll have another one. It is constant in a sense that it does not change through the code execution. But it is a bit "less of a constant" than `birthday`, it is calculated, so we should keep the lower case for it. \ No newline at end of file +In contrast, `age` is evaluated in run-time. Today we have one age, a year after we'll have another one. It is constant in a sense that it does not change through the code execution. But it is a bit "less of a constant" than `birthday`: it is calculated, so we should keep the lower case for it. diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 0c123c22c..6d680b3b0 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -12,7 +12,7 @@ A [variable](https://en.wikipedia.org/wiki/Variable_(computer_science)) is a "na To create a variable in JavaScript, use the `let` keyword. -The statement below creates (in other words: *declares* or *defines*) a variable with the name "message": +The statement below creates (in other words: *declares*) a variable with the name "message": ```js let message; @@ -237,7 +237,7 @@ To declare a constant (unchanging) variable, use `const` instead of `let`: const myBirthday = '18.04.1982'; ``` -Variables declared using `const` are called "constants". They cannot be changed. An attempt to do so would cause an error: +Variables declared using `const` are called "constants". They cannot be reassigned. An attempt to do so would cause an error: ```js run const myBirthday = '18.04.1982'; @@ -290,7 +290,7 @@ In other words, capital-named constants are only used as aliases for "hard-coded Talking about variables, there's one more extremely important thing. -A variable name should have a clean, obvious meaning, describe the data that it stores. +A variable name should have a clean, obvious meaning, describing the data that it stores. Variable naming is one of the most important and complex skills in programming. A quick glance at variable names can reveal which code was written by a beginner versus an experienced developer. @@ -323,7 +323,7 @@ Modern JavaScript minifiers and browsers optimize code well enough, so it won't We can declare variables to store data by using the `var`, `let`, or `const` keywords. -- `let` -- is a modern variable declaration. The code must be in strict mode to use `let` in Chrome (V8). +- `let` -- is a modern variable declaration. - `var` -- is an old-school variable declaration. Normally we don't use it at all, but we'll cover subtle differences from `let` in the chapter , just in case you need them. - `const` -- is like `let`, but the value of the variable can't be changed. diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index afa1deb7b..0da617373 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -178,7 +178,7 @@ The `object` type is special. All other types are called "primitive" because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities. We'll deal with them later in the chapter after we learn more about primitives. -The `symbol` type is used to create unique identifiers for objects. We have to mention it here for completeness, but it's better to study this type after objects. +The `symbol` type is used to create unique identifiers for objects. We mention it here for completeness, but we'll study it after objects. ## The typeof operator [#type-typeof] diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md index 3bee4e939..c90097760 100644 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md @@ -14,11 +14,22 @@ true + false = 1 " -9 " - 5 = -14 // (4) null + 1 = 1 // (5) undefined + 1 = NaN // (6) +" \t \n" - 2 = -2 // (7) ``` +<<<<<<< HEAD 1. Η πρόσθεση με μια συμβολοσειρά όπως π.χ. `"" + 1`, μετατρέπει το `1` σε μια συμβολοσειρά: `"" + 1 = "1"`, και τότε έχουμε `"1" + 0`, όπου ο ίδιος κανόνας εφαρμόζεται. 2. Η αφαίρεση `-` (όπως στις περισσότερες Μαθηματικές πράξεις) λειτουργεί μόνο με αριθμούς, και μετατρέπει μια άδεια συμβολοσειρά `""` σε `0`. 3. Η πρόσθεση με μια συμβολοσειρά, τοποθετεί τον αριθμό `5` στη συμβολοσειρά (επιπλέον παράδειγμα: `5 +"9" = "59"`). 4. Η αφαίρεση μετατρέπει τις τιμές πάντα σε αριθμούς, επομένως εδώ μετατρέπει το `" -9 "` στον αριθμό `-9` (αγνοώντας τα κενά γύρω του). 5. Η `null` γίνεται `0` μετά την αριθμητική μετατροπή. 6. Η `undefined` γίνεται `NaN` μετά την αριθμητική μετατροπή. +======= +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`. +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md index f57ec6c03..bf1616647 100644 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md @@ -21,6 +21,7 @@ true + false " -9 " - 5 null + 1 undefined + 1 +" \t \n" - 2 ``` Σκεφτείτε καλά, γράψτε κάτω τις απαντήσεις σας και μετά συγκρίνετε τες με την απάντηση. diff --git a/1-js/02-first-steps/06-type-conversions/article.md b/1-js/02-first-steps/06-type-conversions/article.md index 6bb4e85a6..6f44b0c61 100644 --- a/1-js/02-first-steps/06-type-conversions/article.md +++ b/1-js/02-first-steps/06-type-conversions/article.md @@ -10,7 +10,7 @@ Σε αυτό το κεφάλαιο, δεν θα καλύψουμε την ύλη για τα αντικείμενα. Αντίθετα, θα μελετήσουμε πρώτα τις απλές τιμές. Αργότερα, όταν μάθουμε για τα αντικείμενα, θα δούμε πώς δουλεύει η μετατροπή των αντικειμένων στο κεφάλαιο . ``` -## ToString +## String Conversion Η μετατροπή συμβολοσειρών γίνεται όταν χρειαζόμαστε μια τιμή σε μορφή συμβολοσειράς. @@ -30,7 +30,7 @@ alert(typeof value); // string Η μετατροπή συμβολοσειρών είναι αρκετά προφανής. Η `false` γίνεται `"false"`, η `null` γίνεται `"null"`, κλπ. -## ToNumber +## Numeric Conversion Η μετατροπή αριθμών γίνεται αυτόματα σε Μαθηματικές συναρτήσεις και εκφράσεις. @@ -94,7 +94,7 @@ alert( '1' + 2 ); // '12' (συμβολοσειρά στα αριστερά) Αυτό συμβαίνει μόνο όταν τουλάχιστον μία από τις παραμέτρους είναι μια συμβολοσειρά. Διαφορετικά, οι τιμές μετατρέπονται σε αριθμούς. ```` -## ToBoolean +## Boolean Conversion Η μετατροπή αληθοτιμών είναι η πιο απλή. @@ -116,6 +116,7 @@ alert(Boolean("")); // false ``` ```` +<<<<<<< HEAD ## Περίληψη Οι τρεις πιο διαδεδομένα χρησιμοποιούμενες μετατροπές τύπων, είναι: σε συμβολοσειρά, σε αριθμό, και σε αληθοτιμή. @@ -123,6 +124,15 @@ alert(Boolean("")); // false **`ToString`** -- Γίνεται όταν κάτι το δίνουμε ως έξοδο. Μπορεί να εφαρμοστεί με τη `String(value)`. Η μετατροπή σε συμβολοσειρά είναι συνήθως προφανής για τις απλές τιμές. **`ToNumber`** -- Γίνεται στις Μαθηματικές πράξεις. Μπορεί να εφαρμοστεί με τη `Number(value)`. +======= +## Summary + +The three most widely used type conversions are to string, to number, and to boolean. + +**`String Conversion`** -- Occurs when we output something. Can be performed with `String(value)`. The conversion to string is usually obvious for primitive values. + +**`Numeric Conversion`** -- Occurs in math operations. Can be performed with `Number(value)`. +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac Η μετατροπή ακολουθεί τους κανόνες: @@ -133,7 +143,11 @@ alert(Boolean("")); // false |true / false | `1 / 0` | | `string` | Η συμβολοσειρά διαβάζεται "όπως είναι", τα κενά και από τις δύο πλευρές αγνοούνται. Μια άδεια συμβολοσειρά γίνεται `0`. Ένα σφάλμα δίνει `NaN`. | +<<<<<<< HEAD **`ToBoolean`** -- Γίνεται σε λογικές πράξεις. Μπορεί να εφαρμοστεί με τη `Boolean(value)`. +======= +**`Boolean Conversion`** -- Occurs in logical operations. Can be performed with `Boolean(value)`. +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac Ακολουθεί τους κανόνες: diff --git a/1-js/02-first-steps/07-operators/article.md b/1-js/02-first-steps/07-operators/article.md index b3fbfd9bb..a1373eade 100644 --- a/1-js/02-first-steps/07-operators/article.md +++ b/1-js/02-first-steps/07-operators/article.md @@ -26,7 +26,7 @@ Before we move on, let's grasp some common terminology. alert( y - x ); // 2, binary minus subtracts values ``` - Formally, we're talking about two different operators here: the unary negation (single operand: reverses the sign) and the binary subtraction (two operands: subtracts). + Formally, in the examples above we have two different operators that share the same symbol: the negation operator, a unary operator that reverses the sign, and the subtraction operator, a binary operator that subtracts one number from another. ## String concatenation, binary + @@ -93,9 +93,7 @@ alert( +"" ); // 0 It actually does the same thing as `Number(...)`, but is shorter. -The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings. - -What if we want to sum them? +The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings. What if we want to sum them? The binary plus would add them as strings: @@ -253,14 +251,14 @@ So, there are special operators for it: ```js run no-beautify let counter = 2; - counter++; // works the same as counter = counter + 1, but is shorter + counter++; // works the same as counter = counter + 1, but is shorter alert( counter ); // 3 ``` - **Decrement** `--` decreases a variable by 1: ```js run no-beautify let counter = 2; - counter--; // works the same as counter = counter - 1, but is shorter + counter--; // works the same as counter = counter - 1, but is shorter alert( counter ); // 1 ``` diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md index 5c8bd2bc4..6437b512e 100644 --- a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md +++ b/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md @@ -3,11 +3,11 @@ ```js no-beautify 5 > 4 → true "apple" > "pineapple" → false -"2" > "12" → true -undefined == null → true -undefined === null → false +"2" > "12" → true +undefined == null → true +undefined === null → false null == "\n0\n" → false -null === +"\n0\n" → false +null === +"\n0\n" → false ``` Some of the reasons: @@ -17,5 +17,5 @@ Some of the reasons: 3. Again, dictionary comparison, first char of `"2"` is greater than the first char of `"1"`. 4. Values `null` and `undefined` equal each other only. 5. Strict equality is strict. Different types from both sides lead to false. -6. See (4). +6. Similar to `(4)`, `null` only equals `undefined`. 7. Strict equality of different types. diff --git a/1-js/02-first-steps/08-comparison/article.md b/1-js/02-first-steps/08-comparison/article.md index 8697076a4..d889b1328 100644 --- a/1-js/02-first-steps/08-comparison/article.md +++ b/1-js/02-first-steps/08-comparison/article.md @@ -74,7 +74,7 @@ alert( '2' > 1 ); // true, string '2' becomes a number 2 alert( '01' == 1 ); // true, string '01' becomes a number 1 ``` -For boolean values, `true` becomes `1` and `false` becomes `0`. +For boolean values, `true` becomes `1` and `false` becomes `0`. For example: @@ -138,11 +138,8 @@ The strict equality operator is a bit longer to write, but makes it obvious what ## Comparison with null and undefined -Let's see more edge cases. - There's a non-intuitive behavior when `null` or `undefined` are compared to other values. - For a strict equality check `===` : These values are different, because each of them is a different type. diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index 1d1a3f33d..a4d943245 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md @@ -11,4 +11,3 @@ If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: " ![](ifelse_task2.svg) [demo src="ifelse_task2"] - diff --git a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md index 638ce81f1..ff32354fa 100644 --- a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md +++ b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/solution.md @@ -1,6 +1,6 @@ ```js -result = (a + b < 4) ? 'Below' : 'Over'; +let result = (a + b < 4) ? 'Below' : 'Over'; ``` diff --git a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md index 684e239f2..6bdf8453e 100644 --- a/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md +++ b/1-js/02-first-steps/10-ifelse/5-rewrite-if-question/task.md @@ -4,13 +4,14 @@ importance: 5 # Rewrite 'if' into '?' -Rewrite this `if` using the ternary operator `'?'`: +Rewrite this `if` using the conditional operator `'?'`: ```js +let result; + if (a + b < 4) { result = 'Below'; } else { result = 'Over'; } ``` - diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 49c1fc041..30287ccba 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -6,7 +6,7 @@ To do that, we can use the `if` statement and the conditional operator `?`, that ## The "if" statement -The `if` statement evaluates a condition and, if the condition's result is `true`, executes a block of code. +The `if(...)` statement evaluates a condition in parentheses and, if the result is `true`, executes a block of code. For example: @@ -216,7 +216,7 @@ Depending on the condition `company == 'Netscape'`, either the first or the seco We don't assign a result to a variable here. Instead, we execute different code depending on the condition. -**We don't recommend using the question mark operator in this way.** +**It's not recommended to use the question mark operator in this way.** The notation is shorter than the equivalent `if` statement, which appeals to some programmers. But it is less readable. diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md index b535650ec..a30db7aae 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md @@ -10,7 +10,7 @@ if (userName == 'Admin') { if (pass == 'TheMaster') { alert( 'Welcome!' ); } else if (pass == '' || pass == null) { - alert( 'Canceled.' ); + alert( 'Canceled' ); } else { alert( 'Wrong password' ); } diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/task.md b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md index 0728efad1..290a52642 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/task.md +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md @@ -6,13 +6,13 @@ importance: 3 Write the code which asks for a login with `prompt`. -If the visitor enters `"Admin"`, then `prompt` for a password, if the input is an empty line or `key:Esc` -- show "Canceled.", if it's another string -- then show "I don't know you". +If the visitor enters `"Admin"`, then `prompt` for a password, if the input is an empty line or `key:Esc` -- show "Canceled", if it's another string -- then show "I don't know you". The password is checked as follows: - If it equals "TheMaster", then show "Welcome!", - Another string -- show "Wrong password", -- For an empty string or cancelled input, show "Canceled." +- For an empty string or cancelled input, show "Canceled" The schema: diff --git a/1-js/02-first-steps/11-logical-operators/article.md b/1-js/02-first-steps/11-logical-operators/article.md index 0773a10cb..25f8ff7f5 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -64,7 +64,7 @@ if (hour < 10 || hour > 18 || isWeekend) { } ``` -## OR finds the first truthy value +## OR "||" finds the first truthy value The logic described above is somewhat classical. Now, let's bring in the "extra" features of JavaScript. @@ -186,7 +186,7 @@ if (1 && 0) { // evaluated as true && false ``` -## AND finds the first falsy value +## AND "&&" finds the first falsy value Given multiple AND'ed values: diff --git a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md b/1-js/02-first-steps/12-while-for/7-list-primes/solution.md index 9ff0663d7..b4b64b6fa 100644 --- a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md +++ b/1-js/02-first-steps/12-while-for/7-list-primes/solution.md @@ -26,4 +26,4 @@ for (let i = 2; i <= n; i++) { // for each i... } ``` -There's a lot of space to opimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc. +There's a lot of space to optimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc. diff --git a/1-js/02-first-steps/12-while-for/article.md b/1-js/02-first-steps/12-while-for/article.md index c809581f5..580ac3e14 100644 --- a/1-js/02-first-steps/12-while-for/article.md +++ b/1-js/02-first-steps/12-while-for/article.md @@ -17,7 +17,7 @@ while (condition) { } ``` -While the `condition` is `true`, the `code` from the loop body is executed. +While the `condition` is truthy, the `code` from the loop body is executed. For instance, the loop below outputs `i` while `i < 3`: @@ -84,7 +84,7 @@ This form of syntax should only be used when you want the body of the loop to ex ## The "for" loop -The `for` loop is the most commonly used loop. +The `for` loop is more complex, but it's also the most commonly used loop. It looks like this: @@ -108,11 +108,11 @@ Let's examine the `for` statement part-by-part: |-------|----------|----------------------------------------------------------------------------| | begin | `i = 0` | Executes once upon entering the loop. | | condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. | -| step| `i++` | Executes after the body on each iteration but before the condition check. | | body | `alert(i)`| Runs again and again while the condition is truthy. | - +| step| `i++` | Executes after the body on each iteration. | The general loop algorithm works like this: + ``` Run begin → (if condition → run body and run step) @@ -121,6 +121,8 @@ Run begin → ... ``` +That is, `begin` executes once, and then it iterates: after each `condition` test, `body` and `step` are executed. + If you are new to loops, it could help to go back to the example and reproduce how it runs step-by-step on a piece of paper. Here's exactly what happens in our case: @@ -289,8 +291,7 @@ if (i > 5) { (i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here ``` -...it stops working. Code like this will give a syntax error: - +...it stops working: there's a syntax error. This is just another reason not to use the question mark operator `?` instead of `if`. ```` @@ -299,7 +300,7 @@ This is just another reason not to use the question mark operator `?` instead of Sometimes we need to break out from multiple nested loops at once. -For example, in the code below we loop over `i` and `j`, prompting for the coordinates `(i, j)` from `(0,0)` to `(3,3)`: +For example, in the code below we loop over `i` and `j`, prompting for the coordinates `(i, j)` from `(0,0)` to `(2,2)`: ```js run no-beautify for (let i = 0; i < 3; i++) { @@ -308,8 +309,7 @@ for (let i = 0; i < 3; i++) { let input = prompt(`Value at coords (${i},${j})`, ''); - // what if I want to exit from here to Done (below)? - + // what if we want to exit from here to Done (below)? } } @@ -358,12 +358,12 @@ for (let i = 0; i < 3; i++) { ... } The `continue` directive can also be used with a label. In this case, code execution jumps to the next iteration of the labeled loop. -````warn header="Labels are not a \"goto\"" +````warn header="Labels do not allow to \"jump\" anywhere" 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; // jumps to label? No. +break label; // doesn't jumps to the label below label: for (...) ``` diff --git a/1-js/02-first-steps/13-switch/article.md b/1-js/02-first-steps/13-switch/article.md index 258f24068..dec40a537 100644 --- a/1-js/02-first-steps/13-switch/article.md +++ b/1-js/02-first-steps/13-switch/article.md @@ -125,7 +125,7 @@ switch (a) { break; *!* - case 3: // (*) grouped two cases + case 3: // (*) grouped two cases case 5: alert('Wrong!'); alert("Why don't you take a math class?"); diff --git a/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md b/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md index 523bb127a..46da079c0 100644 --- a/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md +++ b/1-js/02-first-steps/14-function-basics/2-rewrite-function-question-or/task.md @@ -13,7 +13,7 @@ function checkAge(age) { if (age > 18) { return true; } else { - return confirm('Do you have your parents permission to access this page?'); + return confirm('Did parents allow you?'); } } ``` diff --git a/1-js/02-first-steps/14-function-basics/4-pow/solution.md b/1-js/02-first-steps/14-function-basics/4-pow/solution.md index 5ef20c386..19fe9011f 100644 --- a/1-js/02-first-steps/14-function-basics/4-pow/solution.md +++ b/1-js/02-first-steps/14-function-basics/4-pow/solution.md @@ -14,10 +14,8 @@ let x = prompt("x?", ''); let n = prompt("n?", ''); if (n < 1) { - alert(`Power ${n} is not supported, - use an integer greater than 0`); + alert(`Power ${n} is not supported, use a positive integer`); } else { alert( pow(x, n) ); } ``` - diff --git a/1-js/02-first-steps/14-function-basics/article.md b/1-js/02-first-steps/14-function-basics/article.md index ec34b744d..b1881e311 100644 --- a/1-js/02-first-steps/14-function-basics/article.md +++ b/1-js/02-first-steps/14-function-basics/article.md @@ -20,9 +20,13 @@ function showMessage() { } ``` -The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (empty in the example above) and finally the code of the function, also named "the function body", between curly braces. +The `function` keyword goes first, then goes the *name of the function*, then a list of *parameters* between the parentheses (comma-separated, empty in the example above) and finally the code of the function, also named "the function body", between curly braces. -![](function_basics.png) +```js +function name(parameters) { + ...body... +} +``` Our new function can be called by its name: `showMessage()`. @@ -205,12 +209,11 @@ function showMessage(from, text = anotherFunction()) { ``` ```smart header="Evaluation of default parameters" +In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. -In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. In the example above, `anotherFunction()` is called every time `showMessage()` is called without the `text` parameter. This is in contrast to some other languages like Python, where any default parameters are evaluated only once during the initial interpretation. - +In the example above, `anotherFunction()` is called every time `showMessage()` is called without the `text` parameter. ``` - ````smart header="Default parameters old-style" Old editions of JavaScript did not support default parameters. So there are alternative ways to support them, that you can find mostly in the old scripts. @@ -335,7 +338,19 @@ That doesn't work, because JavaScript assumes a semicolon after `return`. That'l return*!*;*/!* (some + long + expression + or + whatever * f(a) + f(b)) ``` -So, it effectively becomes an empty return. We should put the value on the same line instead. + +So, it effectively becomes an empty return. + +If we want the returned expression to wrap across multiple lines, we should start it at the same line as `return`. Or at least put the opening parentheses there as follows: + +```js +return ( + some + long + expression + + or + + whatever * f(a) + f(b) + ) +``` +And it will work just as we expect it to. ```` ## Naming a function [#function-naming] diff --git a/1-js/02-first-steps/14-function-basics/function_basics.png b/1-js/02-first-steps/14-function-basics/function_basics.png deleted file mode 100644 index f5e6f9418..000000000 Binary files a/1-js/02-first-steps/14-function-basics/function_basics.png and /dev/null differ diff --git a/1-js/02-first-steps/14-function-basics/function_basics@2x.png b/1-js/02-first-steps/14-function-basics/function_basics@2x.png deleted file mode 100644 index c31b2636a..000000000 Binary files a/1-js/02-first-steps/14-function-basics/function_basics@2x.png and /dev/null differ diff --git a/1-js/02-first-steps/15-function-expressions-arrows/article.md b/1-js/02-first-steps/15-function-expressions-arrows/article.md index a31cb4b25..55c923713 100644 --- a/1-js/02-first-steps/15-function-expressions-arrows/article.md +++ b/1-js/02-first-steps/15-function-expressions-arrows/article.md @@ -22,7 +22,6 @@ let sayHi = function() { Here, the function is created and assigned to the variable explicitly, like any other value. No matter how the function is defined, it's just a value stored in the variable `sayHi`. - The meaning of these code samples is the same: "create a function and put it into the variable `sayHi`". We can even print out that value using `alert`: @@ -41,7 +40,7 @@ Please note that the last line does not run the function, because there are no p In JavaScript, a function is a value, so we can deal with it as a value. The code above shows its string representation, which is the source code. -It is a special value of course, in the sense that we can call it like `sayHi()`. +Surely, a function is a special value, in the sense that we can call it like `sayHi()`. But it's still a value. So we can work with it like with other kinds of values. @@ -61,21 +60,21 @@ sayHi(); // Hello // this still works too (why wouldn't it) Here's what happens above in detail: 1. The Function Declaration `(1)` creates the function and puts it into the variable named `sayHi`. -2. Line `(2)` copies it into the variable `func`. - - Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself. +2. Line `(2)` copies it into the variable `func`. Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself. 3. Now the function can be called as both `sayHi()` and `func()`. Note that we could also have used a Function Expression to declare `sayHi`, in the first line: ```js -let sayHi = function() { ... }; +let sayHi = function() { + alert( "Hello" ); +}; let func = sayHi; // ... ``` -Everything would work the same. Even more obvious what's going on, right? +Everything would work the same. ````smart header="Why is there a semicolon at the end?" @@ -93,7 +92,7 @@ let sayHi = function() { The answer is simple: - There's no need for `;` at the end of code blocks and syntax structures that use them like `if { ... }`, `for { }`, `function f { }` etc. -- A Function Expression is used inside the statement: `let sayHi = ...;`, as a value. It's not a code block. The semicolon `;` is recommended at the end of statements, no matter what is the value. So the semicolon here is not related to the Function Expression itself in any way, it just terminates the statement. +- A Function Expression is used inside the statement: `let sayHi = ...;`, as a value. It's not a code block, but rather an assignment. The semicolon `;` is recommended at the end of statements, no matter what the value is. So the semicolon here is not related to the Function Expression itself, it just terminates the statement. ```` ## Callback functions @@ -133,11 +132,11 @@ function showCancel() { ask("Do you agree?", showOk, showCancel); ``` -Before we explore how we can write it in a much shorter way, let's note that in the browser (and on the server-side in some cases) such functions are quite popular. The major difference between a real-life implementation and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such a function usually draws a nice-looking question window. But that's another story. +In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such function usually draws a nice-looking question window. But that's another story. -**The arguments of `ask` are called *callback functions* or just *callbacks*.** +**The arguments `showOk` and `showCancel` of `ask` are called *callback functions* or just *callbacks*.** -The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for the "yes" answer, and `showCancel` for the "no" answer. +The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for "yes" answer, and `showCancel` for "no" answer. We can use Function Expressions to write the same function much shorter: @@ -156,12 +155,10 @@ ask( */!* ``` - Here, functions are declared right inside the `ask(...)` call. They have no name, and so are called *anonymous*. Such functions are not accessible outside of `ask` (because they are not assigned to variables), but that's just what we want here. Such code appears in our scripts very naturally, it's in the spirit of JavaScript. - ```smart header="A function is a value representing an \"action\"" Regular values like strings or numbers represent the *data*. @@ -210,7 +207,6 @@ That's due to internal algorithms. When JavaScript prepares to run the script, i And after all Function Declarations are processed, the code is executed. So it has access to these functions. - For example, this works: ```js run refresh untrusted @@ -239,6 +235,8 @@ let sayHi = function(name) { // (*) no magic any more Function Expressions are created when the execution reaches them. That would happen only in the line `(*)`. Too late. +Another special feature of Function Declarations is their block scope. + **In strict mode, when a Function Declaration is within a code block, it's visible everywhere inside that block. But not outside of it.** For instance, let's imagine that we need to declare a function `welcome()` depending on the `age` variable that we get during runtime. And then we plan to use it some time later. @@ -291,7 +289,7 @@ if (age < 18) { } else { - function welcome() { // for age = 16, this "welcome" is never created + function welcome() { alert("Greetings!"); } } @@ -308,7 +306,7 @@ What can we do to make `welcome` visible outside of `if`? The correct approach would be to use a Function Expression and assign `welcome` to the variable that is declared outside of `if` and has the proper visibility. -Now it works as intended: +This code works as intended: ```js run let age = prompt("What is your age?", 18); @@ -395,7 +393,7 @@ alert( sum(1, 2) ); // 3 ``` -If we have only one argument, then parentheses can be omitted, making that even shorter: +If we have only one argument, then parentheses around parameters can be omitted, making that even shorter: ```js run // same as @@ -455,7 +453,7 @@ alert( sum(1, 2) ); // 3 ```smart header="More to come" Here we praised arrow functions for brevity. But that's not all! Arrow functions have other interesting features. We'll return to them later in the chapter . -For now, we can already use them for one-line actions and callbacks. +For now, we can already use arrow functions for one-line actions and callbacks. ``` ## Summary @@ -466,7 +464,6 @@ For now, we can already use them for one-line actions and callbacks. - Function Declarations are processed before the code block is executed. They are visible everywhere in the block. - Function Expressions are created when the execution flow reaches them. - In most cases when we need to declare a function, a Function Declaration is preferable, because it is visible prior to the declaration itself. That gives us more flexibility in code organization, and is usually more readable. So we should use a Function Expression only when a Function Declaration is not fit for the task. We've seen a couple of examples of that in this chapter, and will see more in the future. diff --git a/1-js/02-first-steps/16-javascript-specials/article.md b/1-js/02-first-steps/16-javascript-specials/article.md index b1aefd1d4..b246a841c 100644 --- a/1-js/02-first-steps/16-javascript-specials/article.md +++ b/1-js/02-first-steps/16-javascript-specials/article.md @@ -53,7 +53,7 @@ To fully enable all features of modern JavaScript, we should start scripts with ... ``` -The directive must be at the top of a script or at the beginning of a function. +The directive must be at the top of a script or at the beginning of a function body. Without `"use strict"`, everything still works, but some features behave in the old-fashion, "compatible" way. We'd generally prefer the modern behavior. @@ -143,13 +143,13 @@ Assignments : There is a simple assignment: `a = b` and combined ones like `a *= 2`. Bitwise -: Bitwise operators work with integers on bit-level: see the [docs](mdn:/JavaScript/Reference/Operators/Bitwise_Operators) when they are needed. +: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Reference/Operators/Bitwise_Operators) when they are needed. -Ternary +Conditional : The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`. Logical operators -: Logical AND `&&` and OR `||` perform short-circuit evaluation and then return the value where it stopped. Logical NOT `!` converts the operand to boolean type and returns the inverse value. +: Logical AND `&&` and OR `||` perform short-circuit evaluation and then return the value where it stopped (not necessary `true`/`false`). Logical NOT `!` converts the operand to boolean type and returns the inverse value. Comparisons : Equality check `==` for values of different types converts them to a number (except `null` and `undefined` that equal each other and nothing else), so these are equal: @@ -245,11 +245,9 @@ We covered three ways to create a function in JavaScript: let result = a + b; return result; - } + }; ``` - Function expressions can have a name, like `sum = function name(a, b)`, but that `name` is only visible inside that function. - 3. Arrow functions: ```js @@ -274,13 +272,7 @@ We covered three ways to create a function in JavaScript: - Parameters can have default values: `function sum(a = 1, b = 2) {...}`. - Functions always return something. If there's no `return` statement, then the result is `undefined`. - -| Function Declaration | Function Expression | -|----------------------|---------------------| -| visible in the whole code block | created when the execution reaches it | -| - | can have a name, visible only inside the function | - -More: see , . +Details: see , . ## More to come diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md index f2614ac77..d1cbd18d6 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -2,23 +2,23 @@ Before writing more complex code, let's talk about debugging. -All modern browsers and most other environments support "debugging" -- a special UI in developer tools that makes finding and fixing errors much easier. +[Debugging](https://en.wikipedia.org/wiki/Debugging) is the process of finding and fixing errors within a script. All modern browsers and most other environments support debugging tools -- a special UI in developer tools that makes debugging much easier. It also allows to trace the code step by step to see what exactly is going on. -We'll be using Chrome here, because it's probably the most feature-rich in this aspect. +We'll be using Chrome here, because it has enough features, most other browsers have a similar process`. -## The "sources" pane +## The "Sources" panel Your Chrome version may look a little bit different, but it still should be obvious what's there. - Open the [example page](debugging/index.html) in Chrome. - Turn on developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`). -- Select the `sources` pane. +- Select the `Sources` panel. Here's what you should see if you are doing it for the first time: ![](chrome-open-sources.svg) -The toggler button opens the tab with files. +The toggler button opens the tab with files. Let's click it and select `hello.js` in the tree view. Here's what should show up: @@ -30,7 +30,7 @@ Here we can see three zones: 2. The **Source zone** shows the source code. 3. The **Information and control zone** is for debugging, we'll explore it soon. -Now you could click the same toggler again to hide the resources list and give the code some space. +Now you could click the same toggler again to hide the resources list and give the code some space. ## Console @@ -56,8 +56,8 @@ A *breakpoint* is a point of code where the debugger will automatically pause th While the code is paused, we can examine current variables, execute commands in the console etc. In other words, we can debug it. -We can always find a list of breakpoints in the right pane. That's useful when we have many breakpoints in various files. It allows us to: -- Quickly jump to the breakpoint in the code (by clicking on it in the right pane). +We can always find a list of breakpoints in the right panel. That's useful when we have many breakpoints in various files. It allows us to: +- Quickly jump to the breakpoint in the code (by clicking on it in the right panel). - Temporarily disable the breakpoint by unchecking it. - Remove the breakpoint by right-clicking and selecting Remove. - ...And so on. @@ -70,7 +70,7 @@ That's handy when we need to stop only for a certain variable value or for certa ## Debugger command -We can also pause the code by using the `debugger` command, like this: +We can also pause the code by using the `debugger` command in it, like this: ```js function hello(name) { @@ -89,7 +89,7 @@ That's very convenient when we are in a code editor and don't want to switch to ## Pause and look around -In our example, `hello()` is called during the page load, so the easiest way to activate the debugger is to reload the page. So let's press `key:F5` (Windows, Linux) or `key:Cmd+R` (Mac). +In our example, `hello()` is called during the page load, so the easiest way to activate the debugger (after we've set the breakpoints) is to reload the page. So let's press `key:F5` (Windows, Linux) or `key:Cmd+R` (Mac). As the breakpoint is set, the execution pauses at the 4th line: @@ -105,7 +105,7 @@ Please open the informational dropdowns to the right (labeled with arrows). They At the current moment the debugger is inside `hello()` call, called by a script in `index.html` (no function there, so it's called "anonymous"). - If you click on a stack item, the debugger jumps to the corresponding code, and all its variables can be examined as well. + If you click on a stack item (e.g. "anonymous"), the debugger jumps to the corresponding code, and all its variables can be examined as well. 3. **`Scope` -- current variables.** `Local` shows local function variables. You can also see their values highlighted right over the source. @@ -118,52 +118,68 @@ Please open the informational dropdowns to the right (labeled with arrows). They Now it's time to *trace* the script. -There are buttons for it at the top of the right pane. Let's engage them. - - -- continue the execution, hotkey `key:F8`. +There are buttons for it at the top of the right panel. Let's engage them. + + -- "Resume": continue the execution, hotkey `key:F8`. : Resumes the execution. If there are no additional breakpoints, then the execution just continues and the debugger loses control. Here's what we can see after a click on it: ![](chrome-sources-debugger-trace-1.svg) +<<<<<<< HEAD +======= + + The execution has resumed, reached another breakpoint inside `say()` and paused there. Take a look at the "Call Stack" at the right. It has increased by one more call. We're inside `say()` now. + + -- "Step": run the next command, hotkey `key:F9`. +: Run the next statement. If we click it now, `alert` will be shown. + + Clicking this again and again will step through all script statements one by one. + + -- "Step over": run the next command, but *don't go into a function*, hotkey `key:F10`. +: Similar to the previous the "Step" command, but behaves differently if the next statement is a function call. That is: not a built-in, like `alert`, but a function of our own. + + The "Step" command goes into it and and pauses the execution at its first line, while "Step over" executes the nested function call invisibly, skipping the function internals. + + The execution is then paused immediately after that function. +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac - The execution has resumed, reached another breakpoint inside `say()` and paused there. Take a look at the "Call stack" at the right. It has increased by one more call. We're inside `say()` now. + That's good if we're not interested to see what happens inside the function call. - -- make a step (run the next command), but *don't go into the function*, hotkey `key:F10`. -: If we click it now, `alert` will be shown. The important thing is that `alert` can be any function, the execution "steps over it", skipping the function internals. + -- "Step into", hotkey `key:F11`. +: That's similar to "Step", but behaves differently in case of asynchronous function calls. If you're only starting to learn JavaScript, then you can ignore the difference, as we don't have asynchronous calls yet. - -- make a step, hotkey `key:F11`. -: The same as the previous one, but "steps into" nested functions. Clicking this will step through all script actions one by one. + For the future, just note that "Step" command ignores async actions, such as `setTimeout` (scheduled function call), that execute later. The "Step into" goes into their code, waiting for them if necessary. See [DevTools manual](https://developers.google.com/web/updates/2018/01/devtools#async) for more details. - -- continue the execution till the end of the current function, hotkey `key:Shift+F11`. -: The execution would stop at the very last line of the current function. That's handy when we accidentally entered a nested call using , but it does not interest us, and we want to continue to its end as soon as possible. + -- "Step out": continue the execution till the end of the current function, hotkey `key:Shift+F11`. +: Continue the execution and stop it at the very last line of the current function. That's handy when we accidentally entered a nested call using , but it does not interest us, and we want to continue to its end as soon as possible. - -- enable/disable all breakpoints. + -- enable/disable all breakpoints. : That button does not move the execution. Just a mass on/off for breakpoints. - -- enable/disable automatic pause in case of an error. + -- enable/disable automatic pause in case of an error. : When enabled, and the developer tools is open, a script error automatically pauses the execution. Then we can analyze variables to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment. ```smart header="Continue to here" Right click on a line of code opens the context menu with a great option called "Continue to here". -That's handy when we want to move multiple steps forward, but we're too lazy to set a breakpoint. +That's handy when we want to move multiple steps forward to the line, but we're too lazy to set a breakpoint. ``` ## Logging -To output something to console, there's `console.log` function. +To output something to console from our code, there's `console.log` function. For instance, this outputs values from `0` to `4` to console: ```js run // open console to see for (let i = 0; i < 5; i++) { - console.log("value", i); + console.log("value,", i); } ``` -Regular users don't see that output, it is in the console. To see it, either open the Console tab of developer tools or press `key:Esc` while in another tab: that opens the console at the bottom. +Regular users don't see that output, it is in the console. To see it, either open the Console panel of developer tools or press `key:Esc` while in another panel: that opens the console at the bottom. If we have enough logging in our code, then we can see what's going on from the records, without the debugger. @@ -172,12 +188,12 @@ If we have enough logging in our code, then we can see what's going on from the As we can see, there are three main ways to pause a script: 1. A breakpoint. 2. The `debugger` statements. -3. An error (if dev tools are open and the button is "on"). +3. An error (if dev tools are open and the button is "on"). -Then we can examine variables and step on to see where the execution goes wrong. +When paused, we can debug - examine variables and trace the code to see where the execution goes wrong. There are many more options in developer tools than covered here. The full manual is at . The information from this chapter is enough to begin debugging, but later, especially if you do a lot of browser stuff, please go there and look through more advanced capabilities of developer tools. -Oh, and also you can click at various places of dev tools and just see what's showing up. That's probably the fastest route to learn dev tools. Don't forget about the right click as well! +Oh, and also you can click at various places of dev tools and just see what's showing up. That's probably the fastest route to learn dev tools. Don't forget about the right click and context menus! diff --git a/1-js/03-code-quality/01-debugging-chrome/head.html b/1-js/03-code-quality/01-debugging-chrome/head.html index f219b0af1..615326c08 100644 --- a/1-js/03-code-quality/01-debugging-chrome/head.html +++ b/1-js/03-code-quality/01-debugging-chrome/head.html @@ -1,8 +1,8 @@ diff --git a/1-js/03-code-quality/01-debugging-chrome/largeIcons.svg b/1-js/03-code-quality/01-debugging-chrome/largeIcons.svg new file mode 100644 index 000000000..83303365b --- /dev/null +++ b/1-js/03-code-quality/01-debugging-chrome/largeIcons.svg @@ -0,0 +1,1472 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + a + b + c + d + e + f + g + h + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + + + + + + + + + + + + + + + + + diff --git a/1-js/03-code-quality/01-debugging-chrome/toolbarButtonGlyphs.svg b/1-js/03-code-quality/01-debugging-chrome/toolbarButtonGlyphs.svg deleted file mode 100644 index 5bdf20a83..000000000 --- a/1-js/03-code-quality/01-debugging-chrome/toolbarButtonGlyphs.svg +++ /dev/null @@ -1,1035 +0,0 @@ - -image/svg+xml \ No newline at end of file diff --git a/1-js/03-code-quality/02-coding-style/article.md b/1-js/03-code-quality/02-coding-style/article.md index e51f13b48..bdcfec545 100644 --- a/1-js/03-code-quality/02-coding-style/article.md +++ b/1-js/03-code-quality/02-coding-style/article.md @@ -26,7 +26,7 @@ let n = prompt("n?", ""); if (n < 0) { alert(`Power ${n} is not supported, - please enter an integer number, greater than 0`); + please enter a non-negative integer number`); } else { alert( pow(x, n) ); } @@ -56,21 +56,27 @@ A single-line construct, such as `if (condition) doSomething()`, is an important Here are the annotated variants so you can judge their readability for yourself: - -![](figure-bracket-style.png) +For a very brief code, one line is allowed, e.g. `if (cond) return null`. But a code block (the last variant) is usually more readable. ### Line Length @@ -106,9 +112,9 @@ There are two types of indents: - **Horizontal indents: 2 or 4 spaces.** - A horizontal indentation is made using either 2 or 4 spaces or the "Tab" symbol. Which one to choose is an old holy war. Spaces are more common nowadays. + A horizontal indentation is made using either 2 or 4 spaces or the horizontal tab symbol (key `key:Tab`). Which one to choose is an old holy war. Spaces are more common nowadays. - One advantage of spaces over tabs is that spaces allow more flexible configurations of indents than the "Tab" symbol. + One advantage of spaces over tabs is that spaces allow more flexible configurations of indents than the tab symbol. For instance, we can align the arguments with the opening bracket, like this: @@ -153,7 +159,7 @@ If you're an experienced JavaScript programmer, you may choose a no-semicolon co Try to avoid nesting code too many levels deep. -For example, in the loop, it's sometimes a good idea to use the ["continue"](info:while-for#continue) directive to avoid extra nesting. +For example, in the loop, it's sometimes a good idea to use the [`continue`](info:while-for#continue) directive to avoid extra nesting. For example, instead of adding a nested `if` conditional like this: @@ -271,7 +277,7 @@ That's because when reading code, we first want to know *what it does*. If the c ## Style Guides -A style guide contains general rules about "how to write" code, e.g. which quotes to use, how many spaces to indent, where to put line breaks, etc. A lot of minor things. +A style guide contains general rules about "how to write" code, e.g. which quotes to use, how many spaces to indent, the maximal line length, etc. A lot of minor things. When all members of a team use the same style guide, the code looks uniform, regardless of which team member wrote it. diff --git a/1-js/03-code-quality/02-coding-style/figure-bracket-style.png b/1-js/03-code-quality/02-coding-style/figure-bracket-style.png deleted file mode 100644 index b04db65c6..000000000 Binary files a/1-js/03-code-quality/02-coding-style/figure-bracket-style.png and /dev/null differ diff --git a/1-js/03-code-quality/02-coding-style/figure-bracket-style@2x.png b/1-js/03-code-quality/02-coding-style/figure-bracket-style@2x.png deleted file mode 100644 index 0e994ca4b..000000000 Binary files a/1-js/03-code-quality/02-coding-style/figure-bracket-style@2x.png and /dev/null differ diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md index fabe3b783..29ba701f8 100644 --- a/1-js/03-code-quality/03-comments/article.md +++ b/1-js/03-code-quality/03-comments/article.md @@ -4,7 +4,7 @@ As we know from the chapter , comments can be single-line: start We normally use them to describe how and why the code works. -At first sight, commenting might be obvious, but novices in programming usually get it wrong. +At first sight, commenting might be obvious, but novices in programming often use them wrongly. ## Bad comments @@ -120,9 +120,9 @@ In reality, we can't totally avoid "explanatory" comments. There are complex alg So, explanatory comments are usually bad. Which comments are good? Describe the architecture -: Provide a high-level overview of components, how they interact, what's the control flow in various situations... In short -- the bird's eye view of the code. There's a special diagram language [UML](http://wikipedia.org/wiki/Unified_Modeling_Language) for high-level architecture diagrams. Definitely worth studying. +: Provide a high-level overview of components, how they interact, what's the control flow in various situations... In short -- the bird's eye view of the code. There's a special language [UML](http://wikipedia.org/wiki/Unified_Modeling_Language) to build high-level architecture diagrams explaining the code. Definitely worth studying. -Document a function usage +Document function parameters and usage : There's a special syntax [JSDoc](http://en.wikipedia.org/wiki/JSDoc) to document a function: usage, parameters, returned value. For instance: @@ -175,6 +175,6 @@ Good comments allow us to maintain the code well, come back to it after a delay **Avoid comments:** - That tell "how code works" and "what it does". -- Put them only if it's impossible to make the code so simple and self-descriptive that it doesn't require those. +- Put them in only if it's impossible to make the code so simple and self-descriptive that it doesn't require them. Comments are also used for auto-documenting tools like JSDoc3: they read them and generate HTML-docs (or docs in another format). diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 3455ac47d..e9b5e96c1 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -1,4 +1,4 @@ -# Automated testing with mocha +# Automated testing with Mocha Automated testing will be used in further tasks, and it's also widely used in real projects. @@ -18,15 +18,15 @@ For instance, we're creating a function `f`. Wrote some code, testing: `f(1)` wo That's very typical. When we develop something, we keep a lot of possible use cases in mind. But it's hard to expect a programmer to check all of them manually after every change. So it becomes easy to fix one thing and break another one. -**Automated testing means that tests are written separately, in addition to the code. They can be executed automatically and check all the main use cases.** +**Automated testing means that tests are written separately, in addition to the code. They run our functions in various ways and compare results with the expected.** ## Behavior Driven Development (BDD) -Let's use a technique named [Behavior Driven Development](http://en.wikipedia.org/wiki/Behavior-driven_development) or, in short, BDD. That approach is used among many projects. BDD is not just about testing. That's more. +Let's start with a technique named [Behavior Driven Development](http://en.wikipedia.org/wiki/Behavior-driven_development) or, in short, BDD. **BDD is three things in one: tests AND documentation AND examples.** -Let's see the example. +To understand BDD, we'll examine a practical case of development. ## Development of "pow": the spec @@ -36,7 +36,7 @@ That task is just an example: there's the `**` operator in JavaScript that can d Before creating the code of `pow`, we can imagine what the function should do and describe it. -Such description is called a *specification* or, in short, a spec, and looks like this: +Such description is called a *specification* or, in short, a spec, and contains descriptions of use cases together with tests for them, like this: ```js describe("pow", function() { @@ -51,7 +51,7 @@ describe("pow", function() { A spec has three main building blocks that you can see above: `describe("title", function() { ... })` -: What functionality we're describing. Uses to group "workers" -- the `it` blocks. In our case we're describing the function `pow`. +: What functionality we're describing. In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks. `it("use case description", function() { ... })` : In the title of `it` we *in a human-readable way* describe the particular use case, and the second argument is a function that tests it. @@ -59,9 +59,9 @@ A spec has three main building blocks that you can see above: `assert.equal(value1, value2)` : The code inside `it` block, if the implementation is correct, should execute without errors. - Functions `assert.*` are used to check whether `pow` works as expected. Right here we're using one of them -- `assert.equal`, it compares arguments and yields an error if they are not equal. Here it checks that the result of `pow(2, 3)` equals `8`. + Functions `assert.*` are used to check whether `pow` works as expected. Right here we're using one of them -- `assert.equal`, it compares arguments and yields an error if they are not equal. Here it checks that the result of `pow(2, 3)` equals `8`. There are other types of comparisons and checks, that we'll add later. - There are other types of comparisons and checks that we'll see further. +The specification can be executed, and it will run the test specified in `it` block. We'll see that later. ## The development flow @@ -79,7 +79,7 @@ So, the development is *iterative*. We write the spec, implement it, make sure t Let's see this development flow in our practical case. -The first step is complete: we have an initial spec for `pow`. Now, before making the implementaton, let's use few JavaScript libraries to run the tests, just to see that they are working (they will all fail). +The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use few JavaScript libraries to run the tests, just to see that they are working (they will all fail). ## The spec in action @@ -336,10 +336,9 @@ The result with new tests: The newly added tests fail, because our implementation does not support them. That's how BDD is done: first we write failing tests, and then make an implementation for them. ```smart header="Other assertions" - Please note the assertion `assert.isNaN`: it checks for `NaN`. -There are other assertions in Chai as well, for instance: +There are other assertions in [Chai](http://chaijs.com) as well, for instance: - `assert.equal(value1, value2)` -- checks the equality `value1 == value2`. - `assert.strictEqual(value1, value2)` -- checks the strict equality `value1 === value2`. @@ -380,9 +379,9 @@ In BDD, the spec goes first, followed by implementation. At the end we have both The spec can be used in three ways: -1. **Tests** guarantee that the code works correctly. -2. **Docs** -- the titles of `describe` and `it` tell what the function does. -3. **Examples** -- the tests are actually working examples showing how a function can be used. +1. As **Tests** - they guarantee that the code works correctly. +2. As **Docs** -- the titles of `describe` and `it` tell what the function does. +3. As **Examples** -- the tests are actually working examples showing how a function can be used. With the spec, we can safely improve, change, even rewrite the function from scratch and make sure it still works right. diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index f4df5998c..b399fd428 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -19,7 +19,7 @@ Here Babel comes to the rescue. Actually, there are two parts in Babel: -1. First, the transpiler program, which rewrites the code. The developer runs it on their own computer. It rewrites the code into the older standard. And then the code is delivered to the website for users. Modern project build system like [webpack](http://webpack.github.io/) provide means to run transpiler automatically on every code change, so that very easy to integrate into development process. +1. First, the transpiler program, which rewrites the code. The developer runs it on their own computer. It rewrites the code into the older standard. And then the code is delivered to the website for users. Modern project build systems like [webpack](http://webpack.github.io/) provide means to run transpiler automatically on every code change, so that very easy to integrate into development process. 2. Second, the polyfill. @@ -29,8 +29,8 @@ Actually, there are two parts in Babel: A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations. Two interesting polyfills are: - - [babel polyfill](https://babeljs.io/docs/usage/polyfill/) that supports a lot, but is big. - - [polyfill.io](http://polyfill.io) service that allows to load/construct polyfills on-demand, depending on the features we need. + - [core js](https://github.com/zloirock/core-js) that supports a lot, allows to include only needed features. + - [polyfill.io](http://polyfill.io) service that provides a script with polyfills, depending on the features and user's browser. So, if we're going to use modern language features, a transpiler and a polyfill are necessary. diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index da24144b8..ea015e7ca 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -154,7 +154,7 @@ let user = { }; let key = "name"; -user.key // undefined +alert( user.key ) // undefined ``` ### Computed properties @@ -236,7 +236,7 @@ In that case the visitor may choose `__proto__` as the key, and the assignment l There is a way to make objects treat `__proto__` as a regular property, which we'll cover later, but first we need to know more about objects. -There's also another data structure [Map](info:map-set-weakmap-weakset), that we'll learn in the chapter , which supports arbitrary keys. +There's also another data structure [Map](info:map-set), that we'll learn in the chapter , which supports arbitrary keys. ```` @@ -322,7 +322,7 @@ alert( *!*key*/!* in user ); // true, takes the name from key and checks for suc ``` ````smart header="Using \"in\" for properties that store `undefined`" -Usually, the strict comparison `"=== undefined"` check works fine. But there's a special case when it fails, but `"in"` works correctly. +Usually, the strict comparison `"=== undefined"` check the property existance just fine. But there's a special case when it fails, but `"in"` works correctly. It's when an object property exists, but stores `undefined`: @@ -342,7 +342,6 @@ In the code above, the property `obj.test` technically exists. So the `in` opera Situations like this happen very rarely, because `undefined` is usually not assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code. ```` - ## The "for..in" loop To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before. @@ -531,7 +530,7 @@ The equality `==` and strict equality `===` operators for objects work exactly t **Two objects are equal only if they are the same object.** -For instance, two variables reference the same object, they are equal: +For instance, if two variables reference the same object, they are equal: ```js run let a = {}; @@ -570,7 +569,7 @@ user.age = 25; // (*) alert(user.age); // 25 ``` -It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes the value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`. +It might seem that the line `(*)` would cause an error, but no, there's totally no problem. That's because `const` fixes only value of `user` itself. And here `user` stores the reference to the same object all the time. The line `(*)` goes *inside* the object, it doesn't reassign `user`. The `const` would give an error if we try to set `user` to something else, for instance: @@ -712,7 +711,7 @@ alert(clone.sizes.width); // 51, see the result from the other one To fix that, we should use the cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning". -There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](http://w3c.github.io/html/infrastructure.html#safe-passing-of-structured-data). In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library [lodash](https://lodash.com), the method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +There's a standard algorithm for deep cloning that handles the case above and more complex cases, called the [Structured cloning algorithm](https://html.spec.whatwg.org/multipage/structured-data.html#safe-passing-of-structured-data). In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library [lodash](https://lodash.com), the method is called [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). diff --git a/1-js/04-object-basics/02-garbage-collection/article.md b/1-js/04-object-basics/02-garbage-collection/article.md index 620cbc21a..672e26d43 100644 --- a/1-js/04-object-basics/02-garbage-collection/article.md +++ b/1-js/04-object-basics/02-garbage-collection/article.md @@ -156,7 +156,7 @@ The following "garbage collection" steps are regularly performed: - The garbage collector takes roots and "marks" (remembers) them. - Then it visits and "marks" all references from them. - Then it visits marked objects and marks *their* references. All visited objects are remembered, so as not to visit the same object twice in the future. -- ...And so on until there are unvisited references (reachable from the roots). +- ...And so on until every reachable (from the roots) references are visited. - All objects except marked ones are removed. For instance, let our object structure look like this: @@ -181,9 +181,9 @@ Now the objects that could not be visited in the process are considered unreacha ![](garbage-collection-5.svg) -That's the concept of how garbage collection works. +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. -JavaScript engines apply many optimizations to make it run faster and not affect the execution. +That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not affect the execution. Some of the optimizations: @@ -191,7 +191,7 @@ Some of the optimizations: - **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine tries to split the garbage collection into pieces. Then the pieces are executed one by one, separately. That requires some extra bookkeeping between them to track changes, but we have many tiny delays instead of a big one. - **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution. -There are other optimizations and flavours of garbage collection algorithms. As much as I'd like to describe them here, I have to hold off, because different engines implement different tweaks and techniques. And, what's even more important, things change as engines develop, so going deeper "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below. +There exist other optimizations and flavours of garbage collection algorithms. As much as I'd like to describe them here, I have to hold off, because different engines implement different tweaks and techniques. And, what's even more important, things change as engines develop, so studying deeper "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below. ## Summary diff --git a/1-js/04-object-basics/03-symbol/article.md b/1-js/04-object-basics/03-symbol/article.md index bd02a97eb..a17f85fe2 100644 --- a/1-js/04-object-basics/03-symbol/article.md +++ b/1-js/04-object-basics/03-symbol/article.md @@ -3,11 +3,11 @@ By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types. -Till now we've only seen strings. Now let's see the advantages that symbols can give us. +Till now we've been using only strings. Now let's see the benefits that symbols can give us. ## Symbols -"Symbol" value represents a unique identifier. +A "symbol" represents a unique identifier. A value of this type can be created using `Symbol()`: @@ -50,9 +50,9 @@ alert(id); // TypeError: Cannot convert a Symbol value to a string */!* ``` -That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not occasionally convert one into another. +That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not accidentally convert one into another. -If we really want to show a symbol, we need to call `.toString()` on it, like here: +If we really want to show a symbol, we need to explicitly call `.toString()` on it, like here: ```js run let id = Symbol("id"); *!* @@ -60,7 +60,7 @@ alert(id.toString()); // Symbol(id), now it works */!* ``` -Or get `symbol.description` property to get the description only: +Or get `symbol.description` property to show the description only: ```js run let id = Symbol("id"); *!* @@ -72,23 +72,27 @@ alert(id.description); // id ## "Hidden" properties -Symbols allow us to create "hidden" properties of an object, that no other part of code can occasionally access or overwrite. +Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite. -For instance, if we're working with `user` objects, that belong to a third-party code and don't have any `id` field. We'd like to add identifiers to them. +For instance, if we're working with `user` objects, that belong to a third-party code. We'd like to add identifiers to them. Let's use a symbol key for it: ```js run -let user = { name: "John" }; +let user = { // belongs to another code + name: "John" +}; + let id = Symbol("id"); -user[id] = "ID Value"; +user[id] = 1; + alert( user[id] ); // we can access the data using the symbol as the key ``` What's the benefit of using `Symbol("id")` over a string `"id"`? -As `user` objects belongs to another code, and that code also works with them, we shouldn't just add any fields to it. That's unsafe. But a symbol cannot be accessed occasionally, the third-party code probably won't even see it, so it's probably all right to do. +As `user` objects belongs to another code, and that code also works with them, we shouldn't just add any fields to it. That's unsafe. But a symbol cannot be accessed accidentally, the third-party code probably won't even see it, so it's probably all right to do. Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. That may be another JavaScript library, so that the scripts are completely unaware of each other. @@ -108,13 +112,13 @@ There will be no conflict between our and their identifiers, because symbols are ```js run let user = { name: "John" }; -// our script uses "id" property -user.id = "ID Value"; +// Our script uses "id" property +user.id = "Our id value"; -// ...if later another script the uses "id" for its purposes... +// ...Another script also wants "id" for its purposes... user.id = "Their id value" -// boom! overwritten! it did not mean to harm the colleague, but did it! +// Boom! overwritten by another script! ``` ### Symbols in a literal @@ -129,7 +133,7 @@ let id = Symbol("id"); let user = { name: "John", *!* - [id]: 123 // not just "id: 123" + [id]: 123 // not "id: 123" */!* }; ``` @@ -192,9 +196,7 @@ alert( obj[0] ); // test (same property) ## Global symbols -As we've seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. - -For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property. +As we've seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property. To achieve that, there exists a *global symbol registry*. We can create symbols in it and access them later, and it guarantees that repeated accesses by the same name return exactly the same symbol. @@ -230,22 +232,29 @@ For global symbols, not only `Symbol.for(key)` returns a symbol by name, but the For instance: ```js run +// get symbol by name let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); -// get name from symbol +// get name by symbol alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id ``` The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and return `undefined`. +That said, any symbols have `description` property. + For instance: ```js run -alert( Symbol.keyFor(Symbol.for("name")) ); // name, global symbol +let globalSymbol = Symbol.for("name"); +let localSymbol = Symbol("name"); + +alert( Symbol.keyFor(globalSymbol) ); // name, global symbol +alert( Symbol.keyFor(localSymbol) ); // undefined, not global -alert( Symbol.keyFor(Symbol("name2")) ); // undefined, the argument isn't a global symbol +alert( localSymbol.description ); // name ``` ## System symbols @@ -275,10 +284,10 @@ Symbols are always different values, even if they have the same name. If we want Symbols have two main use cases: 1. "Hidden" object properties. - If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be occasionally 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 occasional use or overwrite. + If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite. So we can "covertly" hide something into objects that we need, but others should not see, using symbolic properties. 2. There are many system symbols used by JavaScript which are accessible as `Symbol.*`. We can use them to alter some built-in behaviors. For instance, later in the tutorial we'll use `Symbol.iterator` for [iterables](info:iterable), `Symbol.toPrimitive` to setup [object-to-primitive conversion](info:object-toprimitive) and so on. -Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in methods and syntax constructs adhere to a common agreement that they are. And the one who explicitly calls the aforementioned methods probably understands well what he's doing. +Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in functions and syntax constructs don't use these methods. diff --git a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md b/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md index 5589b655e..0534202a8 100644 --- a/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md +++ b/1-js/04-object-basics/04-object-methods/2-check-syntax/solution.md @@ -11,7 +11,7 @@ let user = { (user.go)() // error! ``` -The error message in most browsers does not give understanding what went wrong. +The error message in most browsers does not give us much of a clue about what went wrong. **The error appears because a semicolon is missing after `user = {...}`.** 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 fefcb9086..2dda938d7 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -61,7 +61,7 @@ user.sayHi(); // Hello! ``` ```smart header="Object-oriented programming" -When we write our code using objects to represent entities, that's called an [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), in short: "OOP". +When we write our code using objects to represent entities, that's called [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), in short: "OOP". OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture, and there are great books on that topic, like "Design Patterns: Elements of Reusable Object-Oriented Software" by E.Gamma, R.Helm, R.Johnson, J.Vissides or "Object-Oriented Analysis and Design with Applications" by G.Booch, and more. ``` @@ -111,6 +111,7 @@ let user = { sayHi() { *!* + // "this" is the "current object" alert(this.name); */!* } @@ -166,9 +167,9 @@ If we used `this.name` instead of `user.name` inside the `alert`, then the code ## "this" is not bound -In JavaScript, "this" keyword behaves unlike most other programming languages. It can be used in any function. +In JavaScript, keyword `this` behaves unlike most other programming languages. It can be used in any function. -There's no syntax error in the code like that: +There's no syntax error in the following example: ```js function sayHi() { @@ -176,7 +177,7 @@ function sayHi() { } ``` -The value of `this` is evaluated during the run-time, depending on the context. And it can be anything. +The value of `this` is evaluated during the run-time, depending on the context. For instance, here the same function is assigned to two different objects and has different "this" in the calls: @@ -219,17 +220,17 @@ In this case `this` is `undefined` in strict mode. If we try to access `this.nam In non-strict mode the value of `this` in such case will be the *global object* (`window` in a browser, we'll get to it later in the chapter [](info:global-object)). This is a historical behavior that `"use strict"` fixes. -Usually such call is an programming error. If there's `this` inside a function, it expects to be called in an object context. +Usually such call is a programming error. If there's `this` inside a function, it expects to be called in an object context. ```` ```smart header="The consequences of unbound `this`" If you come from another programming language, then you are probably used to the idea of a "bound `this`", where methods defined in an object always have `this` referencing that object. -In JavaScript `this` is "free", its value is evaluated at call-time and does not depend on where the method was declared, but rather on what's the object "before the dot". +In JavaScript `this` is "free", its value is evaluated at call-time and does not depend on where the method was declared, but rather on what object is "before the dot". -The concept of run-time evaluated `this` has both pluses and minuses. On the one hand, a function can be reused for different objects. On the other hand, greater flexibility opens a place for mistakes. +The concept of run-time evaluated `this` has both pluses and minuses. On the one hand, a function can be reused for different objects. On the other hand, the greater flexibility creates more possibilities for mistakes. -Here our position is not to judge whether this language design decision is good or bad. We'll understand how to work with it, how to get benefits and evade problems. +Here our position is not to judge whether this language design decision is good or bad. We'll understand how to work with it, how to get benefits and avoid problems. ``` ## Internals: Reference Type diff --git a/1-js/04-object-basics/05-object-toprimitive/article.md b/1-js/04-object-basics/05-object-toprimitive/article.md index 5018637fd..ca449e5fe 100644 --- a/1-js/04-object-basics/05-object-toprimitive/article.md +++ b/1-js/04-object-basics/05-object-toprimitive/article.md @@ -15,9 +15,7 @@ In the chapter we've seen the rules for numeric, string We can fine-tune string and numeric conversion, using special object methods. -The conversion algorithm is called `ToPrimitive` in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive). It's called with a "hint" that specifies the conversion type. - -There are three variants: +There are three variants of type conversion, so-called "hints", described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive): `"string"` : For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`: @@ -66,7 +64,7 @@ Please note -- there are only three hints. It's that simple. There is no "boolea **To do the conversion, JavaScript tries to find and call three object methods:** -1. Call `obj[Symbol.toPrimitive](hint)` if the method exists, +1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists, 2. Otherwise if hint is `"string"` - try `obj.toString()` and `obj.valueOf()`, whatever exists. 3. Otherwise if hint is `"number"` or `"default"` @@ -78,9 +76,9 @@ Let's start from the first method. There's a built-in symbol named `Symbol.toPri ```js obj[Symbol.toPrimitive] = function(hint) { - // return a primitive value + // must return a primitive value // hint = one of "string", "number", "default" -} +}; ``` For instance, here `user` object implements it: @@ -138,6 +136,8 @@ alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500 ``` +As we can see, the behavior is the same as the previous example with `Symbol.toPrimitive`. + Often we want a single "catch-all" place to handle all primitive conversions. In this case, we can implement `toString` only, like this: ```js run @@ -171,53 +171,40 @@ In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there wil ## Further operations -An operation that initiated the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary. +An operation that initiated the conversion gets the primitive, and then continues to work with it, applying further conversions if necessary. For instance: -- Mathematical operations (except binary plus) perform `ToNumber` conversion: +- Mathematical operations, except binary plus, convert the primitive to a number: ```js run let obj = { - toString() { // toString handles all conversions in the absence of other methods - return "2"; - } - }; - - alert(obj * 2); // 4, ToPrimitive gives "2", then it becomes 2 - ``` - -- Binary plus checks the primitive -- if it's a string, then it does concatenation, otherwise it performs `ToNumber` and works with numbers. - - String example: - ```js run - let obj = { + // toString handles all conversions in the absence of other methods toString() { return "2"; } }; - alert(obj + 2); // 22 (ToPrimitive returned string => concatenation) + alert(obj * 2); // 4, object converted to primitive "2", then multiplication made it a number ``` - Number example: +- Binary plus will concatenate strings in the same situation: ```js run let obj = { toString() { - return true; + return "2"; } }; - alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber) + alert(obj + 2); // 22 (conversion to primitive returned a string => concatenation) ``` - ## Summary The object-to-primitive conversion is called automatically by many built-in functions and operators that expect a primitive as a value. There are 3 types (hints) of it: -- `"string"` (for `alert` and other string conversions) +- `"string"` (for `alert` and other operations that need a string) - `"number"` (for maths) - `"default"` (few operators) diff --git a/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md b/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md index 3362b5b4b..c2c44881e 100644 --- a/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md +++ b/1-js/04-object-basics/06-constructor-new/3-accumulator/task.md @@ -17,8 +17,10 @@ Here's the demo of the code: ```js let accumulator = new Accumulator(1); // initial value 1 + accumulator.read(); // adds the user-entered value accumulator.read(); // adds the user-entered value + alert(accumulator.value); // shows the sum of these values ``` 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 4a7922a70..a885e35ff 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -27,7 +27,7 @@ alert(user.name); // Jack alert(user.isAdmin); // false ``` -When a function is executed as `new User(...)`, it does the following steps: +When a function is executed with `new`, it does the following steps: 1. A new empty object is created and assigned to `this`. 2. The function body executes. Usually it modifies `this`, adds new properties to it. @@ -51,7 +51,7 @@ function User(name) { } ``` -So the result of `new User("Jack")` is the same object as: +So `let user = new User("Jack")` gives the same result as: ```js let user = { @@ -136,7 +136,7 @@ Usually, constructors do not have a `return` statement. Their task is to write a But if there is a `return` statement, then the rule is simple: -- If `return` is called with object, then it is returned instead of `this`. +- If `return` is called with an object, then the object is returned instead of `this`. - If `return` is called with a primitive, it's ignored. In other words, `return` with an object returns that object, in all other cases `this` is returned. @@ -148,10 +148,10 @@ function BigUser() { this.name = "John"; - return { name: "Godzilla" }; // <-- returns an object + return { name: "Godzilla" }; // <-- returns this object } -alert( new BigUser().name ); // Godzilla, got that object ^^ +alert( new BigUser().name ); // Godzilla, got that object ``` And here's an example with an empty `return` (or we could place a primitive after it, doesn't matter): @@ -161,10 +161,7 @@ function SmallUser() { this.name = "John"; - return; // finishes the execution, returns this - - // ... - + return; // <-- returns this } alert( new SmallUser().name ); // John @@ -215,6 +212,8 @@ john = { */ ``` +To create complex objects, there's a more advanced syntax, [classes](info:classes), that we'll cover later. + ## Summary - Constructor functions or, briefly, constructors, are regular functions, but there's a common agreement to name them with capital letter first. diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md index 13b71b2fe..fd22a4653 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md @@ -17,9 +17,7 @@ Why? Let's replay what's happening at line `(*)`: 1. When a property of `str` is accessed, a "wrapper object" is created. 2. In strict mode, writing into it is an error. -3. Otherwise, the operation with the property is carried on, the object gets the `test` property, but after that the "wrapper object" disappears. - -So, without strict mode, in the last line `str` has no trace of the property. +3. Otherwise, the operation with the property is carried on, the object gets the `test` property, but after that the "wrapper object" disappears, so in the last line `str` has no trace of the property. **This example clearly shows that primitives are not objects.** diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index bc94ef994..06138a004 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -1,8 +1,6 @@ # Methods of primitives -JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects. - -They also provide methods to call as such. We will study those soon, but first we'll see how it works because, of course, primitives are not objects (and here we will make it even clearer). +JavaScript allows us to work with primitives (strings, numbers, etc.) as if they were objects. They also provide methods to call as such. We will study those soon, but first we'll see how it works because, of course, primitives are not objects (and here we will make it even clearer). Let's look at the key distinctions between primitives and objects. @@ -35,7 +33,7 @@ Many built-in objects already exist, such as those that work with dates, errors, But, these features come with a cost! -Objects are "heavier" than primitives. They require additional resources to support the internal machinery. But as properties and methods are very useful in programming, JavaScript engines try to optimize them to reduce the additional burden. +Objects are "heavier" than primitives. They require additional resources to support the internal machinery. ## A primitive as an object @@ -52,7 +50,7 @@ The solution looks a little bit awkward, but here it is: The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean` and `Symbol`. Thus, they provide different sets of methods. -For instance, there exists a method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns a capitalized string. +For instance, there exists a string method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns a capitalized `str`. Here's how it works: @@ -84,7 +82,7 @@ We'll see more specific methods in chapters and . ````warn header="Constructors `String/Number/Boolean` are for internal use only" -Some languages like Java allow us to create "wrapper objects" for primitives explicitly using a syntax like `new Number(1)` or `new Boolean(false)`. +Some languages like Java allow us to explicitly create "wrapper objects" for primitives using a syntax like `new Number(1)` or `new Boolean(false)`. In JavaScript, that's also possible for historical reasons, but highly **unrecommended**. Things will go crazy in several places. diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 57162d752..7da5df209 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -2,7 +2,7 @@ All numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers". -Let's recap and expand upon what we currently know about them. +Let's expand upon what we currently know about them. ## More ways to write a number @@ -213,7 +213,7 @@ So, division by powers `10` is guaranteed to work well in the decimal system, bu There's just no way to store *exactly 0.1* or *exactly 0.2* using the binary system, just like there is no way to store one-third as a decimal fraction. -The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don't allow us to see that "tiny precision loss", so the number shows up as `0.3`. But beware, the loss still exists. +The numeric format IEEE-754 solves this by rounding to the nearest possible number. These rounding rules normally don't allow us to see that "tiny precision loss", but it exists. We can see this in action: ```js run @@ -417,7 +417,7 @@ To write big numbers: For different numeral systems: - Can write numbers directly in hex (`0x`), octal (`0o`) and binary (`0b`) systems -- `parseInt(str, base)` parses an integer from any numeral system with base: `2 ≤ base ≤ 36`. +- `parseInt(str, base)` parses the string `str` into an integer in numeral system with given `base`, `2 ≤ base ≤ 36`. - `num.toString(base)` converts a number to a string in the numeral system with the given `base`. For converting values like `12pt` and `100px` to a number: diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index e20bc2e02..bb17543d6 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -50,7 +50,7 @@ let guestList = "Guests: // Error: Unexpected token ILLEGAL Single and double quotes come from ancient times of language creation when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile. -Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`. The function `func` is called automatically, receives the string and embedded expressions and can process them. You can read more about it in the [docs](mdn:/JavaScript/Reference/Template_literals#Tagged_template_literals). This is called "tagged templates". This feature makes it easier to wrap strings into custom templating or other functionality, but it is rarely used. +Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`. The function `func` is called automatically, receives the string and embedded expressions and can process them. This is called "tagged templates". This feature makes it easier to implement custom templating, but is rarely used in practice. You can read more about it in the [manual](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). ## Special characters @@ -86,7 +86,7 @@ Here's the full list: |`\\`|Backslash| |`\t`|Tab| |`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- kept for compatibility, not used nowadays. | -|`\xXX`|Unicode character with the given hexadimal unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.| +|`\xXX`|Unicode character with the given hexadecimal unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.| |`\uXXXX`|A unicode symbol with the hex code `XXXX` in UTF-16 encoding, for instance `\u00A9` -- is a unicode for the copyright symbol `©`. It must be exactly 4 hex digits. | |`\u{X…XXXXXX}` (1 to 6 hex characters)|A unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two unicode symbols, taking 4 bytes. This way we can insert long codes. | @@ -364,8 +364,8 @@ alert( "Hello".includes("Bye") ); // false The optional second argument of `str.includes` is the position to start searching from: ```js run -alert( "Midget".includes("id") ); // true -alert( "Midget".includes("id", 3) ); // false, from position 3 there is no "id" +alert( "Widget".includes("id") ); // true +alert( "Widget".includes("id", 3) ); // false, from position 3 there is no "id" ``` The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say: @@ -538,10 +538,10 @@ Luckily, all modern browsers (IE10- requires the additional library [Intl.JS](ht It provides a special method to compare strings in different languages, following their rules. -The call [str.localeCompare(str2)](mdn:js/String/localeCompare) returns an integer indicating whether `str` comes before, after or is equivalent to `str2` according to the language rules: +The call [str.localeCompare(str2)](mdn:js/String/localeCompare) returns an integer indicating whether `str` is less, equal or greater than `str2` according to the language rules: -- Returns a negative number if `str` is less than `str2`, i.e. `str` occurs before `str2`. -- Returns a positive number if `str` is greater than `str2`, i.e. `str` occurs after `str2`. +- Returns a negative number if `str` is less than `str2`. +- Returns a positive number if `str` is greater than `str2`. - Returns `0` if they are equivalent. For instance: diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 22c5c02cd..7dc54bd4b 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -394,7 +394,7 @@ let matrix = [ [7, 8, 9] ]; -alert( matrix[1][1] ); // the central element +alert( matrix[1][1] ); // 5, the central element ``` ## toString diff --git a/1-js/05-data-types/05-array-methods/11-array-unique/solution.md b/1-js/05-data-types/05-array-methods/11-array-unique/solution.md index 32d3b2679..b9d627a0a 100644 --- a/1-js/05-data-types/05-array-methods/11-array-unique/solution.md +++ b/1-js/05-data-types/05-array-methods/11-array-unique/solution.md @@ -36,4 +36,4 @@ So if `arr.length` is `10000` we'll have something like `10000*10000` = 100 mill So the solution is only good for small arrays. -Further in the chapter we'll see how to optimize it. +Further in the chapter we'll see how to optimize it. diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js index 50c40e804..45ef1619d 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/_js.view/solution.js @@ -1,6 +1,6 @@ function Calculator() { - let methods = { + this.methods = { "-": (a, b) => a - b, "+": (a, b) => a + b }; @@ -12,14 +12,14 @@ function Calculator() { op = split[1], b = +split[2] - if (!methods[op] || isNaN(a) || isNaN(b)) { + if (!this.methods[op] || isNaN(a) || isNaN(b)) { return NaN; } - return methods[op](a, b); + return this.methods[op](a, b); } this.addMethod = function(name, func) { - methods[name] = func; + this.methods[name] = func; }; } diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md b/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md index 41178663d..982d3f9ed 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/solution.md @@ -1,3 +1,5 @@ -- Please note how methods are stored. They are simply added to the internal object. +- Please note how methods are stored. They are simply added to `this.methods` property. - All tests and numeric conversions are done in the `calculate` method. In future it may be extended to support more complex expressions. + +[js src="_js/solution.js"] diff --git a/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md b/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md index cc5453ceb..e0d302f4c 100644 --- a/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md +++ b/1-js/05-data-types/05-array-methods/6-calculator-extendable/task.md @@ -31,6 +31,6 @@ The task consists of two parts. alert( result ); // 8 ``` -- No brackets or complex expressions in this task. +- No parentheses or complex expressions in this task. - The numbers and the operator are delimited with exactly one space. - There may be error handling if you'd like to add it. diff --git a/1-js/05-data-types/05-array-methods/9-shuffle/solution.md b/1-js/05-data-types/05-array-methods/9-shuffle/solution.md index a43715db8..6674c444f 100644 --- a/1-js/05-data-types/05-array-methods/9-shuffle/solution.md +++ b/1-js/05-data-types/05-array-methods/9-shuffle/solution.md @@ -45,7 +45,7 @@ for (let key in count) { } ``` -An example result (for V8, July 2017): +An example result (depends on JS engine): ```js 123: 250706 @@ -68,7 +68,13 @@ There are other good ways to do the task. For instance, there's a great algorith function shuffle(array) { for (let i = array.length - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); // random index from 0 to i - [array[i], array[j]] = [array[j], array[i]]; // swap elements + + // swap elements array[i] and array[j] + // we use "destructuring assignment" syntax to achieve that + // you'll find more details about that syntax in later chapters + // same can be written as: + // let t = array[i]; array[i] = array[j]; array[j] = t + [array[i], array[j]] = [array[j], array[i]]; } } ``` 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 001989541..674f64fe5 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -119,29 +119,28 @@ The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking The syntax is: ```js -arr.slice(start, end) +arr.slice([start], [end]) ``` -It returns a new array containing 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 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 works like `str.slice`, but makes subarrays instead of substrings. +It's similar to a string method `str.slice`, but instead of substrings it makes subarrays. For instance: ```js run -let str = "test"; let arr = ["t", "e", "s", "t"]; -alert( str.slice(1, 3) ); // es -alert( arr.slice(1, 3) ); // e,s +alert( arr.slice(1, 3) ); // e,s (copy from 1 to 3) -alert( str.slice(-2) ); // st -alert( arr.slice(-2) ); // s,t +alert( arr.slice(-2) ); // s,t (copy from -2 till the end) ``` +We can also call it without arguments: `arr.slice()` creates a copy of `arr`. That's often used to obtain a copy for further transformations that should not affect the original array. + ### concat -The method [arr.concat](mdn:js/Array/concat) joins the array with other arrays and/or items. +The method [arr.concat](mdn:js/Array/concat) creates a new array that includes values from other arrays and additional items. The syntax is: @@ -153,24 +152,24 @@ It accepts any number of arguments -- either arrays or values. The result is a new array containing items from `arr`, then `arg1`, `arg2` etc. -If an argument is an array or has `Symbol.isConcatSpreadable` property, then all its elements are copied. Otherwise, the argument itself is copied. +If an argument `argN` is an array, then all its elements are copied. Otherwise, the argument itself is copied. For instance: ```js run let arr = [1, 2]; -// merge arr with [3,4] -alert( arr.concat([3, 4])); // 1,2,3,4 +// create an array from: arr and [3,4] +alert( arr.concat([3, 4]) ); // 1,2,3,4 -// merge arr with [3,4] and [5,6] -alert( arr.concat([3, 4], [5, 6])); // 1,2,3,4,5,6 +// create an array from: arr and [3,4] and [5,6] +alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6 -// merge arr with [3,4], then add values 5 and 6 -alert( arr.concat([3, 4], 5, 6)); // 1,2,3,4,5,6 +// create an array from: arr and [3,4], then add values 5 and 6 +alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6 ``` -Normally, it only copies elements from arrays ("spreads" them). Other objects, even if they look like arrays, added as a whole: +Normally, it only copies elements from arrays. Other objects, even if they look like arrays, are added as a whole: ```js run let arr = [1, 2]; @@ -181,10 +180,9 @@ let arrayLike = { }; alert( arr.concat(arrayLike) ); // 1,2,[object Object] -//[1, 2, arrayLike] ``` -...But if an array-like object has `Symbol.isConcatSpreadable` property, then its elements are added instead: +...But if an array-like object has a special `Symbol.isConcatSpreadable` property, then it's treated as an array by `concat`: its elements are added instead: ```js run let arr = [1, 2]; @@ -232,7 +230,7 @@ The result of the function (if it returns any) is thrown away and ignored. ## Searching in array -These are methods to search for something in an array. +Now let's cover methods that search in an array. ### indexOf/lastIndexOf and includes @@ -280,7 +278,7 @@ let result = arr.find(function(item, index, array) { }); ``` -The function is called repetitively for each element of the array: +The function is called for elements of the array, one after another: - `item` is the element. - `index` is its index. @@ -304,7 +302,7 @@ alert(user.name); // John In real life arrays of objects is 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. Other arguments of this function are rarely used. +Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. That's typical, other arguments of this function are rarely used. The [arr.findIndex](mdn:js/Array/findIndex) method is essentially the same, but it returns the index where the element was found instead of the element itself and `-1` is returned when nothing is found. @@ -314,12 +312,12 @@ The `find` method looks for a single (first) element that makes the function ret If there may be many, we can use [arr.filter(fn)](mdn:js/Array/filter). -The syntax is similar to `find`, but filter continues to iterate for all array elements even if `true` is already returned: +The syntax is similar to `find`, but `filter` returns an array of all matching elements: ```js let results = arr.filter(function(item, index, array) { - // if true item is pushed to results and iteration continues - // returns empty array for complete falsy scenario + // if true item is pushed to results and the iteration continues + // returns empty array if nothing found }); ``` @@ -340,23 +338,22 @@ alert(someUsers.length); // 2 ## Transform an array -This section is about the methods transforming or reordering the array. - +Let's move on to methods that transform and reorder an array. ### map The [arr.map](mdn:js/Array/map) method is one of the most useful and often used. +It calls the function for each element of the array and returns the array of results. + The syntax is: ```js let result = arr.map(function(item, index, array) { // returns the new value instead of item -}) +}); ``` -It calls the function for each element of the array and returns the array of results. - For instance, here we transform each element into its length: ```js run @@ -366,14 +363,16 @@ alert(lengths); // 5,7,6 ### sort(fn) -The method [arr.sort](mdn:js/Array/sort) sorts the array *in place*. +The call to [arr.sort()](mdn:js/Array/sort) sorts the array *in place*, changing its element order. + +It also returns the sorted array, but the returned value is usually ignored, as `arr` itself is modified. For instance: ```js run let arr = [ 1, 2, 15 ]; -// the method reorders the content of arr (and returns it) +// the method reorders the content of arr arr.sort(); alert( arr ); // *!*1, 15, 2*/!* @@ -385,20 +384,20 @@ The order became `1, 15, 2`. Incorrect. But why? **The items are sorted as strings by default.** -Literally, all elements are converted to strings and then compared. So, the lexicographic ordering is applied and indeed `"2" > "15"`. +Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed `"2" > "15"`. -To use our own sorting order, we need to supply a function of two arguments as the argument of `arr.sort()`. +To use our own sorting order, we need to supply a function as the argument of `arr.sort()`. -The function should work like this: +The function should compare two arbitrary values and return: ```js function compare(a, b) { - if (a > b) return 1; - if (a == b) return 0; - if (a < b) return -1; + if (a > b) return 1; // if the first value is greater than the second + if (a == b) return 0; // if values are equal + if (a < b) return -1; // if the first value is less than the second } ``` -For instance: +For instance, to sort as numbers: ```js run function compareNumeric(a, b) { @@ -418,9 +417,9 @@ 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 HTML elements or whatever. We have a set of *something*. 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 what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order. -The `arr.sort(fn)` method has a built-in implementation of sorting algorithm. We don't need to care how it exactly works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison. +The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) 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: @@ -430,7 +429,7 @@ By the way, if we ever want to know which elements are compared -- nothing preve }); ``` -The algorithm may compare an element multiple times in the process, but it tries to make as few comparisons as possible. +The algorithm may compare an element with multiple others in the process, but it tries to make as few comparisons as possible. ````smart header="A comparison function may return any number" @@ -454,7 +453,7 @@ Remember [arrow functions](info:function-expressions-arrows#arrow-functions)? We arr.sort( (a, b) => a - b ); ``` -This works exactly the same as the other, longer, version above. +This works exactly the same as the longer version above. ```` ### reverse @@ -508,14 +507,14 @@ alert( str.split('') ); // t,e,s,t ``` ```` -The call [arr.join(separator)](mdn:js/Array/join) does the reverse to `split`. It creates a string of `arr` items glued by `separator` between them. +The call [arr.join(glue)](mdn:js/Array/join) does the reverse to `split`. It creates a string of `arr` items joined by `glue` between them. For instance: ```js run let arr = ['Bilbo', 'Gandalf', 'Nazgul']; -let str = arr.join(';'); +let str = arr.join(';'); // glue the array into a string using ; alert( str ); // Bilbo;Gandalf;Nazgul ``` @@ -533,18 +532,21 @@ The syntax is: ```js let value = arr.reduce(function(previousValue, item, index, array) { // ... -}, initial); +}, [initial]); ``` -The function is applied to the elements. You may notice the familiar arguments, starting from the 2nd: +The function is applied to all array elements one after another and "carries on" its result to the next call. +Arguments: + +- `previousValue` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided). - `item` -- is the current array item. - `index` -- is its position. - `array` -- is the array. -So far, like `forEach/map`. But there's one more argument: +As function is applied, the result of the previous function call is passed to the next one as the first argument. -- `previousValue` -- is the result of the previous function call, `initial` for the first call. +Sounds complicated, but it's not if you think about the first argument as the "accumulator" that stores the combined result of all previous execution. And at the end it becomes the result of `reduce`. The easiest way to grasp that is by example. @@ -558,11 +560,11 @@ let result = arr.reduce((sum, current) => sum + current, 0); alert(result); // 15 ``` -Here we used the most common variant of `reduce` which uses only 2 arguments. +The function passed to `reduce` uses only 2 arguments, that's typically enough. Let's see the details of what's going on. -1. On the first run, `sum` is the initial value (the last argument of `reduce`), equals `0`, and `current` is the first array element, equals `1`. So the result is `1`. +1. On the first run, `sum` is the `initial` value (the last argument of `reduce`), equals `0`, and `current` is the first array element, equals `1`. So the function result is `1`. 2. On the second run, `sum = 1`, we add the second array element (`2`) to it and return. 3. On the 3rd run, `sum = 3` and we add one more element to it, and so on... @@ -580,8 +582,7 @@ Or in the form of a table, where each row represents a function call on the next |the fourth call|`6`|`4`|`10`| |the fifth call|`10`|`5`|`15`| - -As we can see, the result of the previous call becomes the first argument of the next one. +Here we can clearly see how the result of the previous call becomes the first argument of the next one. We also can omit the initial value: @@ -653,31 +654,37 @@ arr.map(func, thisArg); The value of `thisArg` parameter becomes `this` for `func`. -For instance, here we use an object method as a filter and `thisArg` comes in handy: +For example, here we use a method of `army` object as a filter, and `thisArg` passes the context: ```js run -let user = { - age: 18, - younger(otherUser) { - return otherUser.age < this.age; +let army = { + minAge: 18, + maxAge: 27, + canJoin(user) { + return user.age >= this.minAge && user.age < this.maxAge; } }; let users = [ - {age: 12}, {age: 16}, - {age: 32} + {age: 20}, + {age: 23}, + {age: 30} ]; *!* -// find all users younger than user -let youngerUsers = users.filter(user.younger, user); +// find users, for who army.canJoin returns true +let soldiers = users.filter(army.canJoin, army); */!* -alert(youngerUsers.length); // 2 +alert(soldiers.length); // 2 +alert(soldiers[0].age); // 20 +alert(soldiers[1].age); // 23 ``` -In the call above, we use `user.younger` as a filter and also provide `user` as the context for it. If we didn't provide the context, `users.filter(user.younger)` would call `user.younger` as a standalone function, with `this=undefined`. That would mean an instant error. +If in the example above we used `users.filter(army.canJoin)`, then `army.canJoin` would be called as a standalone function, with `this=undefined`, thus leading to an instant error. + +A call to `users.filter(army.canJoin, army)` can be replaced with `users.filter(user => army.canJoin(user))`, that does the same. The former is used more often, as it's a bit easier to understand for most people. ## Summary @@ -697,7 +704,7 @@ A cheat sheet of array methods: - `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. - + - To iterate over elements: - `forEach(func)` -- calls `func` for every element, does not return anything. @@ -725,7 +732,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 than it seems. +From the 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 474d0463d..80f067ca8 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -3,9 +3,9 @@ *Iterable* objects is a generalization of arrays. That's a concept that allows to make any object useable in a `for..of` loop. -Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, Strings are iterable also. As we'll see, many built-in operators and methods rely on them. +Of course, Arrays are iterable. But there are many other built-in objects, that are iterable as well. For instance, strings are also iterable. -If an object represents a collection (list, set) of something, then `for..of` is a great syntax to loop over it, so let's see how to make it work. +If an object isn't technically an array, but represents a collection (list, set) of something, then `for..of` is a great syntax to loop over it, so let's see how to make it work. ## Symbol.iterator @@ -31,9 +31,9 @@ To make the `range` iterable (and thus let `for..of` work) we need to add a meth 1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`. 2. Onward, `for..of` works *only with that returned object*. 3. When `for..of` wants the next value, it calls `next()` on that object. -4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` must be the new value. +4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` is the next value. -Here's the full implementation for `range`: +Here's the full implementation for `range` with remarks: ```js run let range = { @@ -68,10 +68,10 @@ for (let num of range) { } ``` -Please note the core feature of iterables: an important separation of concerns: +Please note the core feature of iterables: separation of concerns. - The `range` itself does not have the `next()` method. -- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`, and it handles the whole iteration. +- Instead, another object, a so-called "iterator" is created by the call to `range[Symbol.iterator]()`, and its `next()` generates values for the iteration. So, the iterator object is separate from the object it iterates over. @@ -140,9 +140,7 @@ for (let char of str) { ## Calling an iterator explicitly -Normally, internals of iterables are hidden from the external code. There's a `for..of` loop, that works, that's all it needs to know. - -But to understand things a little bit deeper let's see how to create an iterator explicitly. +For deeper understanding let's see how to use an iterator explicitly. We'll iterate over a string in exactlly the same way as `for..of`, but with direct calls. This code creates a string iterator and gets values from it "manually": @@ -283,7 +281,7 @@ let str = '𝒳😂𩷶'; alert( slice(str, 1, 3) ); // 😂𩷶 -// native method does not support surrogate pairs +// the native method does not support surrogate pairs alert( str.slice(1, 3) ); // garbage (two pieces from different surrogate pairs) ``` 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 044d6392e..c4d7c21a4 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -92,35 +92,6 @@ map.set('1', 'str1') ``` ```` -## Map from Object - -When a `Map` is created, we can pass an array (or another iterable) with key-value pairs for initialization, like this: - -```js -// array of [key, value] pairs -let map = new Map([ - ['1', 'str1'], - [1, 'num1'], - [true, 'bool1'] -]); -``` - -If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format. - -So we can initialize a map from an object like this: - -```js -let obj = { - name: "John", - age: 30 -}; - -*!* -let map = new Map(Object.entries(obj)); -*/!* -``` - -Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs. ## Iteration over Map @@ -168,6 +139,90 @@ recipeMap.forEach( (value, key, map) => { }); ``` +## Object.entries: Map from Object + +When a `Map` is created, we can pass an array (or another iterable) with key/value pairs for initialization, like this: + +```js run +// array of [key, value] pairs +let map = new Map([ + ['1', 'str1'], + [1, 'num1'], + [true, 'bool1'] +]); + +alert( map.get('1') ); // str1 +``` + +If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format. + +So we can create a map from an object like this: + +```js run +let obj = { + name: "John", + age: 30 +}; + +*!* +let map = new Map(Object.entries(obj)); +*/!* + +alert( map.get('name') ); // John +``` + +Here, `Object.entries` returns the array of key/value pairs: `[ ["name","John"], ["age", 30] ]`. That's what `Map` needs. + + +## Object.fromEntries: Object from Map + +We've just seen how to create `Map` from a plain object with `Object.entries(obj)`. + +There's `Object.fromEntries` method that does the reverse: given an array of `[key, value]` pairs, it creates an object from them: + +```js run +let prices = Object.fromEntries([ + ['banana', 1], + ['orange', 2], + ['meat', 4] +]); + +// now prices = { banana: 1, orange: 2, meat: 4 } + +alert(prices.orange); // 2 +``` + +We can use `Object.fromEntries` to get an plain object from `Map`. + +E.g. we store the data in a `Map`, but we need to pass it to a 3rd-party code that expects a plain object. + +Here we go: + +```js run +let map = new Map(); +map.set('banana', 1); +map.set('orange', 2); +map.set('meat', 4); + +*!* +let obj = Object.fromEntries(map.entries()); // make a plain object (*) +*/!* + +// done! +// obj = { banana: 1, orange: 2, meat: 4 } + +alert(obj.orange); // 2 +``` + +A call to `map.entries()` returns an array of key/value pairs, exactly in the right format for `Object.fromEntries`. + +We could also make line `(*)` shorter: +```js +let obj = Object.fromEntries(map); // omit .entries() +``` + +That's the same, because `Object.fromEntries` expects an iterable object as the argument. Not necessarily an array. And the standard iteration for `map` returns same key/value pairs as `map.entries()`. So we get a plain object with same key/values as the `map`. + ## Set A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index c2b09da59..11ff9d5eb 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,7 +1,5 @@ # WeakMap and WeakSet -`WeakSet` is a special kind of `Set` that does not prevent JavaScript from removing its items from memory. `WeakMap` is the same thing for `Map`. - As we know from the chapter , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). For instance: @@ -55,9 +53,9 @@ john = null; // overwrite the reference */!* ``` -`WeakMap/WeakSet` are fundamentally different in this aspect. They do not prevent garbage-collection of key objects. +`WeakMap` is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. -Let's explain it starting with `WeakMap`. +Let's see what it means on examples. ## WeakMap @@ -110,7 +108,7 @@ Now where do we need such data structure? The main area of application for `WeakMap` is an *additional data storage*. -If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then `WeakMap` is the right choice! +If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then `WeakMap` is exactly what's needed. We put the data to a `WeakMap`, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well. @@ -132,7 +130,7 @@ let visitsCountMap = new Map(); // map: user => visits count // increase the visits count function countUser(user) { let count = visitsCountMap.get(user) || 0; - visitsCountMap.set(count + 1); + visitsCountMap.set(user, count + 1); } ``` @@ -149,7 +147,7 @@ countUser(john); john = null; ``` -Now, we have a problem: `john` object should be garbage collected, but remains is memory, as it's a key in `visitsCountMap`. +Now `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures. @@ -157,22 +155,22 @@ We can avoid it by switching to `WeakMap` instead: ```js // 📁 visitsCount.js -let visitsCountMap = new WeakMap(); // map: user => visits count +let visitsCountMap = new WeakMap(); // weakmap: user => visits count // increase the visits count function countUser(user) { let count = visitsCountMap.get(user) || 0; - visitsCountMap.set(count + 1); + visitsCountMap.set(user, count + 1); } ``` -Now we don't have to clean `visitsCountMap`. After `john` is removed from memory, the additionally stored information from `WeakMap` will be removed as well. +Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`. ## Use case: caching Another common example is caching: when a function result should be remembered ("cached"), so that future calls on the same object reuse it. -We can use `Map` for it, like this: +We can use `Map` to store results, like this: ```js run // 📁 cache.js @@ -181,7 +179,7 @@ let cache = new Map(); // calculate and remember the result function process(obj) { if (!cache.has(obj)) { - let result = /* calculate the result for */ obj; + let result = /* calculations of the result for */ obj; cache.set(obj, result); } @@ -190,25 +188,26 @@ function process(obj) { } *!* -// Usage in another file: +// Now we use process() in another file: */!* + // 📁 main.js -let obj = {/* some object */}; +let obj = {/* let's say we have an object */}; let result1 = process(obj); // calculated // ...later, from another place of the code... -let result2 = process(obj); // taken from cache +let result2 = process(obj); // remembered result taken from cache // ...later, when the object is not needed any more: obj = null; -alert(cache.size); // 1 (Ouch! It's still in cache, taking memory!) +alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!) ``` -Now for multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more. +For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more. -If we replace `Map` with `WeakMap`, then the cached result will be removed from memory automatically after the object gets garbage collected: +If we replace `Map` with `WeakMap`, then this problem disappears: the cached result will be removed from memory automatically after the object gets garbage collected. ```js run // 📁 cache.js @@ -236,7 +235,8 @@ let result2 = process(obj); // ...later, when the object is not needed any more: obj = null; -// Can't get cache.size, as it's a WeakMap, but it's 0 or soon be 0 +// Can't get cache.size, as it's a WeakMap, +// but it's 0 or soon be 0 // When obj gets garbage collected, cached data will be removed as well ``` @@ -250,7 +250,7 @@ obj = null; Being "weak", it also serves as an additional storage. But not for an arbitrary data, but rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. -For instance, we can use `WeakSet` to keep track of users that visited our site: +For instance, we can add users to `WeakSet` to keep track of those who visited our site: ```js run let visitedSet = new WeakSet(); @@ -282,8 +282,8 @@ The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterati `WeakMap` is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. -`WeakSet` is `Set`-like collection that only stores objects and removes them once they become inaccessible by other means. +`WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. -Both of them do not support methods and properties that refer to all keys or their count. Only individial operations are allowed. +Both of them do not support methods and properties that refer to all keys or their count. Only individual operations are allowed. `WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. diff --git a/1-js/05-data-types/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md index 3780b33e5..9fe6d3bcb 100644 --- a/1-js/05-data-types/09-keys-values-entries/article.md +++ b/1-js/05-data-types/09-keys-values-entries/article.md @@ -23,7 +23,7 @@ For plain objects, the following methods are available: - [Object.values(obj)](mdn:js/Object/values) -- returns an array of values. - [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of `[key, value]` pairs. -...But please note the distinctions (compared to map for example): +Please note the distinctions (compared to map for example): | | Map | Object | |-------------|------------------|--------------| @@ -32,7 +32,7 @@ For plain objects, the following methods are available: The first difference is that we have to call `Object.keys(obj)`, and not `obj.keys()`. -Why so? The main reason is flexibility. Remember, objects are a base of all complex structures in JavaScript. So we may have an object of our own like `order` that implements its own `order.values()` method. And we still can call `Object.values(order)` on it. +Why so? The main reason is flexibility. Remember, objects are a base of all complex structures in JavaScript. So we may have an object of our own like `data` that implements its own `data.values()` method. And we still can call `Object.values(data)` on it. The second difference is that `Object.*` methods return "real" array objects, not just an iterable. That's mainly for historical reasons. @@ -69,52 +69,18 @@ Just like a `for..in` loop, these methods ignore properties that use `Symbol(... Usually that's convenient. But if we want symbolic keys too, then there's a separate method [Object.getOwnPropertySymbols](mdn:js/Object/getOwnPropertySymbols) that returns an array of only symbolic keys. Also, there exist a method [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys. ``` -## Object.fromEntries to transform objects -Sometimes we need to perform a transformation of an object to `Map` and back. +## Transforming objects -We already have `new Map(Object.entries(obj))` to make a `Map` from `obj`. +Objects lack many methods that exist for arrays, e.g. `map`, `filter` and others. -The syntax of `Object.fromEntries` does the reverse. Given an array of `[key, value]` pairs, it creates an object: +If we'd like to apply them, then we can use `Object.entries` followed `Object.fromEntries`: -```js run -let prices = Object.fromEntries([ - ['banana', 1], - ['orange', 2], - ['meat', 4] -]); - -// now prices = { banana: 1, orange: 2, meat: 4 } - -alert(prices.orange); // 2 -``` - -Let's see practical applications. - -For example, we'd like to create a new object with double prices from the existing one. - -For arrays, we have `.map` method that allows to transform an array, but nothing like that for objects. - -So we can use a loop: - -```js run -let prices = { - banana: 1, - orange: 2, - meat: 4, -}; +1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`. +2. Use array methods on that array, e.g. `map`. +3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object. -let doublePrices = {}; -for(let [product, price] of Object.entries(prices)) { - doublePrices[product] = price * 2; -} - -alert(doublePrices.meat); // 8 -``` - -...Or we can represent the object as an `Array` using `Object.entries`, then perform the operations with `map` (and potentially other array methods), and then go back using `Object.fromEntries`. - -Let's do it for our object: +For example, we have an object with prices, and would like to double them: ```js run let prices = { @@ -133,23 +99,4 @@ let doublePrices = Object.fromEntries( alert(doublePrices.meat); // 8 ``` -It may look difficult from the first sight, but becomes easy to understand after you use it once or twice. - -We also can use `fromEntries` to get an object from `Map`. - -E.g. we have a `Map` of prices, but we need to pass it to a 3rd-party code that expects an object. - -Here we go: - -```js run -let map = new Map(); -map.set('banana', 1); -map.set('orange', 2); -map.set('meat', 4); - -let obj = Object.fromEntries(map); - -// now obj = { banana: 1, orange: 2, meat: 4 } - -alert(obj.orange); // 2 -``` +It may look difficult from the first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way. 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 ddd3f1672..d069ccf03 100644 --- a/1-js/05-data-types/10-destructuring-assignment/article.md +++ b/1-js/05-data-types/10-destructuring-assignment/article.md @@ -262,7 +262,7 @@ alert(height); // 200 Just like with arrays or function parameters, default values can be any expressions or even function calls. They will be evaluated if the value is not provided. -The code below asks for width, but not the title. +In the code below `prompt` asks for `width`, but not for `title`: ```js run let options = { @@ -293,6 +293,21 @@ alert(w); // 100 alert(h); // 200 ``` +If we have a complex object with many properties, we can extract only what we need: + +```js run +let options = { + title: "Menu", + width: 100, + height: 200 +}; + +// only extract title as a variable +let { title } = options; + +alert(title); // Menu +``` + ### The rest pattern "..." What if the object has more properties than we have variables? Can we take some and then assign the "rest" somewhere? @@ -319,8 +334,6 @@ alert(rest.height); // 200 alert(rest.width); // 100 ``` - - ````smart header="Gotcha if there's no `let`" In the examples above variables were declared right in the assignment: `let {…} = {…}`. Of course, we could use existing variables too, without `let`. But there's a catch. @@ -343,7 +356,9 @@ The problem is that JavaScript treats `{...}` in the main code flow (not inside } ``` -To show JavaScript that it's not a code block, we can make it a part of an expression by wrapping in parentheses `(...)`: +So here JavaScript assumes that we have a code block, that's why there's an error. We want destructuring instead. + +To show JavaScript that it's not a code block, we can wrap the expression in parentheses `(...)`: ```js run let title, width, height; @@ -353,14 +368,13 @@ let title, width, height; alert( title ); // Menu ``` - ```` ## Nested destructuring -If an object or an array contain other objects and arrays, we can use more complex left-side patterns to extract deeper portions. +If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions. -In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure: +In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure to extract values from them: ```js run let options = { @@ -369,7 +383,7 @@ let options = { height: 200 }, items: ["Cake", "Donut"], - extra: true // something extra that we will not destruct + extra: true }; // destructuring assignment split in multiple lines for clarity @@ -389,20 +403,13 @@ alert(item1); // Cake alert(item2); // Donut ``` -The whole `options` object except `extra` that was not mentioned, is assigned to corresponding variables. - -Note that `size` and `items` itself is not destructured. +All properties of `options` object except `extra` that is absent in the left part, are assigned to corresponding variables: ![](destructuring-complex.svg) Finally, we have `width`, `height`, `item1`, `item2` and `title` from the default value. -If we have a complex object with many properties, we can extract only what we need: - -```js -// take size as a whole into a variable, ignore the rest -let { size } = options; -``` +Note that there are no variables for `size` and `items`, as we take their content instead. ## Smart function parameters @@ -421,6 +428,7 @@ In real-life, the problem is how to remember the order of arguments. Usually IDE Like this? ```js +// undefined where default values are fine showMenu("My Menu", undefined, undefined, ["Item1", "Item2"]) ``` @@ -472,29 +480,28 @@ function showMenu({ showMenu(options); ``` -The syntax is the same as for a destructuring assignment: +The full syntax is the same as for a destructuring assignment: ```js function({ - incomingProperty: parameterName = defaultValue + incomingProperty: varName = defaultValue ... }) ``` +Then, for an object of parameters, there will be a variable `varName` for 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: ```js -showMenu({}); - +showMenu({}); // ok, all values are default showMenu(); // this would give an error ``` -We can fix this by making `{}` the default value for the whole destructuring thing: - +We can fix this by making `{}` the default value for the whole object of parameters: ```js run -// simplified parameters a bit for clarity -function showMenu(*!*{ title = "Menu", width = 100, height = 200 } = {}*/!*) { +function showMenu({ title = "Menu", width = 100, height = 200 }*!* = {}*/!*) { alert( `${title} ${width} ${height}` ); } @@ -506,7 +513,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw ## Summary - Destructuring assignment allows for instantly mapping an object or array onto many variables. -- The object syntax: +- The full object syntax: ```js let {prop : varName = default, ...rest} = object ``` @@ -515,7 +522,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw Object properties that have no mapping are copied to the `rest` object. -- The array syntax: +- The full array syntax: ```js let [item1 = default, item2, ...rest] = array @@ -523,4 +530,4 @@ In the code above, the whole arguments object is `{}` by default, so there's alw The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`. -- For more complex cases, the left side must have the same structure as the right one. +- 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/8-format-date-relative/task.md b/1-js/05-data-types/11-date/8-format-date-relative/task.md index 7b341ca2e..4dc067375 100644 --- a/1-js/05-data-types/11-date/8-format-date-relative/task.md +++ b/1-js/05-data-types/11-date/8-format-date-relative/task.md @@ -20,6 +20,6 @@ alert( formatDate(new Date(new Date - 30 * 1000)) ); // "30 sec. ago" alert( formatDate(new Date(new Date - 5 * 60 * 1000)) ); // "5 min. ago" -// yesterday's date like 31.12.2016, 20:00 +// yesterday's date like 31.12.16, 20:00 alert( formatDate(new Date(new Date - 86400 * 1000)) ); ``` diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index 512bc7120..4f80f752b 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -29,13 +29,12 @@ To create a new `Date` object call `new Date()` with one of the following argume alert( Jan02_1970 ); ``` - The number of milliseconds that has passed since the beginning of 1970 is called a *timestamp*. + An integer number representing the number of milliseconds that has passed since the beginning of 1970 is called a *timestamp*. It's a lightweight numeric representation of a date. We can always create a date from a timestamp using `new Date(timestamp)` and convert the existing `Date` object to a timestamp using the `date.getTime()` method (see below). `new Date(datestring)` -: If there is a single argument, and it's a string, then it is parsed with the `Date.parse` algorithm (see below). - +: If there is a single argument, and it's a string, then it is parsed automatically. The algorithm is the same as `Date.parse` uses, we'll cover it later. ```js run let date = new Date("2017-01-26"); @@ -131,12 +130,12 @@ Besides the given methods, there are two special ones that do not have a UTC-var The following methods allow to set date/time components: -- [`setFullYear(year [, month, date])`](mdn:js/Date/setFullYear) -- [`setMonth(month [, date])`](mdn:js/Date/setMonth) +- [`setFullYear(year, [month], [date])`](mdn:js/Date/setFullYear) +- [`setMonth(month, [date])`](mdn:js/Date/setMonth) - [`setDate(date)`](mdn:js/Date/setDate) -- [`setHours(hour [, min, sec, ms])`](mdn:js/Date/setHours) -- [`setMinutes(min [, sec, ms])`](mdn:js/Date/setMinutes) -- [`setSeconds(sec [, ms])`](mdn:js/Date/setSeconds) +- [`setHours(hour, [min], [sec], [ms])`](mdn:js/Date/setHours) +- [`setMinutes(min, [sec], [ms])`](mdn:js/Date/setMinutes) +- [`setSeconds(sec, [ms])`](mdn:js/Date/setSeconds) - [`setMilliseconds(ms)`](mdn:js/Date/setMilliseconds) - [`setTime(milliseconds)`](mdn:js/Date/setTime) (sets the whole date by milliseconds since 01.01.1970 UTC) @@ -318,7 +317,7 @@ As a result, the first benchmark will have less CPU resources than the second. T **For more reliable benchmarking, the whole pack of benchmarks should be rerun multiple times.** -Here's the code example: +For example, like this: ```js run function diffSubtract(date1, date2) { @@ -424,4 +423,4 @@ alert(`Loading started ${performance.now()}ms ago`); // more than 3 digits after the decimal point are precision errors, but only the first 3 are correct ``` -Node.js has `microtime` module and other ways. Technically, any device and environment allows to get more precision, it's just not in `Date`. +Node.js has `microtime` module and other ways. Technically, almost any device and environment allows to get more precision, it's just not in `Date`. diff --git a/1-js/05-data-types/12-json/2-serialize-event-circular/task.md b/1-js/05-data-types/12-json/2-serialize-event-circular/task.md index 8b3963ddf..3755a24aa 100644 --- a/1-js/05-data-types/12-json/2-serialize-event-circular/task.md +++ b/1-js/05-data-types/12-json/2-serialize-event-circular/task.md @@ -6,7 +6,7 @@ importance: 5 In simple cases of circular references, we can exclude an offending property from serialization by its name. -But sometimes there are many backreferences. And names may be used both in circular references and normal properties. +But sometimes we can't just use the name, as it may be used both in circular references and normal properties. So we can check the property by its value. Write `replacer` function to stringify everything, but remove properties that reference `meetup`: @@ -22,7 +22,7 @@ let meetup = { }; *!* -// circular references +// circular references room.occupiedBy = meetup; meetup.self = meetup; */!* @@ -39,4 +39,3 @@ alert( JSON.stringify(meetup, function replacer(key, value) { } */ ``` - diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index ec0844e3d..2942cdd86 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -21,7 +21,7 @@ let user = { alert(user); // {name: "John", age: 30} ``` -...But in the process of development, new properties are added, old properties are renamed and removed. Updating such `toString` every time can become a pain. We could try to loop over properties in it, but what if the object is complex and has nested objects in properties? We'd need to implement their conversion as well. And, if we're sending the object over a network, then we also need to supply the code to "read" our object on the receiving side. +...But in the process of development, new properties are added, old properties are renamed and removed. Updating such `toString` every time can become a pain. We could try to loop over properties in it, but what if the object is complex and has nested objects in properties? We'd need to implement their conversion as well. Luckily, there's no need to write the code to handle all this. The task has been solved already. @@ -76,7 +76,7 @@ Please note that a JSON-encoded object has several important differences from th `JSON.stringify` can be applied to primitives as well. -Natively supported JSON types are: +JSON supports following data types: - Objects `{ ... }` - Arrays `[ ... ]` @@ -100,7 +100,7 @@ alert( JSON.stringify(true) ); // true alert( JSON.stringify([1, 2, 3]) ); // [1,2,3] ``` -JSON is data-only cross-language specification, so some JavaScript-specific object properties are skipped by `JSON.stringify`. +JSON is data-only language-independent specification, so some JavaScript-specific object properties are skipped by `JSON.stringify`. Namely: @@ -213,9 +213,9 @@ alert( JSON.stringify(meetup, *!*['title', 'participants']*/!*) ); // {"title":"Conference","participants":[{},{}]} ``` -Here we are probably too strict. The property list is applied to the whole object structure. So participants are empty, because `name` is not in the list. +Here we are probably too strict. The property list is applied to the whole object structure. So the objects in `participants` are empty, because `name` is not in the list. -Let's include every property except `room.occupiedBy` that would cause the circular reference: +Let's include in the list every property except `room.occupiedBy` that would cause the circular reference: ```js run let room = { @@ -244,7 +244,7 @@ Now everything except `occupiedBy` is serialized. But the list of properties is Fortunately, we can use a function instead of an array as the `replacer`. -The function will be called for every `(key, value)` pair and should return the "replaced" value, which will be used instead of the original one. +The function will be called for every `(key, value)` pair and should return the "replaced" value, which will be used instead of the original one. Or `undefined` if the value is to be skipped. In our case, we can return `value` "as is" for everything except `occupiedBy`. To ignore `occupiedBy`, the code below returns `undefined`: @@ -262,7 +262,7 @@ let meetup = { room.occupiedBy = meetup; // room references meetup alert( JSON.stringify(meetup, function replacer(key, value) { - alert(`${key}: ${value}`); // to see what replacer gets + alert(`${key}: ${value}`); return (key == 'occupiedBy') ? undefined : value; })); @@ -283,16 +283,16 @@ Please note that `replacer` function gets every key/value pair including nested The first call is special. It is made using a special "wrapper object": `{"": meetup}`. In other words, the first `(key, value)` pair has an empty key, and the value is the target object as a whole. That's why the first line is `":[object Object]"` in the example above. -The idea is to provide as much power for `replacer` as possible: it has a chance to analyze and replace/skip the whole object if necessary. +The idea is to provide as much power for `replacer` as possible: it has a chance to analyze and replace/skip even the whole object if necessary. -## Formatting: spacer +## Formatting: space -The third argument of `JSON.stringify(value, replacer, spaces)` is the number of spaces to use for pretty formatting. +The third argument of `JSON.stringify(value, replacer, space)` is the number of spaces to use for pretty formatting. -Previously, all stringified objects had no indents and extra spaces. That's fine if we want to send an object over a network. The `spacer` argument is used exclusively for a nice output. +Previously, all stringified objects had no indents and extra spaces. That's fine if we want to send an object over a network. The `space` argument is used exclusively for a nice output. -Here `spacer = 2` tells JavaScript to show nested objects on multiple lines, with indentation of 2 spaces inside an object: +Here `space = 2` tells JavaScript to show nested objects on multiple lines, with indentation of 2 spaces inside an object: ```js run let user = { @@ -328,7 +328,7 @@ alert(JSON.stringify(user, null, 2)); */ ``` -The `spaces` parameter is used solely for logging and nice-output purposes. +The `space` parameter is used solely for logging and nice-output purposes. ## Custom "toJSON" @@ -393,7 +393,7 @@ alert( JSON.stringify(meetup) ); */ ``` -As we can see, `toJSON` is used both for the direct call `JSON.stringify(room)` and for the nested object. +As we can see, `toJSON` is used both for the direct call `JSON.stringify(room)` and when `room` is nested in another encoded object. ## JSON.parse @@ -402,7 +402,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 @@ -432,7 +432,7 @@ user = JSON.parse(user); alert( user.friends[1] ); // 1 ``` -The JSON may be as complex as necessary, objects and arrays can include other objects and arrays. But they must obey the format. +The JSON may be as complex as necessary, objects and arrays can include other objects and arrays. But they must obey the same JSON format. Here are typical mistakes in hand-written JSON (sometimes we have to write it for debugging purposes): @@ -481,7 +481,7 @@ Whoops! An error! The value of `meetup.date` is a string, not a `Date` object. How could `JSON.parse` know that it should transform that string into a `Date`? -Let's pass to `JSON.parse` the reviving function that returns all values "as is", but `date` will become a `Date`: +Let's pass to `JSON.parse` the reviving function as the second argument, that returns all values "as is", but `date` will become a `Date`: ```js run let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}'; diff --git a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md index 4e9de1469..cfcbffea5 100644 --- a/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md +++ b/1-js/06-advanced-functions/01-recursion/04-output-single-linked-list/solution.md @@ -43,7 +43,7 @@ function printList(list) { } ``` -...But that would be unwise. In the future we may need to extend a function, do something else with the list. If we change `list`, then we loose such ability. +...But that would be unwise. In the future we may need to extend a function, do something else with the list. If we change `list`, then we lose such ability. Talking about good variable names, `list` here is the list itself. The first element of it. And it should remain like that. That's clear and reliable. diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 9805b5b91..3e41d4324 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -96,7 +96,7 @@ function pow(x, n) { The maximal number of nested calls (including the first one) is called *recursion depth*. In our case, it will be exactly `n`. -The maximal recursion depth is limited by JavaScript engine. We can make sure about 10000, some engines allow more, but 100000 is probably out of limit for the majority of them. There are automatic optimizations that help alleviate this ("tail calls optimizations"), but they are not yet supported everywhere and work only in simple cases. +The maximal recursion depth is limited by JavaScript engine. We can rely on it being 10000, some engines allow more, but 100000 is probably out of limit for the majority of them. There are automatic optimizations that help alleviate this ("tail calls optimizations"), but they are not yet supported everywhere and work only in simple cases. That limits the application of recursion, but it still remains very wide. There are many tasks where recursive way of thinking gives simpler code, easier to maintain. @@ -185,7 +185,13 @@ Here's the context stack when we entered the subcall `pow(2, 2)`: The new current execution context is on top (and bold), and previous remembered contexts are below. -When we finish the subcall -- it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped. Here in the picture we use the word "line", but of course it's more precise. +When we finish the subcall -- it is easy to resume the previous context, because it keeps both variables and the exact place of the code where it stopped. + +```smart +Here in the picture we use the word "line", as our example there's only one subcall in line, but generally a single line of code may contain multiple subcalls, like `pow(…) + pow(…) + somethingElse(…)`. + +So it would be more precise to say that the execution resumes "immediately after the subcall". +``` ### pow(2, 1) @@ -326,18 +332,18 @@ In other words, a company has departments. Now let's say we want a function to get the sum of all salaries. How can we do that? -An iterative approach is not easy, because the structure is not simple. The first idea may be to make a `for` loop over `company` with nested subloop over 1st level departments. But then we need more nested subloops to iterate over the staff in 2nd level departments like `sites`. ...And then another subloop inside those for 3rd level departments that might appear in the future? Should we stop on level 3 or make 4 levels of loops? If we put 3-4 nested subloops in the code to traverse a single object, it becomes rather ugly. +An iterative approach is not easy, because the structure is not simple. The first idea may be to make a `for` loop over `company` with nested subloop over 1st level departments. But then we need more nested subloops to iterate over the staff in 2nd level departments like `sites`... And then another subloop inside those for 3rd level departments that might appear in the future? If we put 3-4 nested subloops in the code to traverse a single object, it becomes rather ugly. Let's try recursion. As we can see, when our function gets a department to sum, there are two possible cases: -1. Either it's a "simple" department with an *array of people* -- then we can sum the salaries in a simple loop. -2. Or it's *an object with `N` subdepartments* -- then we can make `N` recursive calls to get the sum for each of the subdeps and combine the results. +1. Either it's a "simple" department with an *array* of people -- then we can sum the salaries in a simple loop. +2. Or it's *an object* with `N` subdepartments -- then we can make `N` recursive calls to get the sum for each of the subdeps and combine the results. -The (1) is the base of recursion, the trivial case. +The 1st case is the base of recursion, the trivial case, when we get an array. -The (2) is the recursive step. A complex task is split into subtasks for smaller departments. They may in turn split again, but sooner or later the split will finish at (1). +The 2nd case when we get an object is the recursive step. A complex task is split into subtasks for smaller departments. They may in turn split again, but sooner or later the split will finish at (1). The algorithm is probably even easier to read from the code: diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md index a98d8eddd..a14f0fb73 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md @@ -8,7 +8,7 @@ For instance: - `Object.assign(dest, src1, ..., srcN)` -- copies properties from `src1..N` into `dest`. - ...and so on. -In this chapter we'll learn how to do the same. And, more importantly, how to feel comfortable working with such functions and arrays. +In this chapter we'll learn how to do the same. And also, how to pass arrays to such functions as parameters. ## Rest parameters `...` @@ -25,7 +25,7 @@ alert( sum(1, 2, 3, 4, 5) ); There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted. -The rest parameters can be mentioned in a function definition with three dots `...`. They literally mean "gather the remaining parameters into an array". +The rest of the parameters can be included in the function definition by using three dots `...` followed by the name of the array that will contain them. The dots literally mean "gather the remaining parameters into an array". For instance, to gather all arguments into array `args`: @@ -96,9 +96,7 @@ showName("Julius", "Caesar"); showName("Ilya"); ``` -In old times, rest parameters did not exist in the language, and using `arguments` was the only way to get all arguments of the function, no matter their total number. - -And it still works, we can use it today. +In old times, rest parameters did not exist in the language, and using `arguments` was the only way to get all arguments of the function. And it still works, we can find it in the old code. But the downside is that although `arguments` is both array-like and iterable, it's not an array. It does not support array methods, so we can't call `arguments.map(...)` for example. @@ -119,9 +117,10 @@ function f() { f(1); // 1 ``` -```` As we remember, arrow functions don't have their own `this`. Now we know they don't have the special `arguments` object either. +```` + ## Spread operator [#spread-operator] diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 633c7a1c4..cd94c8870 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -70,7 +70,7 @@ The Lexical Environment object consists of two parts: 1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`). 2. A reference to the *outer lexical environment*, the one associated with the outer code. -**So, a "variable" is just a property of the special internal object, `Environment Record`. "To get or change a variable" means "to get or change a property of that object".** +**A "variable" is just a property of the special internal object, `Environment Record`. "To get or change a variable" means "to get or change a property of that object".** For instance, in this simple code, there is only one Lexical Environment: @@ -80,7 +80,7 @@ This is a so-called global Lexical Environment, associated with the whole script On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, so it points to `null`. -Here's the bigger picture of what happens when a `let` changes: +And that's how it changes when a variable is defined and assigned: ![lexical environment](lexical-environment-global-2.svg) @@ -119,7 +119,7 @@ Now let's go on and explore what happens when a function accesses an outer varia During the call, `say()` uses the outer variable `phrase`, let's look at the details of what's going on. -First, when a function runs, a new function Lexical Environment is created automatically. That's a general rule for all functions. That Lexical Environment is used to store local variables and parameters of the call. +When a function runs, a new Lexical Environment is created automatically to store local variables and parameters of the call. For instance, for `say("John")`, it looks like this (the execution is at the line, labelled with an arrow): @@ -143,13 +143,13 @@ So, during the function call we have two Lexical Environments: the inner one (fo It has a single property: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`. - The outer Lexical Environment is the global Lexical Environment. - It has `phrase` and the function itself. + It has `phrase` variable and the function itself. -The inner Lexical Environment has a reference to the outer one. +The inner Lexical Environment has a reference to the `outer` one. **When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.** -If a variable is not found anywhere, that's an error in strict mode. Without `use strict`, an assignment to an undefined variable creates a new global variable, for backwards compatibility. +If a variable is not found anywhere, that's an error in strict mode (without `use strict`, an assignment to a non-existing variable, like `user = "John"` creates a new global variable `user`, that's for backwards compatibility). Let's see how the search proceeds in our example: @@ -160,9 +160,9 @@ Let's see how the search proceeds in our example: Now we can give the answer to the first question from the beginning of the chapter. -**A function gets outer variables as they are now; it uses the most recent values.** +**A function gets outer variables as they are now, it uses the most recent values.** -That's because of the described mechanism. Old variable values are not saved anywhere. When a function wants them, it takes the current values from its own or an outer Lexical Environment. +Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one. So the answer to the first question is `Pete`: @@ -195,7 +195,7 @@ And if a function is called multiple times, then each invocation will have its o ``` ```smart header="Lexical Environment is a specification object" -"Lexical Environment" is a specification object. We can't get this object in our code and manipulate it directly. JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, but the visible behavior should be as described. +"Lexical Environment" is a specification object: it only exists "theoretically" in the [language specification](https://tc39.es/ecma262/#sec-lexical-environments) to describe how things work. We can't get this object in our code and manipulate it directly. JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, as long as the visible behavior remains as described. ``` @@ -309,11 +309,11 @@ alert( counter2() ); // 0 (independent) ``` -Hopefully, the situation with outer variables is clear now. For most situations such understanding is enough. There are few details in the specification that we omitted for brevity. So in the next section we cover even more details, not to miss anything. +Hopefully, the situation with outer variables is clear now. For most situations such understanding is enough. There are few details in the specification that we omitted for brevity. So in the next section we cover even more details. ## Environments in detail -Here's what's going on in the `makeCounter` example step-by-step, follow it to make sure that you know things in the very detail. +Here's what's going on in the `makeCounter` example step-by-step, follow it to make sure that you understand how it works in detail. Please note the additional `[[Environment]]` property is covered here. We didn't mention it before for simplicity. @@ -323,13 +323,15 @@ Please note the additional `[[Environment]]` property is covered here. We didn't At that starting moment there is only `makeCounter` function, because it's a Function Declaration. It did not run yet. - **All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation.** We didn't talk about it yet, but that's how the function knows where it was made. + **All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation.** + + We didn't talk about it yet, that's how the function knows where it was made. Here, `makeCounter` is created in the global Lexical Environment, so `[[Environment]]` keeps a reference to it. In other words, a function is "imprinted" with a reference to the Lexical Environment where it was born. And `[[Environment]]` is the hidden function property that has that reference. -2. The code runs on, the new global variable `counter` is declared and for its value `makeCounter()` is called. Here's a snapshot of the moment when the execution is on the first line inside `makeCounter()`: +2. The code runs on, the new global variable `counter` is declared and gets the result of `makeCounter()` call. Here's a snapshot of the moment when the execution is on the first line inside `makeCounter()`: ![](lexenv-nested-makecounter-2.svg) @@ -337,7 +339,7 @@ Please note the additional `[[Environment]]` property is covered here. We didn't As all Lexical Environments, it stores two things: 1. An Environment Record with local variables. In our case `count` is the only local variable (appearing when the line with `let count` is executed). - 2. The outer lexical reference, which is set to `[[Environment]]` of the function. Here `[[Environment]]` of `makeCounter` references the global Lexical Environment. + 2. The outer lexical reference, which is set to the value of `[[Environment]]` of the function. Here `[[Environment]]` of `makeCounter` references the global Lexical Environment. So, now we have two Lexical Environments: the first one is global, the second one is for the current `makeCounter` call, with the outer reference to global. @@ -357,13 +359,17 @@ Please note the additional `[[Environment]]` property is covered here. We didn't That function has only one line: `return count++`, that will be executed when we run it. +<<<<<<< HEAD 5. When the `counter()` is called, an "empty" Lexical Environment is created for it. It has no local variables by itself. But the `[[Environment]]` of `counter` is used as the outer reference for it, so it has access to the variables of the former `makeCounter()` call where it was created: ![](lexenv-nested-makecounter-5.svg) +======= +5. When `counter()` is called, a new Lexical Environment is created for the call. It's empty, as `counter` has no local variables by itself. But the `[[Environment]]` of `counter` is used as the `outer` reference for it, that provides access to the variables of the former `makeCounter()` call where it was created: +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac - Now if it accesses a variable, it first searches its own Lexical Environment (empty), then the Lexical Environment of the former `makeCounter()` call, then the global one. + ![](lexenv-nested-makecounter-5.svg) - When it looks for `count`, it finds it among the variables `makeCounter`, in the nearest outer Lexical Environment. + Now when the call looks for `count` variable, it first searches its own Lexical Environment (empty), then the Lexical Environment of the outer `makeCounter()` call, where it finds it. Please note how memory management works here. Although `makeCounter()` call finished some time ago, its Lexical Environment was retained in memory, because there's a nested function with `[[Environment]]` referencing it. @@ -372,14 +378,17 @@ Please note the additional `[[Environment]]` property is covered here. We didn't 6. The call to `counter()` not only returns the value of `count`, but also increases it. Note that the modification is done "in place". The value of `count` is modified exactly in the environment where it was found. ![](lexenv-nested-makecounter-6.svg) +<<<<<<< HEAD So we return to the previous step with the only change -- the new value of `count`. The following calls all do the same. +======= +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac 7. Next `counter()` invocations do the same. The answer to the second question from the beginning of the chapter should now be obvious. -The `work()` function in the code below uses the `name` from the place of its origin through the outer lexical environment reference: +The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference: ![](lexenv-nested-work.svg) @@ -430,7 +439,7 @@ For instance, after `if` finishes, the `alert` below won't see the `user`, hence ### For, while -For a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for`, then it's also local to that Lexical Environment: +For a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for(let ...)`, then it's also in there: ```js run for (let i = 0; i < 10; i++) { @@ -441,7 +450,7 @@ for (let i = 0; i < 10; i++) { alert(i); // Error, no such variable ``` -Please note: `let i` is visually outside of `{...}`. The `for` construct is somewhat special here: each iteration of the loop has its own Lexical Environment with the current `i` in it. +Please note: `let i` is visually outside of `{...}`. The `for` construct is special here: each iteration of the loop has its own Lexical Environment with the current `i` in it. Again, similarly to `if`, after the loop `i` is not visible. @@ -537,7 +546,7 @@ There exist other ways besides parentheses to tell JavaScript that we mean a Fun }(); ``` -In all the above cases we declare a Function Expression and run it immediately. +In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code. ## Garbage collection @@ -554,7 +563,7 @@ f(); Here two values are technically the properties of the Lexical Environment. But after `f()` finishes that Lexical Environment becomes unreachable, so it's deleted from the memory. -...But if there's a nested function that is still reachable after the end of `f`, then its `[[Environment]]` reference keeps the outer lexical environment alive as well: +...But if there's a nested function that is still reachable after the end of `f`, then it has `[[Environment]]` property that references the outer lexical environment, so it's also reachable and alive: ```js function f() { @@ -570,7 +579,7 @@ function f() { let g = f(); // g is reachable, and keeps the outer lexical environment in memory ``` -Please note that if `f()` is called many times, and resulting functions are saved, then the corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below: +Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below: ```js function f() { @@ -579,9 +588,8 @@ function f() { return function() { alert(value); }; } -// 3 functions in array, every one of them links to Lexical Environment (LE for short) +// 3 functions in array, every one of them links to Lexical Environment // from the corresponding f() run -// LE LE LE let arr = [f(), f(), f()]; ``` @@ -608,7 +616,7 @@ g = null; // ...and now the memory is cleaned up As we've seen, in theory while a function is alive, all outer variables are also retained. -But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it's easy to see that an outer variable is not used -- it is removed. +But in practice, JavaScript engines try to optimize that. They analyze variable usage and if it's obvious from the code that an outer variable is not used -- it is removed. **An important side effect in V8 (Chrome, Opera) is that such variable will become unavailable in debugging.** @@ -621,7 +629,7 @@ function f() { let value = Math.random(); function g() { - debugger; // in console: type alert( value ); No such variable! + debugger; // in console: type alert(value); No such variable! } return g; @@ -642,7 +650,7 @@ function f() { let value = "the closest value"; function g() { - debugger; // in console: type alert( value ); Surprise! + debugger; // in console: type alert(value); Surprise! } return g; 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 5892f0798..3d195a978 100644 --- a/1-js/06-advanced-functions/05-global-object/article.md +++ b/1-js/06-advanced-functions/05-global-object/article.md @@ -1,22 +1,23 @@ # Global object -The global object provides variables and functions that are available anywhere. Mostly, the ones that are built into the language or the environment. +The global object provides variables and functions that are available anywhere. By default, those that are built into the language or the environment. In a browser it is named `window`, for Node.js it is `global`, for other environments it may have another name. -Recently, `globalThis` was added to the language, as a standartized name for a global object, that should be supported across all environments. In some browsers, namely non-Chromium Edge, `globalThis` is not yet supported, but can be easily polyfilled. +Recently, `globalThis` was added to the language, as a standardized name for a global object, that should be supported across all environments. In some browsers, namely non-Chromium Edge, `globalThis` is not yet supported, but can be easily polyfilled. + +We'll use `window` here, assuming that our environment is a browser. If your script may run in other environments, it's better to use `globalThis` instead. All properties of the global object can be accessed directly: ```js run alert("Hello"); - -// the same as +// is the same as window.alert("Hello"); ``` -In a browser, global functions and variables declared with `var` become the property of the global object: +In a browser, global functions and variables declared with `var` (not `let/const`!) become the property of the global object: ```js run untrusted refresh var gVar = 5; @@ -24,9 +25,9 @@ var gVar = 5; alert(window.gVar); // 5 (became a property of the global object) ``` -Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use JavaScript modules where such thing doesn't happen. We'll cover them later in the chapter [](info:modules). +Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such thing doesn't happen. -Also, more modern variable declarations `let` and `const` do not exhibit such behavior at all: +If we used `let` instead, such thing wouldn't happen: ```js run untrusted refresh let gLet = 5; @@ -52,7 +53,7 @@ alert(currentUser.name); // John alert(window.currentUser.name); // John ``` -That said, using global variables is generally discouraged. There should be as few global variables as possible. The code design where a function gets "input" variables and produces certain "outcome" is clearer, less prone to errors and easier to test. +That said, using global variables is generally discouraged. There should be as few global variables as possible. The code design where a function gets "input" variables and produces certain "outcome" is clearer, less prone to errors and easier to test than if it uses outer or global variables. ## Using for polyfills diff --git a/1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/task.md b/1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/task.md index 0177c8f6e..a11821d67 100644 --- a/1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/task.md +++ b/1-js/06-advanced-functions/06-function-object/2-counter-inc-dec/task.md @@ -7,8 +7,8 @@ importance: 5 Modify the code of `makeCounter()` so that the counter can also decrease and set the number: - `counter()` should return the next number (as before). -- `counter.set(value)` should set the `count` to `value`. -- `counter.decrease()` should decrease the `count` by 1. +- `counter.set(value)` should set the counter to `value`. +- `counter.decrease()` should decrease the counter by 1. See the sandbox code for the complete usage example. 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 e907086c2..ed848c0c5 100644 --- a/1-js/06-advanced-functions/06-function-object/article.md +++ b/1-js/06-advanced-functions/06-function-object/article.md @@ -1,7 +1,7 @@ # Function object, NFE -As we already know, functions in JavaScript are values. +As we already know, a function in JavaScript is a value. Every value in JavaScript has a type. What type is a function? @@ -12,7 +12,7 @@ A good way to imagine functions is as callable "action objects". We can not only ## The "name" property -Function objects contain a few useable properties. +Function objects contain some useable properties. For instance, a function's name is accessible as the "name" property: @@ -24,14 +24,14 @@ function sayHi() { alert(sayHi.name); // sayHi ``` -What's more funny, the name-assigning logic is smart. It also assigns the correct name to functions that are used in assignments: +What's kind of funny, the name-assigning logic is smart. It also assigns the correct name to a function even if it's created without one, and then immediately assigned: ```js run let sayHi = function() { alert("Hi"); -} +}; -alert(sayHi.name); // sayHi (works!) +alert(sayHi.name); // sayHi (there's a name!) ``` It also works if the assignment is done via a default value: @@ -67,7 +67,7 @@ alert(user.sayBye.name); // sayBye There's no magic though. There are cases when there's no way to figure out the right name. In that case, the name property is empty, like here: -```js +```js run // function created inside array let arr = [function() {}]; @@ -93,7 +93,7 @@ alert(many.length); // 2 Here we can see that rest parameters are not counted. -The `length` property is sometimes used for introspection in functions that operate on other functions. +The `length` property is sometimes used for [introspection](https://en.wikipedia.org/wiki/Type_introspection) in functions that operate on other functions. For instance, in the code below the `ask` function accepts a `question` to ask and an arbitrary number of `handler` functions to call. @@ -102,9 +102,9 @@ Once a user provides their answer, the function calls the handlers. We can pass - A zero-argument function, which is only called when the user gives a positive answer. - A function with arguments, which is called in either case and returns an answer. -The idea is that we have a simple, no-arguments handler syntax for positive cases (most frequent variant), but are able to provide universal handlers as well. +To call `handler` the right way, we examine the `handler.length` property. -To call `handlers` the right way, we examine the `length` property: +The idea is that we have a simple, no-arguments handler syntax for positive cases (most frequent variant), but are able to support universal handlers as well: ```js run function ask(question, ...handlers) { @@ -241,7 +241,7 @@ let sayHi = function *!*func*/!*(who) { sayHi("John"); // Hello, John ``` -There are two special things about the name `func`: +There are two special things about the name `func`, that are the reasons for it: 1. It allows the function to reference itself internally. 2. It is not visible outside of the function. @@ -282,7 +282,7 @@ let sayHi = function(who) { }; ``` -The problem with that code is that the value of `sayHi` may change. The function may go to another variable, and the code will start to give errors: +The problem with that code is that `sayHi` may change in the outer code. If the function gets assigned to another variable instead, the code will start to give errors: ```js run let sayHi = function(who) { @@ -329,7 +329,7 @@ Now it works, because the name `"func"` is function-local. It is not taken from The outer code still has it's variable `sayHi` or `welcome`. And `func` is an "internal function name", how the function can call itself internally. ```smart header="There's no such thing for Function Declaration" -The "internal name" feature described here is only available for Function Expressions, not to Function Declarations. For Function Declarations, there's just no syntax possibility to add a one more "internal" name. +The "internal name" feature described here is only available for Function Expressions, not for Function Declarations. For Function Declarations, there is no syntax for adding an "internal" name. Sometimes, when we need a reliable internal name, it's the reason to rewrite a Function Declaration to Named Function Expression form. ``` @@ -347,6 +347,7 @@ If the function is declared as a Function Expression (not in the main code flow) Also, functions may carry additional properties. Many well-known JavaScript libraries make great use of this feature. -They create a "main" function and attach many other "helper" functions to it. For instance, the [jquery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`. And then adds `_.clone`, `_.keyBy` and other properties to (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. +They create a "main" function and attach many other "helper" functions to it. For instance, the [jQuery](https://jquery.com) library creates a function named `$`. The [lodash](https://lodash.com) library creates a function `_`, and then adds `_.clone`, `_.keyBy` and other properties to it (see the [docs](https://lodash.com/docs) when you want learn more about them). Actually, they do it to lessen their pollution of the global space, so that a single library gives only one global variable. That reduces the possibility of naming conflicts. + So, a function can do a useful job by itself and also carry a bunch of other functionality in properties. diff --git a/1-js/06-advanced-functions/07-new-function/article.md b/1-js/06-advanced-functions/07-new-function/article.md index b36b68af0..3214ba376 100644 --- a/1-js/06-advanced-functions/07-new-function/article.md +++ b/1-js/06-advanced-functions/07-new-function/article.md @@ -46,14 +46,13 @@ It is used in very specific cases, like when we receive code from a server, or t ## Closure -Usually, a function remembers where it was born in the special property `[[Environment]]`. It references the Lexical Environment from where it's created. +Usually, a function remembers where it was born in the special property `[[Environment]]`. It references the Lexical Environment from where it's created (we covered that in the chapter ). -But when a function is created using `new Function`, its `[[Environment]]` references not the current Lexical Environment, but instead the global one. +But when a function is created using `new Function`, its `[[Environment]]` is set to reference not the current Lexical Environment, but the global one. So, such function doesn't have access to outer variables, only to the global ones. ```js run - function getFunc() { let value = "test"; @@ -99,6 +98,8 @@ So if `new Function` had access to outer variables, it would be unable to find r **If `new Function` had access to outer variables, it would have problems with minifiers.** +Besides, such code would be architecturally bad and prone to errors. + To pass something to a function, created as `new Function`, we should use its arguments. ## Summary @@ -111,7 +112,7 @@ let func = new Function ([arg1, arg2, ...argN], functionBody); For historical reasons, arguments can also be given as a comma-separated list. -These three lines mean the same: +These three declarations mean the same: ```js new Function('a', 'b', 'return a + b'); // basic syntax @@ -119,4 +120,4 @@ new Function('a,b', 'return a + b'); // comma-separated new Function('a , b', 'return a + b'); // comma-separated with spaces ``` -Functions created with `new Function`, have `[[Environment]]` referencing the global Lexical Environment, not the outer one. Hence, they cannot use outer variables. But that's actually good, because it saves us from errors. Passing parameters explicitly is a much better method architecturally and causes no problems with minifiers. +Functions created with `new Function`, have `[[Environment]]` referencing the global Lexical Environment, not the outer one. Hence, they cannot use outer variables. But that's actually good, because it insures us from errors. Passing parameters explicitly is a much better method architecturally and causes no problems with minifiers. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/solution.md b/1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/solution.md index f09372096..b5b1da7a6 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/solution.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/solution.md @@ -18,7 +18,7 @@ function printNumbers(from, to) { printNumbers(5, 10); ``` -Using recursive `setTimeout`: +Using nested `setTimeout`: ```js run diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/task.md b/1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/task.md index 87e723c67..84bb0c39c 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/task.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/1-output-numbers-100ms/task.md @@ -9,5 +9,4 @@ Write a function `printNumbers(from, to)` that outputs a number every second, st Make two variants of the solution. 1. Using `setInterval`. -2. Using recursive `setTimeout`. - +2. Using nested `setTimeout`. diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md b/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md deleted file mode 100644 index 735a446f7..000000000 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/solution.md +++ /dev/null @@ -1,22 +0,0 @@ - - -```js run -let i = 0; - -let start = Date.now(); - -let timer = setInterval(count); - -function count() { - - for(let j = 0; j < 1000000; j++) { - i++; - } - - if (i == 1000000000) { - alert("Done in " + (Date.now() - start) + 'ms'); - clearInterval(timer); - } - -} -``` diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/task.md b/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/task.md deleted file mode 100644 index c3455c2a1..000000000 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/3-rewrite-settimeout/task.md +++ /dev/null @@ -1,32 +0,0 @@ -importance: 4 - ---- - -# Rewrite setTimeout with setInterval - -Here's the function that uses nested `setTimeout` to split a job into pieces. - -Rewrite it to `setInterval`: - -```js run -let i = 0; - -let start = Date.now(); - -function count() { - - if (i == 1000000000) { - alert("Done in " + (Date.now() - start) + 'ms'); - } else { - setTimeout(count); - } - - // a piece of heavy job - for(let j = 0; j < 1000000; j++) { - i++; - } - -} - -count(); -``` 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 c341bf72f..eb88c56ae 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -4,8 +4,8 @@ We may decide to execute a function not right now, but at a certain time later. There are two methods for it: -- `setTimeout` allows to run a function once after the interval of time. -- `setInterval` allows to run a function regularly with the interval between the runs. +- `setTimeout` allows us to run a function once after the interval of time. +- `setInterval` allows us to run a function repeatedly, starting after the interval of time, then repeating continuously at that interval. These methods are not a part of JavaScript specification. But most environments have the internal scheduler and provide these methods. In particular, they are supported in all browsers and Node.js. @@ -129,14 +129,14 @@ setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000); ```smart header="Time goes on while `alert` is shown" In most browsers, including Chrome and Firefox the internal timer continues "ticking" while showing `alert/confirm/prompt`. -So if you run the code above and don't dismiss the `alert` window for some time, then in the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 5 seconds. +So if you run the code above and don't dismiss the `alert` window for some time, then in the next `alert` will be shown immediately as you do it. The actual interval between alerts will be shorter than 2 seconds. ``` -## Recursive setTimeout +## Nested setTimeout There are two ways of running something regularly. -One is `setInterval`. The other one is a recursive `setTimeout`, like this: +One is `setInterval`. The other one is a nested `setTimeout`, like this: ```js /** instead of: @@ -153,7 +153,7 @@ let timerId = setTimeout(function tick() { The `setTimeout` above schedules the next call right at the end of the current one `(*)`. -The recursive `setTimeout` is a more flexible method than `setInterval`. This way the next call may be scheduled differently, depending on the results of the current one. +The nested `setTimeout` is a more flexible method than `setInterval`. This way the next call may be scheduled differently, depending on the results of the current one. For instance, we need to write a service that sends a request to the server every 5 seconds asking for data, but in case the server is overloaded, it should increase the interval to 10, 20, 40 seconds... @@ -175,9 +175,9 @@ let timerId = setTimeout(function request() { ``` -And if we the functions that we're scheduling are CPU-hungry, then we can measure the time taken by the execution and plan the next call sooner or later. +And if the functions that we're scheduling are CPU-hungry, then we can measure the time taken by the execution and plan the next call sooner or later. -**Recursive `setTimeout` guarantees a delay between the executions, `setInterval` -- does not.** +**Nested `setTimeout` allows to set the delay between the executions more precisely than `setInterval`.** Let's compare two code fragments. The first one uses `setInterval`: @@ -188,7 +188,7 @@ setInterval(function() { }, 100); ``` -The second one uses recursive `setTimeout`: +The second one uses nested `setTimeout`: ```js let i = 1; @@ -214,15 +214,15 @@ In this case the engine waits for `func` to complete, then checks the scheduler In the edge case, if the function always executes longer than `delay` ms, then the calls will happen without a pause at all. -And here is the picture for the recursive `setTimeout`: +And here is the picture for the nested `setTimeout`: ![](settimeout-interval.svg) -**The recursive `setTimeout` guarantees the fixed delay (here 100ms).** +**The nested `setTimeout` guarantees the fixed delay (here 100ms).** That's because a new call is planned at the end of the previous one. -````smart header="Garbage collection" +````smart header="Garbage collection and setInterval/setTimeout callback" When a function is passed in `setInterval/setTimeout`, an internal reference is created to it and saved in the scheduler. It prevents the function from being garbage collected, even if there are no other references to it. ```js @@ -239,9 +239,9 @@ There's a side-effect. A function references the outer lexical environment, so, There's a special use case: `setTimeout(func, 0)`, or just `setTimeout(func)`. -This schedules the execution of `func` as soon as possible. But scheduler will invoke it only after the current code is complete. +This schedules the execution of `func` as soon as possible. But the scheduler will invoke it only after the currently executing script is complete. -So the function is scheduled to run "right after" the current code. In other words, *asynchronously*. +So the function is scheduled to run "right after" the current script. For instance, this outputs "Hello", then immediately "World": @@ -251,7 +251,7 @@ setTimeout(() => alert("World")); alert("Hello"); ``` -The first line "puts the call into calendar after 0ms". But the scheduler will only "check the calendar" after the current code is complete, so `"Hello"` is first, and `"World"` -- after it. +The first line "puts the call into calendar after 0ms". But the scheduler will only "check the calendar" after the current script is complete, so `"Hello"` is first, and `"World"` -- after it. There are also advanced browser-related use cases of zero-delay timeout, that we'll discuss in the chapter . @@ -286,13 +286,13 @@ For server-side JavaScript, that limitation does not exist, and there exist othe ## Summary -- Methods `setInterval(func, delay, ...args)` and `setTimeout(func, delay, ...args)` allow to run the `func` regularly/once after `delay` milliseconds. -- To cancel the execution, we should call `clearInterval/clearTimeout` with the value returned by `setInterval/setTimeout`. -- Nested `setTimeout` calls is a more flexible alternative to `setInterval`. Also they can guarantee the minimal time *between* the executions. -- Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current code is complete". +- Methods `setTimeout(func, delay, ...args)` and `setInterval(func, delay, ...args)` allow us to run the `func` once/regularly after `delay` milliseconds. +- To cancel the execution, we should call `clearTimeout/clearInterval` with the value returned by `setTimeout/setInterval`. +- Nested `setTimeout` calls is a more flexible alternative to `setInterval`, allowing us to set the time *between* executions more precisely. +- Zero delay scheduling with `setTimeout(func, 0)` (the same as `setTimeout(func)`) is used to schedule the call "as soon as possible, but after the current script is complete". - The browser limits the minimal delay for five or more nested call of `setTimeout` or for `setInterval` (after 5th call) to 4ms. That's for historical reasons. -Please note that all scheduling methods do not *guarantee* the exact delay. +Please note that all scheduling methods do not *guarantee* the exact delay. For example, the in-browser timer may slow down for a lot of reasons: - The CPU is overloaded. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js index 9ef503703..d5a09efb3 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/_js.view/solution.js @@ -1,11 +1,12 @@ function spy(func) { function wrapper(...args) { + // using ...args instead of arguments to store "real" array in wrapper.calls wrapper.calls.push(args); - return func.apply(this, arguments); + return func.apply(this, args); } wrapper.calls = []; return wrapper; -} \ No newline at end of file +} diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md index 19a072014..0c8a211b4 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/01-spy-decorator/solution.md @@ -1 +1 @@ -Here we can use `calls.push(args)` to store all arguments in the log and `f.apply(this, args)` to forward the call. +The wrapper returned by `spy(f)` should store all arguments and then use `f.apply` to forward the call. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 466c6bc3f..2620f1c71 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -6,7 +6,7 @@ importance: 5 The result of `debounce(f, ms)` decorator should be a wrapper that passes the call to `f` at maximum once per `ms` milliseconds. -In other words, when we call a "debounced" function, it guarantees that all other future in the closest `ms` milliseconds will be ignored. +In other words, when we call a "debounced" function, it guarantees that all future calls to the function made less than `ms` milliseconds after the previous call will be ignored. For instance: @@ -21,4 +21,4 @@ setTimeout( () => f(4), 1100); // runs setTimeout( () => f(5), 1500); // ignored (less than 1000 ms from the last run) ``` -In practice `debounce` is useful for functions that retrieve/update something when we know that nothing new can be done in such a short period of time, so it's better not to waste resources. \ No newline at end of file +In practice `debounce` is useful for functions that retrieve/update something when we know that nothing new can be done in such a short period of time, so it's better not to waste resources. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index 8dd77368d..567c9ce7a 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -14,18 +14,18 @@ Let's check the real-life application to better understand that requirement and In browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms). -**The tracking function should update some information on the web-page.** +**We'd like to update some information on the web-page when the pointer moves.** -Updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in making it more often than once per 100ms. +...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms. -So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but `update()` will be called at maximum once per 100ms. +So we'll wrap it into the decorator: use `throttle(update, 100)` as the function to run on each mouse move instead of the original `update()`. The decorator will be called often, but forward the call to `update()` at maximum once per 100ms. Visually, it will look like this: -1. For the first mouse movement the decorated variant passes the call to `update`. That's important, the user sees our reaction to their move immediately. +1. For the first mouse movement the decorated variant immediately passes the call to `update`. That's important, the user sees our reaction to their move immediately. 2. Then as the mouse moves on, until `100ms` nothing happens. The decorated variant ignores calls. -3. At the end of `100ms` -- one more `update` happens with the last coordinates. -4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, perhaps the most important, the final mouse coordinates are processed. +3. At the end of `100ms` -- one more `update` happens with the last coordinates. +4. Then, finally, the mouse stops somewhere. The decorated variant waits until `100ms` expire and then runs `update` with last coordinates. So, quite important, the final mouse coordinates are processed. A code example: diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index 600f0ea1b..8536cf314 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -6,9 +6,9 @@ JavaScript gives exceptional flexibility when dealing with functions. They can b Let's say we have a function `slow(x)` which is CPU-heavy, but its results are stable. In other words, for the same `x` it always returns the same result. -If the function is called often, we may want to cache (remember) the results for different `x` to avoid spending extra-time on recalculations. +If the function is called often, we may want to cache (remember) the results to avoid spending extra-time on recalculations. -But instead of adding that functionality into `slow()` we'll create a wrapper. As we'll see, there are many benefits of doing so. +But instead of adding that functionality into `slow()` we'll create a wrapper function, that adds caching. As we'll see, there are many benefits of doing so. Here's the code, and explanations follow: @@ -23,13 +23,13 @@ function cachingDecorator(func) { let cache = new Map(); return function(x) { - if (cache.has(x)) { // if the result is in the map - return cache.get(x); // return it + if (cache.has(x)) { // if there's such key in cache + return cache.get(x); // read the result from it } - let result = func(x); // otherwise call func + let result = func(x); // otherwise call func - cache.set(x, result); // and cache (remember) the result + cache.set(x, result); // and cache (remember) the result return result; }; } @@ -49,21 +49,18 @@ The idea is that we can call `cachingDecorator` for any function, and it will re By separating caching from the main function code we also keep the main code simpler. -Now let's get into details of how it works. - The result of `cachingDecorator(func)` is a "wrapper": `function(x)` that "wraps" the call of `func(x)` into caching logic: ![](decorator-makecaching-wrapper.svg) -As we can see, the wrapper returns the result of `func(x)` "as is". From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior. +From an outside code, the wrapped `slow` function still does the same. It just got a caching aspect added to its behavior. To summarize, there are several benefits of using a separate `cachingDecorator` instead of altering the code of `slow` itself: - The `cachingDecorator` is reusable. We can apply it to another function. -- The caching logic is separate, it did not increase the complexity of `slow` itself (if there were any). +- The caching logic is separate, it did not increase the complexity of `slow` itself (if there was any). - We can combine multiple decorators if needed (other decorators will follow). - ## Using "func.call" for the context The caching decorator mentioned above is not suited to work with object methods. @@ -170,10 +167,8 @@ let user = { name: "John" }; say.call( user, "Hello" ); // John: Hello ``` - In our case, we can use `call` in the wrapper to pass the context to the original function: - ```js run let worker = { someMethod() { @@ -231,9 +226,7 @@ let worker = { worker.slow = cachingDecorator(worker.slow); ``` -We have two tasks to solve here. - -First is how to use both arguments `min` and `max` for the key in `cache` map. Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key. +Previously, for a single argument `x` we could just `cache.set(x, result)` to save the result and `cache.get(x)` to retrieve it. But now we need to remember the result for a *combination of arguments* `(min,max)`. The native `Map` takes single value only as the key. There are many solutions possible: @@ -241,85 +234,11 @@ There are many solutions possible: 2. Use nested maps: `cache.set(min)` will be a `Map` that stores the pair `(max, result)`. So we can get `result` as `cache.get(min).get(max)`. 3. Join two values into one. In our particular case we can just use a string `"min,max"` as the `Map` key. For flexibility, we can allow to provide a *hashing function* for the decorator, that knows how to make one value from many. - For many practical applications, the 3rd variant is good enough, so we'll stick to it. -The second task to solve is how to pass many arguments to `func`. Currently, the wrapper `function(x)` assumes a single argument, and `func.call(this, x)` passes it. +Also we need to replace `func.call(this, x)` with `func.call(this, ...arguments)`, to pass all arguments to the wrapped function call, not just the first one. -Here we can use another built-in method [func.apply](mdn:js/Function/apply). - -The syntax is: - -```js -func.apply(context, args) -``` - -It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments. - - -For instance, these two calls are almost the same: - -```js -func(1, 2, 3); -func.apply(context, [1, 2, 3]) -``` - -Both run `func` giving it arguments `1,2,3`. But `apply` also sets `this=context`. - -For instance, here `say` is called with `this=user` and `messageData` as a list of arguments: - -```js run -function say(time, phrase) { - alert(`[${time}] ${this.name}: ${phrase}`); -} - -let user = { name: "John" }; - -let messageData = ['10:00', 'Hello']; // become time and phrase - -*!* -// user becomes this, messageData is passed as a list of arguments (time, phrase) -say.apply(user, messageData); // [10:00] John: Hello (this=user) -*/!* -``` - -The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them. - -We already know the spread operator `...` from the chapter that can pass an array (or any iterable) as a list of arguments. So if we use it with `call`, we can achieve almost the same as `apply`. - -These two calls are almost equivalent: - -```js -let args = [1, 2, 3]; - -*!* -func.call(context, ...args); // pass an array as list with spread operator -func.apply(context, args); // is same as using apply -*/!* -``` - -If we look more closely, there's a minor difference between such uses of `call` and `apply`. - -- The spread operator `...` allows to pass *iterable* `args` as the list to `call`. -- The `apply` accepts only *array-like* `args`. - -So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works. - -And if `args` is both iterable and array-like, like a real array, then we technically could use any of them, but `apply` will probably be faster, because it's a single operation. Most JavaScript engines internally optimize it better than a pair `call + spread`. - -One of the most important uses of `apply` is passing the call to another function, like this: - -```js -let wrapper = function() { - return anotherFunction.apply(this, arguments); -}; -``` - -That's called *call forwarding*. The `wrapper` passes everything it gets: the context `this` and arguments to `anotherFunction` and returns back its result. - -When an external code calls such `wrapper`, it is indistinguishable from the call of the original function. - -Now let's bake it all into the more powerful `cachingDecorator`: +Here's a more powerful `cachingDecorator`: ```js run let worker = { @@ -340,7 +259,7 @@ function cachingDecorator(func, hash) { } *!* - let result = func.apply(this, arguments); // (**) + let result = func.call(this, ...arguments); // (**) */!* cache.set(key, result); @@ -358,13 +277,52 @@ alert( worker.slow(3, 5) ); // works alert( "Again " + worker.slow(3, 5) ); // same (cached) ``` -Now the wrapper operates with any number of arguments. +Now it works with any number of arguments (though the hash function would also need to be adjusted to allow any number of arguments. An interesting way to handle this will be covered below). There are two changes: - In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions. -- Then `(**)` uses `func.apply` to pass both the context and all arguments the wrapper got (no matter how many) to the original function. +- Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function. +Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`. + +The syntax of built-in method [func.apply](mdn:js/Function/apply) is: + +```js +func.apply(context, args) +``` + +It runs the `func` setting `this=context` and using an array-like object `args` as the list of arguments. + +The only syntax difference between `call` and `apply` is that `call` expects a list of arguments, while `apply` takes an array-like object with them. + +So these two calls are almost equivalent: + +```js +func.call(context, ...args); // pass an array as list with spread operator +func.apply(context, args); // is same as using apply +``` + +There's only a minor difference: + +- The spread operator `...` allows to pass *iterable* `args` as the list to `call`. +- The `apply` accepts only *array-like* `args`. + +So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works. + +And for objects that are both iterable and array-like, like a real array, we technically could use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. + +Passing all arguments along with the context to another function is called *call forwarding*. + +That's the simplest form of it: + +```js +let wrapper = function() { + return func.apply(this, arguments); +}; +``` + +When an external code calls such `wrapper`, it is indistinguishable from the call of the original function `func`. ## Borrowing a method [#method-borrowing] @@ -432,12 +390,20 @@ Taken from the specification almost "as-is": So, technically it takes `this` and joins `this[0]`, `this[1]` ...etc together. It's intentionally written in a way that allows any array-like `this` (not a coincidence, many methods follow this practice). That's why it also works with `this=arguments`. +## Decorators and function properties + +It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them. + +E.g. in the example above if `slow` function had any properties on it, then `cachingDecorator(slow)` is a wrapper without them. + +Some decorators may provide their own properties. E.g. a decorator may count how many times a function was invoked and how much time it took, and expose this information via wrapper properties. + +There exists a way to create decorators that keep access to function properties, but this requires using a special `Proxy` object to wrap a function. We'll discuss it later in the article . + ## Summary *Decorator* is a wrapper around a function that alters its behavior. The main job is still carried out by the function. -It is generally safe to replace a function or a method with a decorated one, except for one little thing. If the original function had properties on it, like `func.calledCount` or whatever, then the decorated one will not provide them. Because that is a wrapper. So one needs to be careful if one uses them. Some decorators provide their own properties. - Decorators can be seen as "features" or "aspects" that can be added to a function. We can add one or add many. And all this without changing its code! To implement `cachingDecorator`, we studied methods: @@ -450,10 +416,9 @@ The generic *call forwarding* is usually done with `apply`: ```js let wrapper = function() { return original.apply(this, arguments); -} +}; ``` We also saw an example of *method borrowing* when we take a method from an object and `call` it in the context of another object. It is quite common to take array methods and apply them to `arguments`. The alternative is to use rest parameters object that is a real array. - There are many decorators there in the wild. Check how well you got them by solving the tasks of this chapter. diff --git a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md index 8cd18ec56..d6cfb44bf 100644 --- a/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md +++ b/1-js/06-advanced-functions/10-bind/4-function-property-after-bind/task.md @@ -4,7 +4,7 @@ importance: 5 # Function property after bind -There's a value in the property of a function. Will it change after `bind`? Why, elaborate? +There's a value in the property of a function. Will it change after `bind`? Why, or why not? ```js run function sayHi() { diff --git a/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/solution.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md similarity index 100% rename from 1-js/06-advanced-functions/11-currying-partials/1-ask-currying/solution.md rename to 1-js/06-advanced-functions/10-bind/6-ask-partial/solution.md diff --git a/1-js/06-advanced-functions/11-currying-partials/1-ask-currying/task.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md similarity index 100% rename from 1-js/06-advanced-functions/11-currying-partials/1-ask-currying/task.md rename to 1-js/06-advanced-functions/10-bind/6-ask-partial/task.md diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 06e2000ff..b8c545b1c 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -5,13 +5,13 @@ libs: # Function binding -When using `setTimeout` with object methods or passing object methods along, there's a known problem: "losing `this`". +When passing object methods as callbacks, for instance to `setTimeout`, there's a known problem: "losing `this`". -Suddenly, `this` just stops working right. The situation is typical for novice developers, but happens with experienced ones as well. +In this chapter we'll see the ways to fix it. ## Losing "this" -We already know that in JavaScript it's easy to lose `this`. Once a method is passed somewhere separately from the object -- `this` is lost. +We've already seen examples of losing `this`. Once a method is passed somewhere separately from the object -- `this` is lost. Here's how it may happen with `setTimeout`: @@ -37,7 +37,7 @@ let f = user.sayHi; setTimeout(f, 1000); // lost user context ``` -The method `setTimeout` in-browser is a little special: it sets `this=window` for the function call (for Node.js, `this` becomes the timer object, but doesn't really matter here). So for `this.firstName` it tries to get `window.firstName`, which does not exist. In other similar cases as we'll see, usually `this` just becomes `undefined`. +The method `setTimeout` in-browser is a little special: it sets `this=window` for the function call (for Node.js, `this` becomes the timer object, but doesn't really matter here). So for `this.firstName` it tries to get `window.firstName`, which does not exist. In other similar cases, usually `this` just becomes `undefined`. The task is quite typical -- we want to pass an object method somewhere else (here -- to the scheduler) where it will be called. How to make sure that it will be called in the right context? @@ -196,8 +196,124 @@ for (let key in user) { JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash. ```` +## Partial functions + +Until now we have only been talking about binding `this`. Let's take it a step further. + +We can bind not only `this`, but also arguments. That's rarely done, but sometimes can be handy. + +The full syntax of `bind`: + +```js +let bound = func.bind(context, [arg1], [arg2], ...); +``` + +It allows to bind context as `this` and starting arguments of the function. + +For instance, we have a multiplication function `mul(a, b)`: + +```js +function mul(a, b) { + return a * b; +} +``` + +Let's use `bind` to create a function `double` on its base: + +```js run +function mul(a, b) { + return a * b; +} + +*!* +let double = mul.bind(null, 2); +*/!* + +alert( double(3) ); // = mul(2, 3) = 6 +alert( double(4) ); // = mul(2, 4) = 8 +alert( double(5) ); // = mul(2, 5) = 10 +``` + +The call to `mul.bind(null, 2)` creates a new function `double` that passes calls to `mul`, fixing `null` as the context and `2` as the first argument. Further arguments are passed "as is". + +That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one. + +Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. + +The function `triple` in the code below triples the value: + +```js run +function mul(a, b) { + return a * b; +} + +*!* +let triple = mul.bind(null, 3); +*/!* + +alert( triple(3) ); // = mul(3, 3) = 9 +alert( triple(4) ); // = mul(3, 4) = 12 +alert( triple(5) ); // = mul(3, 5) = 15 +``` + +Why do we usually make a partial function? + +The benefit is that we can create an independent function with a readable name (`double`, `triple`). We can use it and not provide the first argument every time as it's fixed with `bind`. + +In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience. + +For instance, we have a function `send(from, to, text)`. Then, inside a `user` object we may want to use a partial variant of it: `sendTo(to, text)` that sends from the current user. + +## Going partial without context + +What if we'd like to fix some arguments, but not the context `this`? For example, for an object method. + +The native `bind` does not allow that. We can't just omit the context and jump to arguments. + +Fortunately, a helper function `partial` for binding only arguments can be easily implemented. + +Like this: + +```js run +*!* +function partial(func, ...argsBound) { + return function(...args) { // (*) + return func.call(this, ...argsBound, ...args); + } +} +*/!* + +// Usage: +let user = { + firstName: "John", + say(time, phrase) { + alert(`[${time}] ${this.firstName}: ${phrase}!`); + } +}; + +// add a partial method with fixed time +user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); + +user.sayNow("Hello"); +// Something like: +// [10:00] John: Hello! +``` + +The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that calls `func` with: +- Same `this` as it gets (for `user.sayNow` call it's `user`) +- Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`) +- Then gives it `...args` -- arguments given to the wrapper (`"Hello"`) + +So easy to do it with the spread operator, right? + +Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library. + ## Summary Method `func.bind(context, ...args)` returns a "bound variant" of function `func` that fixes the context `this` and first arguments if given. -Usually we apply `bind` to fix `this` in an object method, so that we can pass it somewhere. For example, to `setTimeout`. There are more reasons to `bind` in the modern development, we'll meet them later. +Usually we apply `bind` to fix `this` for an object method, so that we can pass it somewhere. For example, to `setTimeout`. + +When we fix some arguments of an existing function, the resulting (less universal) function is called *partially applied* or *partial*. + +Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a `send(from, to)` function, and `from` should always be the same for our task, we can get a partial and go on with it. diff --git a/1-js/06-advanced-functions/11-currying-partials/article.md b/1-js/06-advanced-functions/11-currying-partials/article.md deleted file mode 100644 index e8a3d7bdf..000000000 --- a/1-js/06-advanced-functions/11-currying-partials/article.md +++ /dev/null @@ -1,304 +0,0 @@ -libs: - - lodash - ---- - -# Currying and partials - -Until now we have only been talking about binding `this`. Let's take it a step further. - -We can bind not only `this`, but also arguments. That's rarely done, but sometimes can be handy. - -The full syntax of `bind`: - -```js -let bound = func.bind(context, arg1, arg2, ...); -``` - -It allows to bind context as `this` and starting arguments of the function. - -For instance, we have a multiplication function `mul(a, b)`: - -```js -function mul(a, b) { - return a * b; -} -``` - -Let's use `bind` to create a function `double` on its base: - -```js run -function mul(a, b) { - return a * b; -} - -*!* -let double = mul.bind(null, 2); -*/!* - -alert( double(3) ); // = mul(2, 3) = 6 -alert( double(4) ); // = mul(2, 4) = 8 -alert( double(5) ); // = mul(2, 5) = 10 -``` - -The call to `mul.bind(null, 2)` creates a new function `double` that passes calls to `mul`, fixing `null` as the context and `2` as the first argument. Further arguments are passed "as is". - -That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one. - -Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. - -The function `triple` in the code below triples the value: - -```js run -function mul(a, b) { - return a * b; -} - -*!* -let triple = mul.bind(null, 3); -*/!* - -alert( triple(3) ); // = mul(3, 3) = 9 -alert( triple(4) ); // = mul(3, 4) = 12 -alert( triple(5) ); // = mul(3, 5) = 15 -``` - -Why do we usually make a partial function? - -The benefit is that we can create an independent function with a readable name (`double`, `triple`). We can use it and not provide first argument of every time as it's fixed with `bind`. - -In other cases, partial application is useful when we have a very generic function and want a less universal variant of it for convenience. - -For instance, we have a function `send(from, to, text)`. Then, inside a `user` object we may want to use a partial variant of it: `sendTo(to, text)` that sends from the current user. - -## Going partial without context - -What if we'd like to fix some arguments, but not bind `this`? - -The native `bind` does not allow that. We can't just omit the context and jump to arguments. - -Fortunately, a `partial` function for binding only arguments can be easily implemented. - -Like this: - -```js run -*!* -function partial(func, ...argsBound) { - return function(...args) { // (*) - return func.call(this, ...argsBound, ...args); - } -} -*/!* - -// Usage: -let user = { - firstName: "John", - say(time, phrase) { - alert(`[${time}] ${this.firstName}: ${phrase}!`); - } -}; - -// add a partial method that says something now by fixing the first argument -user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes()); - -user.sayNow("Hello"); -// Something like: -// [10:00] John: Hello! -``` - -The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that calls `func` with: -- Same `this` as it gets (for `user.sayNow` call it's `user`) -- Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`) -- Then gives it `...args` -- arguments given to the wrapper (`"Hello"`) - -So easy to do it with the spread operator, right? - -Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library. - -## Currying - -Sometimes people mix up partial function application mentioned above with another thing named "currying". That's another interesting technique of working with functions that we just have to mention here. - -[Currying](https://en.wikipedia.org/wiki/Currying) is a transformation of functions that translates a function from callable as `f(a, b, c)` into callable as `f(a)(b)(c)`. In JavaScript, we usually make a wrapper to keep the original function. - -Currying doesn't call a function. It just transforms it. - -Let's create a helper `curry(f)` function that performs currying for a two-argument `f`. In other words, `curry(f)` for two-argument `f(a, b)` translates it into `f(a)(b)` - -```js run -*!* -function curry(f) { // curry(f) does the currying transform - return function(a) { - return function(b) { - return f(a, b); - }; - }; -} -*/!* - -// usage -function sum(a, b) { - return a + b; -} - -let carriedSum = curry(sum); - -alert( carriedSum(1)(2) ); // 3 -``` - -As you can see, the implementation is a series of wrappers. - -- The result of `curry(func)` is a wrapper `function(a)`. -- When it is called like `sum(1)`, the argument is saved in the Lexical Environment, and a new wrapper is returned `function(b)`. -- Then `sum(1)(2)` finally calls `function(b)` providing `2`, and it passes the call to the original multi-argument `sum`. - -More advanced implementations of currying like [_.curry](https://lodash.com/docs#curry) from lodash library do something more sophisticated. They return a wrapper that allows a function to be called normally when all arguments are supplied *or* returns a partial otherwise. - -```js -function curry(f) { - return function(...args) { - // if args.length == f.length (as many arguments as f has), - // then pass the call to f - // otherwise return a partial function that fixes args as first arguments - }; -} -``` - -## Currying? What for? - -To understand the benefits we definitely need a worthy real-life example. - -Advanced currying allows the function to be both callable normally and partially. - -For instance, we have the logging function `log(date, importance, message)` that formats and outputs the information. In real projects such functions also have many other useful features like sending logs over the network, here we just use `alert`: - -```js -function log(date, importance, message) { - alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`); -} -``` - -Let's curry it! - -```js -log = _.curry(log); -``` - -After that `log` work both the normal way and in the curried form: - -```js -log(new Date(), "DEBUG", "some debug"); // log(a,b,c) -log(new Date())("DEBUG")("some debug"); // log(a)(b)(c) -``` - -Now we can easily make a convenience function for current logs: - -```js -// currentLog will be the partial of log with fixed first argument -let logNow = log(new Date()); - -// use it -logNow("INFO", "message"); // [HH:mm] INFO message -``` - -And here's a convenience function for current debug messages: - -```js -let debugNow = logNow("DEBUG"); - -debugNow("message"); // [HH:mm] DEBUG message -``` - -So: -1. We didn't lose anything after currying: `log` is still callable normally. -2. We were able to generate partial functions such as for today's logs. - -## Advanced curry implementation - -In case you'd like to get in details (not obligatory!), here's the "advanced" curry implementation that we could use above. - -It's pretty short: - -```js -function curry(func) { - - return function curried(...args) { - if (args.length >= func.length) { - return func.apply(this, args); - } else { - return function(...args2) { - return curried.apply(this, args.concat(args2)); - } - } - }; - -} -``` - -Usage examples: - -```js -function sum(a, b, c) { - return a + b + c; -} - -let curriedSum = curry(sum); - -alert( curriedSum(1, 2, 3) ); // 6, still callable normally -alert( curriedSum(1)(2,3) ); // 6, currying of 1st arg -alert( curriedSum(1)(2)(3) ); // 6, full currying -``` - -The new `curry` may look complicated, but it's actually easy to understand. - -The result of `curry(func)` is the wrapper `curried` that looks like this: - -```js -// func is the function to transform -function curried(...args) { - if (args.length >= func.length) { // (1) - return func.apply(this, args); - } else { - return function pass(...args2) { // (2) - return curried.apply(this, args.concat(args2)); - } - } -}; -``` - -When we run it, there are two branches: - -1. Call now: if passed `args` count is the same as the original function has in its definition (`func.length`) or longer, then just pass the call to it. -2. Get a partial: otherwise, `func` is not called yet. Instead, another wrapper `pass` is returned, that will re-apply `curried` providing previous arguments together with the new ones. Then on a new call, again, we'll get either a new partial (if not enough arguments) or, finally, the result. - -For instance, let's see what happens in the case of `sum(a, b, c)`. Three arguments, so `sum.length = 3`. - -For the call `curried(1)(2)(3)`: - -1. The first call `curried(1)` remembers `1` in its Lexical Environment, and returns a wrapper `pass`. -2. The wrapper `pass` is called with `(2)`: it takes previous args (`1`), concatenates them with what it got `(2)` and calls `curried(1, 2)` with them together. - - As the argument count is still less than 3, `curry` returns `pass`. -3. The wrapper `pass` is called again with `(3)`, for the next call `pass(3)` takes previous args (`1`, `2`) and adds `3` to them, making the call `curried(1, 2, 3)` -- there are `3` arguments at last, they are given to the original function. - -If that's still not obvious, just trace the calls sequence in your mind or on the paper. - -```smart header="Fixed-length functions only" -The currying requires the function to have a known fixed number of arguments. -``` - -```smart header="A little more than currying" -By definition, currying should convert `sum(a, b, c)` into `sum(a)(b)(c)`. - -But most implementations of currying in JavaScript are advanced, as described: they also keep the function callable in the multi-argument variant. -``` - -## Summary - -- When we fix some arguments of an existing function, the resulting (less universal) function is called *a partial*. We can use `bind` to get a partial, but there are other ways also. - - Partials are convenient when we don't want to repeat the same argument over and over again. Like if we have a `send(from, to)` function, and `from` should always be the same for our task, we can get a partial and go on with it. - -- *Currying* is a transform that makes `f(a,b,c)` callable as `f(a)(b)(c)`. JavaScript implementations usually both keep the function callable normally and return the partial if arguments count is not enough. - - Currying is great when we want easy partials. As we've seen in the logging example: the universal function `log(date, importance, message)` after currying gives us partials when called with one argument like `log(date)` or two arguments `log(date, importance)`. diff --git a/1-js/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md index 1ade1a419..1d6b04394 100644 --- a/1-js/06-advanced-functions/12-arrow-functions/article.md +++ b/1-js/06-advanced-functions/12-arrow-functions/article.md @@ -2,9 +2,9 @@ Let's revisit arrow functions. -Arrow functions are not just a "shorthand" for writing small stuff. +Arrow functions are not just a "shorthand" for writing small stuff. They have some very specific and useful features. -JavaScript is full of situations where we need to write a small function, that's executed somewhere else. +JavaScript is full of situations where we need to write a small function that's executed somewhere else. For instance: @@ -14,7 +14,7 @@ For instance: It's in the very spirit of JavaScript to create a function and pass it somewhere. -And in such functions we usually don't want to leave the current context. +And in such functions we usually don't want to leave the current context. That's where arrow functions come in handy. ## Arrow functions have no "this" 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 16b3abc63..9f8f85d9c 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -11,7 +11,7 @@ In this chapter we'll study additional configuration options, and in the next we Object properties, besides a **`value`**, have three special attributes (so-called "flags"): -- **`writable`** -- if `true`, can be changed, otherwise it's read-only. +- **`writable`** -- if `true`, the value can be changed, otherwise it's read-only. - **`enumerable`** -- if `true`, then listed in loops, otherwise not listed. - **`configurable`** -- if `true`, the property can be deleted and these attributes can be modified, otherwise not. @@ -63,7 +63,7 @@ Object.defineProperty(obj, propertyName, descriptor) ``` `obj`, `propertyName` -: The object and property to work on. +: The object and its property to apply the descriptor. `descriptor` : Property descriptor to apply. @@ -100,9 +100,9 @@ Compare it with "normally created" `user.name` above: now all flags are falsy. I Now let's see effects of the flags by example. -## Read-only +## Non-writable -Let's make `user.name` read-only by changing `writable` flag: +Let's make `user.name` non-writable (can't be reassigned) by changing `writable` flag: ```js run let user = { @@ -116,35 +116,34 @@ Object.defineProperty(user, "name", { }); *!* -user.name = "Pete"; // Error: Cannot assign to read only property 'name'... +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 read-only properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +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. ``` -Here's the same operation, but for the case when a property doesn't exist: +Here's the same example, but the property is created from scratch: ```js run let user = { }; Object.defineProperty(user, "name", { *!* - value: "Pete", + value: "John", // for new properties need to explicitly list what's true enumerable: true, configurable: true */!* }); -alert(user.name); // Pete -user.name = "Alice"; // Error +alert(user.name); // John +user.name = "Pete"; // Error ``` - ## Non-enumerable Now let's add a custom `toString` to `user`. @@ -195,9 +194,9 @@ alert(Object.keys(user)); // name The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties. -A non-configurable property can not be deleted or altered with `defineProperty`. +A non-configurable property can not be deleted. -For instance, `Math.PI` is read-only, non-enumerable and non-configurable: +For instance, `Math.PI` is non-writable, non-enumerable and non-configurable: ```js run let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI'); @@ -220,7 +219,13 @@ Math.PI = 3; // Error // delete Math.PI won't work either ``` -Making a property non-configurable is a one-way road. We cannot change it back, because `defineProperty` doesn't work on non-configurable properties. +Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`. + +To be precise, non-configurability imposes several restrictions on `defineProperty`: +1. Can't change `configurable` flag. +2. Can't change `enumerable` flag. +3. Can't change `writable: false` to `true` (the other way round works). +4. Can't change `get/set` for an accessor property (but can assign them if absent). Here we are making `user.name` a "forever sealed" constant: @@ -238,11 +243,17 @@ Object.defineProperty(user, "name", { // all this won't work: // user.name = "Pete" // delete user.name -// defineProperty(user, "name", ...) +// defineProperty(user, "name", { value: "Pete" }) Object.defineProperty(user, "name", {writable: true}); // Error */!* ``` +```smart header="\"Non-configurable\" doesn't mean \"non-writable\"" +Notable exception: a value of non-configurable, but writable property can be changed. + +The idea of `configurable: false` is to prevent changes to property flags and its deletion, not changes to its value. +``` + ## Object.defineProperties There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once. @@ -305,6 +316,7 @@ There are also methods that limit access to the *whole* object: [Object.freeze(obj)](mdn:js/Object/freeze) : Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties. + And also there are tests for them: [Object.isExtensible(obj)](mdn:js/Object/isExtensible) 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 f316cc23f..dc541b6da 100644 --- a/1-js/07-object-properties/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -27,14 +27,14 @@ The getter works when `obj.propName` is read, the setter -- when it is assigned. For instance, we have a `user` object with `name` and `surname`: -```js run +```js let user = { name: "John", surname: "Smith" }; ``` -Now we want to add a "fullName" property, that should be "John Smith". Of course, we don't want to copy-paste existing information, so we can implement it as an accessor: +Now we want to add a `fullName` property, that should be `"John Smith"`. Of course, we don't want to copy-paste existing information, so we can implement it as an accessor: ```js run let user = { @@ -55,7 +55,19 @@ alert(user.fullName); // John Smith From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes. -As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error. +As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error: + +```js run +let user = { + get fullName() { + return `...`; + } +}; + +*!* +user.fullName = "Test"; // Error (property has only a getter) +*/!* +``` Let's fix it by adding a setter for `user.fullName`: @@ -84,23 +96,17 @@ alert(user.surname); // Cooper As the result, we have a "virtual" property `fullName`. It is readable and writable, but in fact does not exist. -```smart header="Accessor properties are only accessible with get/set" -Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data property any more. - -- If there's a getter -- we can read `object.prop`, otherwise we can't. -- If there's a setter -- we can set `object.prop=...`, otherwise we can't. - -And in either case we can't `delete` an accessor property. +```smart header="No way to handle `delete`" +There's no similar method to handle deletion of an accessor property. Only getter/setter methods may exist. ``` - ## Accessor descriptors -Descriptors for accessor properties are different -- as compared with data properties. +Descriptors for accessor properties are different from those for data properties. -For accessor properties, there is no `value` and `writable`, but instead there are `get` and `set` functions. +For accessor properties, there is no `value` or `writable`, but instead there are `get` and `set` functions. -So an accessor descriptor may have: +That is, an accessor descriptor may have: - **`get`** -- a function without arguments, that works when a property is read, - **`set`** -- a function with one argument, that is called when the property is set, @@ -132,7 +138,7 @@ alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ``` -Please note once again that a property can be either an accessor or a data property, not both. +Please note once again that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both. If we try to supply both `get` and `value` in the same descriptor, there will be an error: @@ -151,9 +157,9 @@ Object.defineProperty({}, 'prop', { ## Smarter getters/setters -Getters/setters can be used as wrappers over "real" property values to gain more control over them. +Getters/setters can be used as wrappers over "real" property values to gain more control over operations with them. -For instance, if we want to forbid too short names for `user`, we can store `name` in a special property `_name`. And filter assignments in the setter: +For instance, if we want to forbid too short names for `user`, we can have a setter `name` and keep the value in a separate property `_name`: ```js run let user = { @@ -176,14 +182,16 @@ alert(user.name); // Pete user.name = ""; // Name is too short... ``` -Technically, the external code may still access the name directly by using `user._name`. But there is a widely known agreement that properties starting with an underscore `"_"` are internal and should not be touched from outside the object. +So, the name is stored in `_name` property, and the access is done via getter and setter. + +Technically, external code is able to access the name directly by using `user._name`. But there is a widely known convention that properties starting with an underscore `"_"` are internal and should not be touched from outside the object. ## Using for compatibility -One of the great ideas behind getters and setters -- they allow to take control over a "regular" data property at any moment by replacing it with getter and setter and tweak its behavior. +One of the great uses of accessors -- they allow to take control over a "regular" data property at any moment by replacing it with getter and setter and tweak its behavior. -Let's say we started implementing user objects using data properties `name` and `age`: +Imagine, we started implementing user objects using data properties `name` and `age`: ```js function User(name, age) { @@ -209,7 +217,9 @@ let john = new User("John", new Date(1992, 6, 1)); Now what to do with the old code that still uses `age` property? -We can try to find all such places and fix them, but that takes time and can be hard to do if that code is written/used by many other people. And besides, `age` is a nice thing to have in `user`, right? In some places it's just what we want. +We can try to find all such places and fix them, but that takes time and can be hard to do if that code is used by many other people. And besides, `age` is a nice thing to have in `user`, right? + +Let's keep it. Adding a getter for `age` solves the problem: diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md index 002b24b8a..421b57e0a 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md @@ -6,7 +6,7 @@ importance: 5 The task has two parts. -We have an object: +We have objects: ```js let head = { diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md index c7d147b9c..4d6ea2653 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md @@ -3,4 +3,5 @@ That's because `this` is an object before the dot, so `rabbit.eat()` modifies `rabbit`. Property lookup and execution are two different things. -The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit` + +The method `rabbit.eat` is first found in the prototype, then executed with `this=rabbit`. diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md index fad4b8860..c141b2ecd 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md +++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md @@ -10,7 +10,7 @@ Let's look carefully at what's going on in the call `speedy.eat("apple")`. So all hamsters share a single stomach! -Every time the `stomach` is taken from the prototype, then `stomach.push` modifies it "at place". +Both for `lazy.stomach.push(...)` and `speedy.stomach.push()`, the property `stomach` is found in the prototype (as it's not in the object itself), then the new data is pushed into it. Please note that such thing doesn't happen in case of a simple assignment `this.stomach=`: @@ -44,7 +44,7 @@ alert( lazy.stomach ); // Now all works fine, because `this.stomach=` does not perform a lookup of `stomach`. The value is written directly into `this` object. -Also we can totally evade the problem by making sure that each hamster has their own stomach: +Also we can totally avoid the problem by making sure that each hamster has their own stomach: ```js run let hamster = { @@ -77,4 +77,4 @@ alert( speedy.stomach ); // apple alert( lazy.stomach ); // ``` -As a common solution, all properties that describe the state of a particular object, like `stomach` above, are usually written into that object. That prevents such problems. +As a common solution, all properties that describe the state of a particular object, like `stomach` above, should be written into that object. That prevents such problems. diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 42e77a831..8da5fb76e 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -43,7 +43,7 @@ If we look for a property in `rabbit`, and it's missing, JavaScript automaticall For instance: -```js run +```js let animal = { eats: true }; @@ -101,7 +101,6 @@ The method is automatically taken from the prototype, like this: The prototype chain can be longer: - ```js run let animal = { eats: true, @@ -131,10 +130,10 @@ alert(longEar.jumps); // true (from rabbit) ![](proto-animal-rabbit-chain.svg) -There are actually only two limitations: +There are only two limitations: 1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle. -2. The value of `__proto__` can be either an object or `null`, other types (like primitives) are ignored. +2. The value of `__proto__` can be either an object or `null`. Other types are ignored. Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others. @@ -171,7 +170,7 @@ From now on, `rabbit.walk()` call finds the method immediately in the object and ![](proto-animal-rabbit-walk-2.svg) -That's for data properties only, not for accessors. If a property is a getter/setter, then it behaves like a function: getters/setters are looked up in the prototype. +Accessor properties are an exception, as assignment is handled by a setter function. So writing to such a property is actually the same as calling a function. For that reason `admin.fullName` works correctly in the code below: @@ -212,7 +211,7 @@ The answer is simple: `this` is not affected by prototypes at all. So, the setter call `admin.fullName=` uses `admin` as `this`, not `user`. -That is actually a super-important thing, because we may have a big object with many methods and inherit from it. Then inherited objects can run its methods, and they will modify the state of these objects, not the big one. +That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object. For instance, here `animal` represents a "method storage", and `rabbit` makes use of it. @@ -247,7 +246,7 @@ The resulting picture: ![](proto-animal-rabbit-walk-3.svg) -If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects. +If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method call would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects. As a result, methods are shared, but the object state is not. @@ -309,14 +308,14 @@ Here we have the following inheritance chain: `rabbit` inherits from `animal`, t Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? We did not define it. Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited. -...But why `hasOwnProperty` does not appear in `for..in` loop, like `eats` and `jumps`, if it lists all inherited properties. +...But why does `hasOwnProperty` not appear in the `for..in` loop like `eats` and `jumps` do, if `for..in` lists inherited properties? -The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. That's why they are not listed. +The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. And `for..in` only lists enumerable properties. That's why it and the rest of the `Object.prototype` properties are not listed. -```smart header="All other iteration methods ignore inherited properties" -All other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties. +```smart header="Almost all other key/value-getting methods ignore inherited properties" +Almost all other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties. -They only operate on the object itself. Properties from the prototype are taken into account. +They only operate on the object itself. Properties from the prototype are *not* taken into account. ``` ## Summary @@ -325,6 +324,6 @@ They only operate on the object itself. Properties from the prototype are taken - We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon). - The object referenced by `[[Prototype]]` is called a "prototype". - If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype. -- Write/delete operations for act directly on the object, they don't use the prototype (assuming it's a data property, not is a setter). +- Write/delete operations act directly on the object, they don't use the prototype (assuming it's a data property, not a setter). - If we call `obj.method()`, and the `method` is taken from the prototype, `this` still references `obj`. So methods always work with the current object even if they are inherited. -- The `for..in` loop iterates over both own and inherited properties. All other key/value-getting methods only operate on the object itself. +- The `for..in` loop iterates over both its own and its inherited properties. All other key/value-getting methods only operate on the object itself. diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md index 771e3061c..ebbdf3a7c 100644 --- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md +++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md @@ -7,7 +7,7 @@ Answers: 2. `false`. - Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object is referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`. + Objects are assigned by reference. The object from `Rabbit.prototype` is not duplicated, it's still a single object referenced both by `Rabbit.prototype` and by the `[[Prototype]]` of `rabbit`. So when we change its content through one reference, it is visible through the other one. diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md index 43190e163..0073e252e 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md @@ -15,7 +15,7 @@ alert( user2.name ); // Pete (worked!) It worked, because `User.prototype.constructor == User`. -..But if someone, so to say, overwrites `User.prototype` and forgets to recreate `"constructor"`, then it would fail. +..But if someone, so to speak, overwrites `User.prototype` and forgets to recreate `constructor` to reference `User`, then it would fail. For instance: @@ -41,4 +41,4 @@ Here's how `new user.constructor('Pete')` works: 2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing. 3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. -At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object -- that's what we have in `user2` after all. +At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object, similar to `let user2 = {}`, that's what we have in `user2` after all. diff --git a/1-js/08-prototypes/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md index 7ed0f9c29..29b3773eb 100644 --- a/1-js/08-prototypes/02-function-prototype/article.md +++ b/1-js/08-prototypes/02-function-prototype/article.md @@ -160,8 +160,8 @@ In this chapter we briefly described the way of setting a `[[Prototype]]` for ob Everything is quite simple, just few notes to make things clear: -- The `F.prototype` property is not the same as `[[Prototype]]`. The only thing `F.prototype` does: it sets `[[Prototype]]` of new objects when `new F()` is called. -- The value of `F.prototype` should be either an object or null: other values won't work. +- The `F.prototype` property (don't mess with `[[Prototype]]`) sets `[[Prototype]]` of new objects when `new F()` is called. +- The value of `F.prototype` should be either an object or `null`: other values won't work. - The `"prototype"` property only has such a special effect when set on a constructor function, and invoked with `new`. On regular objects the `prototype` is nothing special: diff --git a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md index e3651683f..99c358c9b 100644 --- a/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md +++ b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md @@ -15,3 +15,27 @@ function f(a, b) { f.defer(1000)(1, 2); // shows 3 after 1 sec ``` + +Please note: we use `this` in `f.apply` to make our decoration work for object methods. + +So if the wrapper function is called as an object method, then `this` is passed to the original method `f`. + +```js run +Function.prototype.defer = function(ms) { + let f = this; + return function(...args) { + setTimeout(() => f.apply(this, args), ms); + } +}; + +let user = { + name: "John", + sayHi() { + alert(this.name); + } +} + +user.sayHi = user.sayHi.defer(1000); + +user.sayHi(); +``` diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index c5e0dbd60..b50e779ec 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. -We'll see how it is for plain objects first, and then for more complex ones. +First we'll see at the details, and then how to use it for adding new capabilities to built-in objects. ## Object.prototype @@ -36,7 +36,7 @@ alert(obj.__proto__ === Object.prototype); // true // obj.toString === obj.__proto__.toString == Object.prototype.toString ``` -Please note that there is no additional `[[Prototype]]` in the chain above `Object.prototype`: +Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`: ```js run alert(Object.prototype.__proto__); // null @@ -46,9 +46,9 @@ alert(Object.prototype.__proto__); // null Other built-in objects such as `Array`, `Date`, `Function` and others also keep methods in prototypes. -For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So the array data is written into the new object, and `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient. +For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient. -By specification, all of the built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects". +By specification, all of the built-in prototypes have `Object.prototype` on the top. That's why some people say that "everything inherits from objects". Here's the overall picture (for 3 built-ins to fit): @@ -122,14 +122,14 @@ String.prototype.show = function() { During the process of development, we may have ideas for new built-in methods we'd like to have, and we may be tempted to add them to native prototypes. But that is generally a bad idea. ```warn -Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them will be overwriting the other. +Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them will be overwriting the method of the other. So, generally, modifying a native prototype is considered a bad idea. ``` **In modern programming, there is only one case where modifying native prototypes is approved. That's polyfilling.** -Polyfilling is a term for making a substitute for a method that exists in JavaScript specification, but not yet supported by current JavaScript engine. +Polyfilling is a term for making a substitute for a method that exists in JavaScript specification, but is not yet supported by current JavaScript engine. Then we may implement it manually and populate the built-in prototype with it. @@ -144,7 +144,7 @@ if (!String.prototype.repeat) { // if there's no such method // actually, the code should be a little bit more complex than that // (the full algorithm is in the specification) - // but even an imperfect polyfill is often considered good enough + // but even an imperfect polyfill is often considered good enough for use return new Array(n + 1).join(this); }; } @@ -161,7 +161,7 @@ That's when we take a method from one object and copy it into another. Some methods of native prototypes are often borrowed. -For instance, if we're making an array-like object, we may want to copy some array methods to it. +For instance, if we're making an array-like object, we may want to copy some `Array` methods to it. E.g. @@ -193,4 +193,4 @@ Borrowing methods is flexible, it allows to mix functionality from different obj - The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype` etc). - The object itself stores only the data (array items, object properties, the date). - Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype`, `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects. -- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. Probably the only allowable cause is when we add-in a new standard, but not yet supported by the engine JavaScript method. +- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. Probably the only allowable case is when we add-in a new standard, but not yet supported by the engine JavaScript method. diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index 9ff71045d..a229483f1 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -26,8 +26,9 @@ let rabbit = Object.create(animal); */!* alert(rabbit.eats); // true + *!* -alert(Object.getPrototypeOf(rabbit) === animal); // get the prototype of rabbit +alert(Object.getPrototypeOf(rabbit) === animal); // true */!* *!* @@ -70,18 +71,18 @@ Why so? That's for historical reasons. -- The `"prototype"` property of a constructor function works since very ancient times. -- Later in the year 2012: `Object.create` appeared in the standard. It allowed to create objects with the given prototype, but did not allow to get/set it. So browsers implemented non-standard `__proto__` accessor that allowed to get/set a prototype at any time. -- Later in the year 2015: `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, to perform the same functionality as `__proto__`. As `__proto__` was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is optional for non-browser environments. +- The `"prototype"` property of a constructor function has worked since very ancient times. +- Later, in the year 2012, `Object.create` appeared in the standard. It gave the ability to create objects with a given prototype, but did not provide the ability to get/set it. So browsers implemented the non-standard `__proto__` accessor that allowed the user to get/set a prototype at any time. +- Later, in the year 2015, `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, to perform the same functionality as `__proto__`. As `__proto__` was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is: optional for non-browser environments. As of now we have all these ways at our disposal. Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`? That's an interesting question, requiring us to understand why `__proto__` is bad. Read on to get the answer. -```warn header="Don't reset `[[Prototype]]` unless the speed doesn't matter" +```warn header="Don't change `[[Prototype]]` on existing objects if speed matters" Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time, and then do not modify: `rabbit` inherits from `animal`, and that is not going to change. -And JavaScript engines are highly optimized to that. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation, it breaks internal optimizations for object property access operations. So evade it unless you know what you're doing, or JavaScript speed totally doesn't matter for you. +And JavaScript engines are highly optimized for this. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation, it breaks internal optimizations for object property access operations. So avoid it unless you know what you're doing, or JavaScript speed totally doesn't matter for you. ``` ## "Very plain" objects [#very-plain] @@ -107,17 +108,17 @@ That shouldn't surprise us. The `__proto__` property is special: it must be eith But we didn't *intend* to implement such behavior, right? We want to store key/value pairs, and the key named `"__proto__"` was not properly saved. So that's a bug! -Here the consequences are not terrible. But in other cases, we may be assigning object values, then the prototype may indeed be changed. As the result, the execution will go wrong in totally unexpected ways. +Here the consequences are not terrible. But in other cases we may be assigning object values, and then the prototype may indeed be changed. As the result, the execution will go wrong in totally unexpected ways. -What's worst -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side. +What's worse -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side. -Unexpected things also may happen when accessing `toString` property -- that's a function by default, and other built-in properties. +Unexpected things also may happen when assigning to `toString`, which is a function by default, and to other built-in methods. -How to evade the problem? +How to avoid the problem? First, we can just switch to using `Map`, then everything's fine. -But `Object` also can serve us well here, because language creators gave a thought to that problem long ago. +But `Object` also can serve us well here, because language creators gave thought to that problem long ago. The `__proto__` is not a property of an object, but an accessor property of `Object.prototype`: @@ -160,7 +161,7 @@ alert(obj); // Error (no toString) ...But that's usually fine for associative arrays. -Please note that most object-related methods are `Object.something(...)`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects: +Note that most object-related methods are `Object.something(...)`, like `Object.keys(obj)` -- they are not in the prototype, so they will keep working on such objects: ```js run @@ -189,7 +190,7 @@ Also, `Object.create` provides an easy way to shallow-copy an object with all de let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` -We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just as other methods. +We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just like other methods. We can create an object without a prototype by `Object.create(null)`. Such objects are used as "pure dictionaries", they have no issues with `"__proto__"` as the key. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index cd6579c07..d6c729d25 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -25,7 +25,7 @@ class MyClass { } ``` -Then `new MyClass()` creates a new object with all the listed methods. +Then use `new MyClass()` to create a new object with all the listed methods. The `constructor()` method is called automatically by `new`, so we can initialize the object there. @@ -53,7 +53,7 @@ When `new User("John")` is called: 1. A new object is created. 2. The `constructor` runs with the given argument and assigns `this.name` to it. -...Then we can call methods, such as `user.sayHi`. +...Then we can call object methods, such as `user.sayHi()`. ```warn header="No comma between class methods" @@ -89,7 +89,7 @@ What `class User {...}` construct really does is: 1. Creates a function named `User`, that becomes the result of the class declaration. The function code is taken from the `constructor` method (assumed empty if we don't write such method). 2. Stores class methods, such as `sayHi`, in `User.prototype`. -Afterwards, for `new User` objects, when we call a method, it's taken from the prototype, just as described in the chapter . So the object has access to class methods. +After `new User` object is created, when we call its method, it's taken from the prototype, just as described in the chapter . So the object has access to class methods. We can illustrate the result of `class User` declaration as: @@ -142,11 +142,11 @@ user.sayHi(); The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntax sugar to define a constructor together with its prototype methods. -Although, there are important differences. +Still, there are important differences. 1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually. - Unlike a regular function, a class constructor must be called with `new`: + And unlike a regular function, a class constructor must be called with `new`: ```js run class User { @@ -191,7 +191,7 @@ let User = class { }; ``` -Similar to Named Function Expressions, class expressions may or may not have a name. +Similar to Named Function Expressions, class expressions may have a name. If a class expression has a name, it's visible inside the class only: @@ -200,13 +200,13 @@ If a class expression has a name, it's visible inside the class only: // (no such term in the spec, but that's similar to Named Function Expression) let User = class *!*MyClass*/!* { sayHi() { - alert(MyClass); // MyClass is visible only inside the class + alert(MyClass); // MyClass name is visible only inside the class } }; new User().sayHi(); // works, shows MyClass definition -alert(MyClass); // error, MyClass not visible outside of the class +alert(MyClass); // error, MyClass name isn't visible outside of the class ``` @@ -231,7 +231,7 @@ new User().sayHi(); // Hello ## Getters/setters, other shorthands -Just like literal objects, classes may include getters/setters, generators, computed properties etc. +Just like literal objects, classes may include getters/setters, computed properties etc. Here's an example for `user.name` implemented using `get/set`: @@ -282,13 +282,14 @@ Object.defineProperties(User.prototype, { }); ``` -Here's an example with computed properties: +Here's an example with a computed property in brackets `[...]`: ```js run -function f() { return "sayHi"; } - class User { - [f()]() { + +*!* + ['say' + 'Hi']() { +*/!* alert("Hello"); } @@ -297,8 +298,6 @@ class User { new User().sayHi(); ``` -For a generator method, similarly, prepend it with `*`. - ## Class properties ```warn header="Old browsers may need a polyfill" @@ -309,7 +308,9 @@ In the example above, `User` only had methods. Let's add a property: ```js run class User { +*!* name = "Anonymous"; +*/!* sayHi() { alert(`Hello, ${this.name}!`); @@ -319,8 +320,7 @@ class User { new User().sayHi(); ``` -The property is not placed into `User.prototype`. Instead, it is created by `new`, separately for every object. So, the property will never be shared between different objects of the same class. - +The property `name` is not placed into `User.prototype`. Instead, it is created by `new` before calling the constructor, it's a property of the object itself. ## Summary @@ -328,7 +328,7 @@ The basic class syntax looks like this: ```js class MyClass { - prop = value; // field + prop = value; // property constructor(...) { // constructor // ... @@ -339,7 +339,7 @@ class MyClass { get something(...) {} // getter method set something(...) {} // setter method - [Symbol.iterator]() {} // method with computed name/symbol name + [Symbol.iterator]() {} // method with computed name (symbol here) // ... } ``` diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md index fa26ec834..ca9e80601 100644 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md +++ b/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md @@ -26,7 +26,7 @@ Even after the fix, there's still important difference in `"class Rabbit extends As we know, the "extends" syntax sets up two prototypes: 1. Between `"prototype"` of the constructor functions (for methods). -2. Between the constructor functions itself (for static methods). +2. Between the constructor functions themselves (for static methods). In our case, for `class Rabbit extends Object` it means: diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md index ca6628edf..b82a4255e 100644 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md +++ b/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md @@ -19,7 +19,6 @@ let rabbit = new Rabbit("Rab"); *!* // hasOwnProperty method is from Object.prototype -// rabbit.__proto__ === Object.prototype alert( rabbit.hasOwnProperty('name') ); // true */!* ``` diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 29d3188ad..882907a1b 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -1,9 +1,13 @@ # Class inheritance -Let's say we have two classes. +Class inheritance is a way for one class to extend another class. -`Animal`: +So we can create new functionality on top of the existing. + +## The "extends" keyword + +Let's say with have class `Animal`: ```js class Animal { @@ -17,28 +21,26 @@ class Animal { } stop() { this.speed = 0; - alert(`${this.name} stopped.`); + alert(`${this.name} stands still.`); } } let animal = new Animal("My animal"); ``` +<<<<<<< HEAD ![](rabbit-animal-independent-animal.svg) +======= +Here's how we can represent `animal` object and `Animal` class graphically: +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac +![](rabbit-animal-independent-animal.svg) -...And `Rabbit`: +...And we would like to create another `class Rabbit`. -```js -class Rabbit { - constructor(name) { - this.name = name; - } - hide() { - alert(`${this.name} hides!`); - } -} +As rabbits are animals, `Rabbit` class should be based on `Animal`, have access to animal methods, so that rabbits can do what "generic" animals can do. +<<<<<<< HEAD let rabbit = new Rabbit("My rabbit"); ``` @@ -48,28 +50,13 @@ let rabbit = new Rabbit("My rabbit"); Right now they are fully independent. But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods. +======= +The syntax to extend another class is: `class Child extends Parent`. +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac -To inherit from another class, we should specify `"extends"` and the parent class before the braces `{..}`. - -Here `Rabbit` inherits from `Animal`: - -```js run -class Animal { - constructor(name) { - this.speed = 0; - this.name = name; - } - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - stop() { - this.speed = 0; - alert(`${this.name} stopped.`); - } -} +Let's create `class Rabbit` that inherits from `Animal`: -// Inherit from Animal by specifying "extends Animal" +```js *!* class Rabbit extends Animal { */!* @@ -84,15 +71,18 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides! ``` -Now the `Rabbit` code became a bit shorter, as it uses `Animal` constructor by default, and it also can `run`, as animals do. +Object of `Rabbit` class have access to both `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. -Internally, `extends` keyword adds `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`: +Internally, `extends` keyword works using the good old prototype mechanics. It sets `Rabbit.prototype.[[Prototype]]` to `Animal.prototype`. So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. ![](animal-rabbit-extends.svg) -So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. +For instance, to find `rabbit.run` method, the engine checks (bottom-up on the picture): +1. The `rabbit` object (has no `run`). +2. Its prototype, that is `Rabbit.prototype` (has `hide`, but not `run`). +3. Its prototype, that is (due to `extends`) `Animal.prototype`, that finally has the `run` method. -As we can recall from the chapter , JavaScript uses the same prototypal inheritance for build-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`, so dates have generic object methods. +As we can recall from the chapter , JavaScript itself uses prototypal inheritance for build-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`. That's why dates have access to generic object methods. ````smart header="Any expression is allowed after `extends`" Class syntax allows to specify not just a class, but any expression after `extends`. @@ -119,20 +109,20 @@ That may be useful for advanced programming patterns when we use functions to ge ## Overriding a method -Now let's move forward and override a method. As of now, `Rabbit` inherits the `stop` method that sets `this.speed = 0` from `Animal`. +Now let's move forward and override a method. By default, all methods that are not specified in `class Rabbit` are taken directly "as is" from `class Animal`. -If we specify our own `stop` in `Rabbit`, then it will be used instead: +But if we specify our own method in `Rabbit`, such as `stop()` then it will be used instead: ```js class Rabbit extends Animal { stop() { - // ...this will be used for rabbit.stop() + // ...now this will be used for rabbit.stop() + // instead of stop() from class Animal } } ``` - -...But usually we don't want to totally replace a parent method, but rather to build on top of it, tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. +Usually we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. Classes provide `"super"` keyword for that. @@ -156,7 +146,7 @@ class Animal { stop() { this.speed = 0; - alert(`${this.name} stopped.`); + alert(`${this.name} stands still.`); } } @@ -177,7 +167,7 @@ class Rabbit extends Animal { let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stopped. White rabbit hides! +rabbit.stop(); // White Rabbit stands still. White rabbit hides! ``` Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process. @@ -260,18 +250,18 @@ The short answer is: constructors in inheriting classes must call `super(...)`, ...But why? What's going on here? Indeed, the requirement seems strange. -Of course, there's an explanation. Let's get into details, so you'd really understand what's going on. +Of course, there's an explanation. Let's get into details, so you'll really understand what's going on. -In JavaScript, there's a distinction between a "constructor function of an inheriting class" and all others. In an inheriting class, the corresponding constructor function is labelled with a special internal property `[[ConstructorKind]]:"derived"`. +In JavaScript, there's a distinction between a "constructor function of an inheriting class" and all others. In an inheriting class, the corresponding constructor function is labeled with a special internal property `[[ConstructorKind]]:"derived"`. The difference is: -- When a normal constructor runs, it creates an empty object as `this` and continues with it. -- But when a derived constructor runs, it doesn't do it. It expects the parent constructor to do this job. +- When a normal constructor runs, it creates an empty object and assigns it to `this`. +- But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job. -So if we're making a constructor of our own, then we must call `super`, because otherwise the object with `this` reference to it won't be created. And we'll get an error. +So if we're making a constructor of our own, then we must call `super`, because otherwise the object for `this` won't be created. And we'll get an error. -For `Rabbit` to work, we need to call `super()` before using `this`, like here: +For `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here: ```js run class Animal { @@ -307,16 +297,24 @@ alert(rabbit.earLength); // 10 ## Super: internals, [[HomeObject]] -Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way. +```warn header="Advanced information" +If you're reading the tutorial for the first time - this section may be skipped. + +It's about the internal mechanisms behind inheritance and `super`. +``` + +Let's get a little deeper under the hood of `super`. We'll see some interesting things along the way. First to say, from all that we've learned till now, it's impossible for `super` to work at all! -Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, it needs to retrieve the `method` from the prototype of the current object. +Yeah, indeed, let's ask ourselves, how it should technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, the engine needs to get the `method` from the prototype of the current object. But how? The task may seem simple, but it isn't. The engine knows the current object `this`, so it could get the parent `method` as `this.__proto__.method`. Unfortunately, such a "naive" solution won't work. Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity. +You may skip this part and go below to the `[[HomeObject]]` subsection if you don't want to know the details. That won't harm. Or read on if you're interested in understanding things in-depth. + In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`: ```js run @@ -460,7 +458,7 @@ The very existance of `[[HomeObject]]` violates that principle, because methods The only place in the language where `[[HomeObject]]` is used -- is `super`. So, if a method does not use `super`, then we can still consider it free and copy between objects. But with `super` things may go wrong. -Here's the demo of a wrong `super` call: +Here's the demo of a wrong `super` result after copying: ```js run let animal = { @@ -469,6 +467,7 @@ let animal = { } }; +// rabbit inherits from animal let rabbit = { __proto__: animal, sayHi() { @@ -482,6 +481,7 @@ let plant = { } }; +// tree inherits from plant let tree = { __proto__: plant, *!* @@ -498,9 +498,14 @@ A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong. The reason is simple: - In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication? -- So its `[[HomeObject]]` is `rabbit`, as it was created in `rabbit`. There's no way to change `[[HomeObject]]`. +- Its `[[HomeObject]]` is `rabbit`, as it was created in `rabbit`. There's no way to change `[[HomeObject]]`. - The code of `tree.sayHi()` has `super.sayHi()` inside. It goes up from `rabbit` and takes the method from `animal`. +<<<<<<< HEAD +======= +Here's the diagram of what happens: + +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac ![](super-homeobject-wrong.svg) ### Methods, not function properties @@ -513,7 +518,7 @@ In the example below a non-method syntax is used for comparison. `[[HomeObject]] ```js run let animal = { - eat: function() { // should be the short syntax: eat() {...} + eat: function() { // intentially writing like this instead of eat() {... // ... } }; 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 608788f7a..49944d9a2 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, not to its `"prototype"`. Such methods are called *static*. +We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*. -An example: +In a class, they are prepended by `static` keyword, like this: ```js run class User { @@ -17,7 +17,7 @@ class User { User.staticMethod(); // true ``` -That actually does the same as assigning it as a property: +That actually does the same as assigning it as a property directly: ```js class User() { } @@ -27,11 +27,11 @@ User.staticMethod = function() { }; ``` -The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule). +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. -For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this: +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: ```js run class Article { @@ -61,13 +61,13 @@ articles.sort(Article.compare); alert( articles[0].title ); // CSS ``` -Here `Article.compare` stands "over" the articles, as a means to compare them. It's not a method of an article, but rather of the whole class. +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. Another example would be a so-called "factory" method. Imagine, we need few ways to create an article: 1. Create by given parameters (`title`, `date` etc). 2. Create an empty article with today's date. -3. ... +3. ...or else somehow. The first way can be implemented by the constructor. And for the second one we can make a static method of the class. @@ -90,7 +90,7 @@ class Article { let article = Article.createTodays(); -alert( article.title ); // Todays digest +alert( article.title ); // Today's digest ``` Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class. @@ -107,7 +107,7 @@ Article.remove({id: 12345}); [recent browser=Chrome] -Static properties are also possible, just like regular class properties: +Static properties are also possible, they look like regular class properties, but prepended by `static`: ```js run class Article { @@ -123,9 +123,9 @@ That is the same as a direct assignment to `Article`: Article.publisher = "Ilya Kantor"; ``` -## Statics and inheritance +## Inheritance of static methods -Statics are inherited, we can access `Parent.method` as `Child.method`. +Static methods are inherited. For instance, `Animal.compare` in the code below is inherited and accessible as `Rabbit.compare`: @@ -169,36 +169,43 @@ rabbits.sort(Rabbit.compare); rabbits[0].run(); // Black Rabbit runs with speed 5. ``` -Now we can call `Rabbit.compare` assuming that the inherited `Animal.compare` will be called. +Now when we can call `Rabbit.compare`, the inherited `Animal.compare` will be called. How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`. +![](animal-rabbit-static.svg) +<<<<<<< HEAD ![](animal-rabbit-static.svg) +======= +So, `Rabbit extends Animal` creates two `[[Prototype]]` references: +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac + +1. `Rabbit` function prototypally inherits from `Animal` function. +2. `Rabbit.prototype` prototypally inherits from `Animal.prototype`. -So, `Rabbit` function now inherits from `Animal` function. And `Animal` function normally has `[[Prototype]]` referencing `Function.prototype`, because it doesn't `extend` anything. +As the result, inheritance works both for regular and static methods. -Here, let's check that: +Here, let's check that by code: ```js run class Animal {} class Rabbit extends Animal {} -// for static properties and methods +// for statics alert(Rabbit.__proto__ === Animal); // true -// the next step up leads to Function.prototype -alert(Animal.__proto__ === Function.prototype); // true - -// the "normal" prototype chain for object methods -alert(Rabbit.prototype.__proto__ === Animal.prototype); +// for regular methods +alert(Rabbit.prototype.__proto__ === Animal.prototype); // true ``` -This way `Rabbit` has access to all static methods of `Animal`. - ## Summary -Static methods are used for the functionality that doesn't relate to a concrete class instance, doesn't require an instance to exist, but rather belongs to the class as a whole, like `Article.compare` -- a generic method to compare two articles. +Static methods are used for the functionality that belongs to the class "as a whole", doesn't relate to a concrete class instance. + +For example, a method for comparison `Article.compare(article1, article2)` or a factory method `Article.createTodays()`. + +They are labeled by the word `static` in class declaration. Static properties are used when we'd like to store class-level data, also not bound to an instance. @@ -214,13 +221,13 @@ class MyClass { } ``` -That's technically the same as assigning to the class itself: +Technically, static declaration is the same as assigning to the class itself: ```js MyClass.property = ... MyClass.method = ... ``` -Static properties are inherited. +Static properties and methods are inherited. -Technically, for `class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`. +For `class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`. diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 8946d2f7f..fb1a964c9 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -53,7 +53,7 @@ In JavaScript, there are two types of object fields (properties and methods): - Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods. - Private: accessible only from inside the class. These are for the internal interface. -In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it. They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them. +In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it (like private, but plus access from inheriting classes). They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them. Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated. @@ -248,7 +248,7 @@ Unlike protected ones, private fields are enforced by the language itself. That' But if we inherit from `CoffeeMachine`, then we'll have no direct access to `#waterAmount`. We'll need to rely on `waterAmount` getter/setter: ```js -class MegaCoffeeMachine extends CoffeeMachine() { +class MegaCoffeeMachine extends CoffeeMachine { method() { *!* alert( this.#waterAmount ); // Error: can only access from CoffeeMachine @@ -297,12 +297,12 @@ Supportable **If we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users.** - If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them. + If you're a developer of such class, it's great to know that private methods can be safely renamed, their parameters can be changed, and even removed, because no external code depends on them. For users, when a new version comes out, it may be a total overhaul internally, but still simple to upgrade if the external interface is the same. Hiding complexity -: People adore to use things that are simple. At least from outside. What's inside is a different thing. +: People adore using things that are simple. At least from outside. What's inside is a different thing. Programmers are not an exception. diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md index dfe2ddf33..9a5686734 100644 --- a/1-js/09-classes/05-extend-natives/article.md +++ b/1-js/09-classes/05-extend-natives/article.md @@ -21,14 +21,14 @@ alert(filteredArr); // 10, 50 alert(filteredArr.isEmpty()); // false ``` -Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type. They rely on the `constructor` property to do so. +Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type `PowerArray`. Their internal implementation uses the object's `constructor` property for that. In the example above, ```js arr.constructor === PowerArray ``` -So when `arr.filter()` is called, it internally creates the new array of results using exactly `new PowerArray`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result. +When `arr.filter()` is called, it internally creates the new array of results using exactly `arr.constructor`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result. Even more, we can customize that behavior. @@ -64,20 +64,26 @@ alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further. +```smart header="Other collections work similarly" +Other collections, such as `Map` and `Set`, work alike. They also use `Symbol.species`. +``` + ## No static inheritance in built-ins Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc. As we already know, native classes extend each other. For instance, `Array` extends `Object`. -Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the chapter [](info:static-properties-methods#statics-and-inheritance). +Normally, when one class extends another, both static and non-static methods are inherited. That was thoroughly explained in the article [](info:static-properties-methods#statics-and-inheritance). But built-in classes are an exception. They don't inherit statics from each other. -For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no `Array.keys()` and `Date.keys()` static methods. +For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no, for instance, `Array.keys()` (or `Date.keys()`) static method. Here's the picture structure for `Date` and `Object`: ![](object-date-inheritance.svg) As you can see, there's no link between `Date` and `Object`. They are independent, only `Date.prototype` inherits from `Object.prototype`. + +That's an important difference of inheritance between built-in objects compared to what we get with `extends`. diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index 684901c64..544e07eb7 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -11,7 +11,7 @@ The syntax is: obj instanceof Class ``` -It returns `true` if `obj` belongs to the `Class` (or a class inheriting from it). +It returns `true` if `obj` belongs to the `Class` or a class inheriting from it. For instance: @@ -44,17 +44,19 @@ alert( arr instanceof Array ); // true alert( arr instanceof Object ); // true ``` -Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypally inherits from `Object`. +Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypically inherits from `Object`. -The `instanceof` operator examines the prototype chain for the check, but we can set a custom logic in the static method `Symbol.hasInstance`. +Normally, `instanceof` operator examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`. The algorithm of `obj instanceof Class` works roughly as follows: -1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`. We're done. - For example: +1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`, and we're done. That's how we can customize the behavior of `instanceof`. + + For example: ```js run - // setup instanceOf check that assumes that anything that canEat is an animal + // setup instanceOf check that assumes that + // anything with canEat property is an animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; @@ -68,17 +70,19 @@ The algorithm of `obj instanceof Class` works roughly as follows: 2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` equals to one of prototypes in the `obj` prototype chain. - In other words, compare: + In other words, compare one after another: ```js - obj.__proto__ === Class.prototype - obj.__proto__.__proto__ === Class.prototype - obj.__proto__.__proto__.__proto__ === Class.prototype + obj.__proto__ === Class.prototype? + obj.__proto__.__proto__ === Class.prototype? + obj.__proto__.__proto__.__proto__ === Class.prototype? ... + // if any answer is true, return true + // otherwise, if we reached the end of the chain, return false ``` - In the example above `Rabbit.prototype === rabbit.__proto__`, so that gives the answer immediately. + In the example above `rabbit.__proto__ === Rabbit.prototype`, so that gives the answer immediately. - In the case of an inheritance, `rabbit` is an instance of the parent class as well: + In the case of an inheritance, the match will be at the second step: ```js run class Animal {} @@ -88,8 +92,11 @@ The algorithm of `obj instanceof Class` works roughly as follows: *!* alert(rabbit instanceof Animal); // true */!* + // rabbit.__proto__ === Rabbit.prototype + *!* // rabbit.__proto__.__proto__ === Animal.prototype (match!) + */!* ``` Here's the illustration of what `rabbit instanceof Animal` compares with `Animal.prototype`: @@ -98,9 +105,9 @@ Here's the illustration of what `rabbit instanceof Animal` compares with `Animal By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), that returns `true` if `objA` is somewhere in the chain of prototypes for `objB`. So the test of `obj instanceof Class` can be rephrased as `Class.prototype.isPrototypeOf(obj)`. -That's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters. +It's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters. -That can lead to interesting consequences when `prototype` is changed. +That can lead to interesting consequences when `prototype` property is changed after the object is created. Like here: @@ -117,8 +124,6 @@ alert( rabbit instanceof Rabbit ); // false */!* ``` -That's one of the reasons to avoid changing `prototype`. Just to keep safe. - ## Bonus: Object.prototype.toString for the type We already know that plain objects are converted to string as `[object Object]`: @@ -152,7 +157,7 @@ let objectToString = Object.prototype.toString; // what type is this? let arr = []; -alert( objectToString.call(arr) ); // [object Array] +alert( objectToString.call(arr) ); // [object *!*Array*/!*] ``` Here we used [call](mdn:js/function/call) as described in the chapter [](info:call-apply-decorators) to execute the function `objectToString` in the context `this=arr`. @@ -196,11 +201,11 @@ As you can see, the result is exactly `Symbol.toStringTag` (if exists), wrapped At the end we have "typeof on steroids" that not only works for primitive data types, but also for built-in objects and even can be customized. -It can be used instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check. +We can use `{}.toString.call` instead of `instanceof` for built-in objects when we want to get the type as a string rather than just to check. ## Summary -Let's recap the type-checking methods that we know: +Let's summarize the type-checking methods that we know: | | works for | returns | |---------------|-------------|---------------| diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 3e06800a9..69118f5ac 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -2,19 +2,19 @@ In JavaScript we can only inherit from a single object. There can be only one `[[Prototype]]` for an object. And a class may extend only one other class. -But sometimes that feels limiting. For instance, I have a class `StreetSweeper` and a class `Bicycle`, and want to make a `StreetSweepingBicycle`. +But sometimes that feels limiting. For instance, we have a class `StreetSweeper` and a class `Bicycle`, and want to make their mix: a `StreetSweepingBicycle`. -Or, talking about programming, we have a class `User` and a class `EventEmitter` that implements event generation, and we'd like to add the functionality of `EventEmitter` to `User`, so that our users can emit events. +Or we have a class `User` and a class `EventEmitter` that implements event generation, and we'd like to add the functionality of `EventEmitter` to `User`, so that our users can emit events. There's a concept that can help here, called "mixins". -As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class that contains methods for use by other classes without having to be the parent class of those other classes. +As defined in Wikipedia, a [mixin](https://en.wikipedia.org/wiki/Mixin) is a class containing methods that can be used by other classes without a need to inherit from it. In other words, a *mixin* provides methods that implement a certain behavior, but we do not use it alone, we use it to add the behavior to other classes. ## A mixin example -The simplest way to make a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class. +The simplest way to implement a mixin in JavaScript is to make an object with useful methods, so that we can easily merge them into a prototype of any class. For instance here the mixin `sayHiMixin` is used to add some "speech" for `User`: @@ -75,10 +75,10 @@ let sayHiMixin = { *!* // call parent method */!* - super.say(`Hello ${this.name}`); + super.say(`Hello ${this.name}`); // (*) }, sayBye() { - super.say(`Bye ${this.name}`); + super.say(`Bye ${this.name}`); // (*) } }; @@ -95,11 +95,17 @@ Object.assign(User.prototype, sayHiMixin); new User("Dude").sayHi(); // Hello Dude! ``` -Please note that the call to the parent method `super.say()` from `sayHiMixin` looks for the method in the prototype of that mixin, not the class. +Please note that the call to the parent method `super.say()` from `sayHiMixin` (at lines labelled with `(*)`) looks for the method in the prototype of that mixin, not the class. +<<<<<<< HEAD ![](mixin-inheritance.svg) +======= +Here's the diagram (see the right part): +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac -That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So their `[[HomeObject]]` internal property references `sayHiMixin`, as shown on the picture above. +![](mixin-inheritance.svg) + +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 on the picture above. As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. @@ -107,13 +113,13 @@ As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that mean Now let's make a mixin for real life. -An important feature of many browser objects (for instance) is that they can generate events. Events is a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows to easily add event-related functions to any class/object. +An important feature of many browser objects (for instance) is that they can generate events. Events are a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows us to easily add event-related functions to any class/object. - The mixin will provide a method `.trigger(name, [...data])` to "generate an event" when something important happens to it. The `name` argument is a name of the event, optionally followed by additional arguments with event data. - Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when an event with the given `name` triggers, and get the arguments from `.trigger` call. -- ...And the method `.off(name, handler)` that removes `handler` listener. +- ...And the method `.off(name, handler)` that removes the `handler` listener. -After adding the mixin, an object `user` will become able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen to such events to load the calendar for the logged-in person. +After adding the mixin, an object `user` will be able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen for such events to load the calendar for the logged-in person. Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may assign handlers to react on that event. And so on. @@ -163,7 +169,7 @@ let eventMixin = { ``` -- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. Technically, there's `_eventHandlers` property, that stores an array of handlers for each event name. So it just adds it to the list. +- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name occurs. Technically, there's an `_eventHandlers` property that stores an array of handlers for each event name, and it just adds it to the list. - `.off(eventName, handler)` -- removes the function from the handlers list. - `.trigger(eventName, ...args)` -- generates the event: all handlers from `_eventHandlers[eventName]` are called, with a list of arguments `...args`. @@ -191,7 +197,7 @@ menu.on("select", value => alert(`Value selected: ${value}`)); menu.choose("123"); ``` -Now if we'd like any code to react on menu selection, we can listen to it with `menu.on(...)`. +Now, if we'd like any code to react to a menu selection, we can listen for it with `menu.on(...)`. And `eventMixin` mixin makes it easy to add such behavior to as many classes as we'd like, without interfering with the inheritance chain. @@ -199,8 +205,8 @@ And `eventMixin` mixin makes it easy to add such behavior to as many classes as *Mixin* -- is a generic object-oriented programming term: a class that contains methods for other classes. -Some other languages like e.g. Python allow to create mixins using multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype. +Some other languages allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype. -We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above. +We can use mixins as a way to augment a class by adding multiple behaviors, like event-handling as we have seen above. -Mixins may become a point of conflict if they occasionally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that. +Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that happening. diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md index 05ba72e00..303431d6d 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md @@ -44,4 +44,4 @@ function f() { f(); // cleanup! ``` -It's `finally` that guarantees the cleanup here. If we just put the code at the end of `f`, it wouldn't run. +It's `finally` that guarantees the cleanup here. If we just put the code at the end of `f`, it wouldn't run in these situations. 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 8b265e222..0356fa2a8 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -1,6 +1,6 @@ # Error handling, "try..catch" -No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response and for a thousand of other reasons. +No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons. Usually, a script "dies" (immediately stops) in case of an error, printing it to console. @@ -25,14 +25,14 @@ try { It works like this: 1. First, the code in `try {...}` is executed. -2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and then jumps over `catch`. -3. If an error occurs, then `try` execution is stopped, and the control flows to the beginning of `catch(err)`. The `err` variable (can use any name for it) contains an error object with details about what's happened. +2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. +3. If an error occurs, then `try` execution is stopped, and the control flows to the beginning of `catch(err)`. The `err` variable (can use any name for it) will contain an error object with details about what happened. ![](try-catch-flow.svg) So, an error inside the `try {…}` block does not kill the script: we have a chance to handle it in `catch`. -Let's see more examples. +Let's see examples. - An errorless example: shows `alert` `(1)` and `(2)`: @@ -50,8 +50,6 @@ Let's see more examples. alert('Catch is ignored, because there are no errors'); // (3) } - - alert("...Then the execution continues"); ``` - An example with an error: shows `(1)` and `(3)`: @@ -71,8 +69,6 @@ Let's see more examples. alert(`Error has occurred!`); // *!*(3) <--*/!* } - - alert("...Then the execution continues"); ``` @@ -89,7 +85,7 @@ try { } ``` -The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phrase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. +The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. So, `try..catch` can only handle errors that occur in the valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". ```` @@ -134,10 +130,10 @@ try { } ``` -For all built-in errors, the error object inside `catch` block has two main properties: +For all built-in errors, the error object has two main properties: `name` -: Error name. For an undefined variable that's `"ReferenceError"`. +: Error name. For instance, for an undefined variable that's `"ReferenceError"`. `message` : Textual message about error details. @@ -157,7 +153,7 @@ try { } catch(err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined - alert(err.stack); // ReferenceError: lalala is not defined at ... + alert(err.stack); // ReferenceError: lalala is not defined at (...call stack) // Can also show an error as a whole // The error is converted to string as "name: message" @@ -174,8 +170,8 @@ If we don't need error details, `catch` may omit it: ```js try { // ... -} catch { - // error object omitted +} catch { // <-- without (err) + // ... } ``` @@ -187,7 +183,7 @@ As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) Usually it's used to decode data received over the network, from the server or another source. -We receive it and call `JSON.parse`, like this: +We receive it and call `JSON.parse` like this: ```js run let json = '{"name":"John", "age": 30}'; // data from the server @@ -302,13 +298,13 @@ try { *!* alert(e.name); // SyntaxError */!* - alert(e.message); // Unexpected token o in JSON at position 0 + alert(e.message); // Unexpected token o in JSON at position 2 } ``` As we can see, that's a `SyntaxError`. -And in our case, the absence of `name` could be treated as a syntax error also, assuming that users must have a `name`. +And in our case, the absence of `name` is an error, as users must have a `name`. So let's throw it: @@ -526,7 +522,7 @@ alert(result || "error occurred"); alert( `execution took ${diff}ms` ); ``` -You can check by running the code with entering `35` into `prompt` -- it executes normally, `finally` after `try`. And then enter `-1` -- there will be an immediate error, an the execution will take `0ms`. Both measurements are done correctly. +You can check by running the code with entering `35` into `prompt` -- it executes normally, `finally` after `try`. And then enter `-1` -- there will be an immediate error, and the execution will take `0ms`. Both measurements are done correctly. In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases. @@ -534,7 +530,7 @@ In other words, the function may finish with `return` or `throw`, that doesn't m ```smart header="Variables are local inside `try..catch..finally`" Please note that `result` and `diff` variables in the code above are declared *before* `try..catch`. -Otherwise, if `let` were made inside the `{...}` block, it would only be visible inside of it. +Otherwise, if we declared `let` in `try` block, it would only be visible inside of it. ``` ````smart header="`finally` and `return`" @@ -565,7 +561,7 @@ alert( func() ); // first works alert from finally, and then this one ````smart header="`try..finally`" -The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors right here, but want to be sure that processes that we started are finalized. +The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. ```js function func() { @@ -577,7 +573,7 @@ function func() { } } ``` -In the code above, an error inside `try` always falls out, because there's no `catch`. But `finally` works before the execution flow jumps outside. +In the code above, an error inside `try` always falls out, because there's no `catch`. But `finally` works before the execution flow leaves the function. ```` ## Global catch @@ -590,7 +586,7 @@ Let's imagine we've got a fatal error outside of `try..catch`, and the script di Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages) etc. -There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [process.on('uncaughtException')](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property. It will run in case of an uncaught error. +There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property, that will run in case of an uncaught error. The syntax: @@ -637,7 +633,7 @@ There are also web-services that provide error-logging for such cases, like ; // non-standard, but most environments support it + this.stack = ; // non-standard, but most environments support it } } ``` -Now let's go on and inherit `ValidationError` from it: +Now let's inherit `ValidationError` from it and try it in action: ```js run untrusted *!* @@ -65,10 +61,9 @@ try { } ``` -Please take a look at the constructor: +Please note: in the line `(1)` we call the parent constructor. JavaScript requires us to call `super` in the child constructor, so that's obligatory. The parent constructor sets the `message` property. -1. In the line `(1)` we call the parent constructor. JavaScript requires us to call `super` in the child constructor, so that's obligatory. The parent constructor sets the `message` property. -2. The parent constructor also sets the `name` property to `"Error"`, so in the line `(2)` we reset it to the right value. +The parent constructor also sets the `name` property to `"Error"`, so in the line `(2)` we reset it to the right value. Let's try to use it in `readUser(json)`: @@ -185,7 +180,7 @@ try { The new class `PropertyRequiredError` is easy to use: we only need to pass the property name: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor. -Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. But there's a way out. We can make our own "basic error" class that removes this burden from our shoulders by using `this.constructor.name` for `this.name` in its constructor. And then inherit all ours custom errors from it. +Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. We can avoid it by making our own "basic error" class that assigns `this.name = this.constructor.name`. And then inherit all ours custom errors from it. Let's call it `MyError`. @@ -218,11 +213,11 @@ Now custom errors are much shorter, especially `ValidationError`, as we got rid ## Wrapping exceptions -The purpose of the function `readUser` in the code above is "to read the user data", right? There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow and probably generate other kinds of errors. +The purpose of the function `readUser` in the code above is "to read the user data". There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow and probably generate other kinds of errors. -The code which calls `readUser` should handle these errors. Right now it uses multiple `if` in the `catch` block to check for different error types and rethrow the unknown ones. But if `readUser` function generates several kinds of errors -- then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`? +The code which calls `readUser` should handle these errors. Right now it uses multiple `if`s in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. But if the `readUser` function generates several kinds of errors, then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`? -Often the answer is "No": the outer code wants to be "one level above all that". It wants to have some kind of "data reading error". Why exactly it happened -- is often irrelevant (the error message describes it). Or, even better if there is a way to get error details, but only if we need to. +Often the answer is "No": the outer code wants to be "one level above all that", it just wants to have some kind of "data reading error" -- why exactly it happened is often irrelevant (the error message describes it). Or, even better, it could have a way to get the error details, but only if we need to. So let's make a new class `ReadError` to represent such errors. If an error occurs inside `readUser`, we'll catch it there and generate `ReadError`. We'll also keep the reference to the original error in its `cause` property. Then the outer code will only have to check for `ReadError`. diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index a9183c80f..be60f41ce 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -25,15 +25,16 @@ loadScript('/my/script.js'); The function is called "asynchronously," because the action (script loading) finishes not now, but later. -The call initiates the script loading, then the execution continues. While the script is loading, the code below may finish executing, and if the loading takes time, other scripts may run meanwhile too. +If there's a code below `loadScript(…)`, it doesn't wait until the loading finishes. ```js loadScript('/my/script.js'); -// the code below loadScript doesn't wait for the script loading to finish +// the code below loadScript +// doesn't wait for the script loading to finish // ... ``` -Now let's say we want to use the new script when it loads. It probably declares new functions, so we'd like to run them. +We'd like to use the new script as soon as it loads. It declares new functions, and we want to run them. But if we do that immediately after the `loadScript(…)` call, that wouldn't work: @@ -45,7 +46,7 @@ newFunction(); // no such function! */!* ``` -Naturally, the browser probably didn't have time to load the script. So the immediate call to the new function fails. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script. +Naturally, the browser probably didn't have time to load the script. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script. Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads: @@ -222,6 +223,33 @@ As calls become more nested, the code becomes deeper and increasingly more diffi That's sometimes called "callback hell" or "pyramid of doom." +<<<<<<< HEAD +======= + + +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac ![](callback-hell.svg) The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirals out of control. diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index 4dae2aaa6..ad5cc6581 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -2,13 +2,13 @@ Imagine that you're a top singer, and fans ask day and night for your upcoming single. -To get some relief, you promise to send it to them when it's published. You give your fans a list to which they can subscribe for updates. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, if plans to publish the song are cancelled, they will still be notified. +To get some relief, you promise to send it to them when it's published. You give your fans a list. They can fill in their email addresses, so that when the song becomes available, all subscribed parties instantly receive it. And even if something goes very wrong, say, a fire in the studio, so that you can't publish the song, they will still be notified. -Everyone is happy, because the people don't crowd you anymore, and fans, because they won't miss the single. +Everyone is happy: you, because the people don't crowd you anymore, and fans, because they won't miss the single. This is a real-life analogy for things we often have in programming: -1. A "producing code" that does something and takes time. For instance, the code loads data over a network. That's a "singer". +1. A "producing code" that does something and takes time. For instance, a code that loads the data over a network. That's a "singer". 2. A "consuming code" that wants the result of the "producing code" once it's ready. Many functions may need that result. These are the "fans". 3. A *promise* is a special JavaScript object that links the "producing code" and the "consuming code" together. In terms of our analogy: this is the "subscription list". The "producing code" takes whatever time it needs to produce the promised result, and the "promise" makes that result available to all of the subscribed code when it's ready. @@ -22,27 +22,33 @@ let promise = new Promise(function(resolve, reject) { }); ``` -The function passed to `new Promise` is called the *executor*. When the promise is created, this executor function runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer". +The function passed to `new Promise` is called the *executor*. When `new Promise` is created, it runs automatically. It contains the producing code, that should eventually produce a result. In terms of the analogy above: the executor is the "singer". -The resulting `promise` object has internal properties: +Its arguments `resolve` and `reject` are callbacks provided by JavaScript itself. Our code is only inside the executor. -- `state` — initially "pending", then changes to either "fulfilled" or "rejected", -- `result` — an arbitrary value, initially `undefined`. +When the executor obtains the result, be it soon or late - doesn't matter, it should call one of these callbacks: -When the executor finishes the job, it should call one of the functions that it gets as arguments: +- `resolve(value)` — if the job finished successfully, with result `value`. +- `reject(error)` — if an error occurred, `error` is the error object. -- `resolve(value)` — to indicate that the job finished successfully: - - sets `state` to `"fulfilled"`, - - sets `result` to `value`. -- `reject(error)` — to indicate that an error occurred: - - sets `state` to `"rejected"`, - - sets `result` to `error`. +So to summarize: the executor runs automatically, it should do a job and then call either `resolve` or `reject`. +<<<<<<< HEAD ![](promise-resolve-reject.svg) +======= +The `promise` object returned by `new Promise` constructor has internal properties: +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac -Later we'll see how these changes become known to "fans". +- `state` — initially `"pending"`, then changes to either `"fulfilled"` when `resolve` is called or `"rejected"` when `reject` is called. +- `result` — initially `undefined`, then changes to `value` when `resolve(value)` called or `error` when `reject(error)` is called. -Here's an example of a Promise constructor and a simple executor function with its "producing code" (the `setTimeout`): +So the executor eventually moves `promise` to one of these states: + +![](promise-resolve-reject.svg) + +Later we'll see how "fans" can subscribe to these changes. + +Here's an example of a promise constructor and a simple executor function with "producing code" that takes time (via `setTimeout`): ```js run let promise = new Promise(function(resolve, reject) { @@ -55,12 +61,16 @@ let promise = new Promise(function(resolve, reject) { We can see two things by running the code above: -1. The executor is called automatically and immediately (by the `new Promise`). +1. The executor is called automatically and immediately (by `new Promise`). 2. The executor receives two arguments: `resolve` and `reject` — these functions are pre-defined by the JavaScript engine. So we don't need to create them. We only should call one of them when ready. -After one second of "processing" the executor calls `resolve("done")` to produce the result: + After one second of "processing" the executor calls `resolve("done")` to produce the result. This changes the state of the `promise` object: +<<<<<<< HEAD ![](promise-resolve-1.svg) +======= + ![](promise-resolve-1.svg) +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac That was an example of a successful job completion, a "fulfilled promise". @@ -73,20 +83,28 @@ let promise = new Promise(function(resolve, reject) { }); ``` +<<<<<<< HEAD +![](promise-reject-1.svg) +======= +The call to `reject(...)` moves the promise object to `"rejected"` state: +>>>>>>> a0bfa924a17cad8e7fee213904b27dbf57c2dbac + ![](promise-reject-1.svg) -To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding Promise object. +To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding promise object. -The Promise that is either resolved or rejected is called "settled", as opposed to a initially "pending" Promise. +A promise that is either resolved or rejected is called "settled", as opposed to a initially "pending" promise. ````smart header="There can be only a single result or an error" -The executor should call only one `resolve` or one `reject`. The promise's state change is final. +The executor should call only one `resolve` or one `reject`. Any state change is final. All further calls of `resolve` and `reject` are ignored: ```js let promise = new Promise(function(resolve, reject) { +*!* resolve("done"); +*/!* reject(new Error("…")); // ignored setTimeout(() => resolve("…")); // ignored @@ -99,7 +117,7 @@ Also, `resolve`/`reject` expect only one argument (or none) and will ignore addi ```` ```smart header="Reject with `Error` objects" -In case something goes wrong, we can call `reject` with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent. +In case something goes wrong, the executor should call `reject`. That can be done with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent. ``` ````smart header="Immediately calling `resolve`/`reject`" @@ -112,13 +130,13 @@ let promise = new Promise(function(resolve, reject) { }); ``` -For instance, this might happen when we start to do a job but then see that everything has already been completed and cached. +For instance, this might happen when we start to do a job but then see that everything has already been completed and cached. That's fine. We immediately have a resolved promise. ```` ```smart header="The `state` and `result` are internal" -The properties `state` and `result` of the Promise object are internal. We can't directly access them from our "consuming code". We can use the methods `.then`/`.catch`/`.finally` for that. They are described below. +The properties `state` and `result` of the Promise object are internal. We can't directly access them. We can use the methods `.then`/`.catch`/`.finally` for that. They are described below. ``` ## Consumers: then, catch, finally @@ -138,15 +156,9 @@ promise.then( ); ``` -The first argument of `.then` is a function that: - -1. runs when the promise is resolved, and -2. receives the result. +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: - -1. runs when the promise is rejected, and -2. receives the error. +The second argument of `.then` is a function that runs when the promise is rejected, and receives the error. For instance, here's a reaction to a successfully resolved promise: @@ -216,7 +228,7 @@ The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a short Just like there's a `finally` clause in a regular `try {...} catch {...}`, there's `finally` in promises. -The call `.finally(f)` is similar to `.then(f, f)` in the sense that it always runs when the promise is settled: be it resolve or reject. +The call `.finally(f)` is similar to `.then(f, f)` in the sense that `f` always runs when the promise is settled: be it resolve or reject. `finally` is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed anymore, no matter what the outcome is. @@ -264,16 +276,14 @@ It's not exactly an alias of `then(f,f)` though. There are several important dif 3. Last, but not least, `.finally(f)` is a more convenient syntax than `.then(f, f)`: no need to duplicate the function `f`. ````smart header="On settled promises handlers runs immediately" -If a promise is pending, `.then/catch/finally` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately: +If a promise is pending, `.then/catch/finally` handlers wait for it. Otherwise, if a promise has already settled, they execute immediately: ```js run -// an immediately resolved promise +// the promise becomes resolved immediately upon creation let promise = new Promise(resolve => resolve("done!")); promise.then(alert); // done! (shows up right now) ``` - -The good thing is: a `.then` handler is guaranteed to run whether the promise takes time or settles it immediately. ```` Next, let's see more practical examples of how promises can help us to write asynchronous code. @@ -324,7 +334,7 @@ promise.then( error => alert(`Error: ${error.message}`) ); -promise.then(script => alert('One more handler to do something else!')); +promise.then(script => alert('Another handler...')); ``` We can immediately see a few benefits over the callback-based pattern: @@ -335,4 +345,4 @@ We can immediately see a few benefits over the callback-based pattern: | Promises allow us to do things in the natural order. First, we run `loadScript(script)`, and `.then` we write what to do with the result. | We must have a `callback` function at our disposal when calling `loadScript(script, callback)`. In other words, we must know what to do with the result *before* `loadScript` is called. | | We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next chapter: [](info:promise-chaining). | There can be only one callback. | -So Promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters. +So promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters. diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md index 9482a1d27..eace803da 100644 --- a/1-js/11-async/03-promise-chaining/article.md +++ b/1-js/11-async/03-promise-chaining/article.md @@ -48,24 +48,6 @@ The whole thing works, because a call to `promise.then` returns a promise, so th When a handler returns a value, it becomes the result of that promise, so the next `.then` is called with it. -To make these words more clear, here's the start of the chain: - -```js run -new Promise(function(resolve, reject) { - - setTimeout(() => resolve(1), 1000); - -}).then(function(result) { - - alert(result); - return result * 2; // <-- (1) - -}) // <-- (2) -// .then… -``` - -The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value. - **A classic newbie error: technically we can also add many `.then` to a single promise. This is not chaining.** For example: @@ -102,9 +84,9 @@ In practice we rarely need multiple handlers for one promise. Chaining is used m ## Returning promises -Normally, a value returned by a `.then` handler is immediately passed to the next handler. But there's an exception. +A handler, used in `.then(handler)` may create and return a promise. -If the returned value is a promise, then the further execution is suspended until it settles. After that, the result of that promise is given to the next `.then` handler. +In that case further handlers wait till it settles, and then get its result. For instance: @@ -138,9 +120,9 @@ new Promise(function(resolve, reject) { }); ``` -Here the first `.then` shows `1` and returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result*2`) is passed on to handler of the second `.then` in the line `(**)`. It shows `2` and does the same thing. +Here the first `.then` shows `1` and returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result * 2`) is passed on to handler of the second `.then`. That handler is in the line `(**)`, it shows `2` and does the same thing. -So the output is again 1 -> 2 -> 4, but now with 1 second delay between `alert` calls. +So the output is the same as in the previous example: 1 -> 2 -> 4, but now with 1 second delay between `alert` calls. Returning promises allows us to build chains of asynchronous actions. @@ -184,7 +166,7 @@ Here each `loadScript` call returns a promise, and the next `.then` runs when it We can add more asynchronous actions to the chain. Please note that code is still "flat", it grows down, not to the right. There are no signs of "pyramid of doom". -Please note that technically we can add `.then` directly to each `loadScript`, like this: +Technically, we could add `.then` directly to each `loadScript`, like this: ```js run loadScript("/article/promise-chaining/one.js").then(script1 => { @@ -207,7 +189,7 @@ Sometimes it's ok to write `.then` directly, because the nested function has acc ````smart header="Thenables" -To be precise, `.then` may return a so-called "thenable" object - an arbitrary object that has method `.then`, and it will be treated the same way as a promise. +To be precise, a handler may return not exactly a promise, but a so-called "thenable" object - an arbitrary object that has method `.then`, and it will be treated the same way as a promise. The idea is that 3rd-party libraries may implement "promise-compatible" objects of their own. They can have extended set of methods, but also be compatible with native promises, because they implement `.then`. @@ -227,14 +209,16 @@ class Thenable { new Promise(resolve => resolve(1)) .then(result => { +*!* return new Thenable(result); // (*) +*/!* }) .then(alert); // shows 2 after 1000ms ``` JavaScript checks the object returned by `.then` handler in the line `(*)`: if it has a callable method named `then`, then it calls that method providing native functions `resolve`, `reject` as arguments (similar to executor) and waits until one of them is called. In the example above `resolve(2)` is called after 1 second `(**)`. Then the result is passed further down the chain. -This feature allows to integrate custom objects with promise chains without having to inherit from `Promise`. +This feature allows us to integrate custom objects with promise chains without having to inherit from `Promise`. ```` @@ -242,7 +226,7 @@ This feature allows to integrate custom objects with promise chains without havi In frontend programming promises are often used for network requests. So let's see an extended example of that. -We'll use the [fetch](info:fetch) method to load the information about the user from the remote server. It has a lot of optional parameters covered in separate chapters, but the basic syntax is quite simple: +We'll use the [fetch](info:fetch) method to load the information about the user from the remote server. It has a lot of optional parameters covered in [separate chapters](info:fetch), but the basic syntax is quite simple: ```js let promise = fetch(url); @@ -259,7 +243,7 @@ fetch('/article/promise-chaining/user.json') // .then below runs when the remote server responds .then(function(response) { // response.text() returns a new promise that resolves with the full response text - // when we finish downloading it + // when it loads return response.text(); }) .then(function(text) { @@ -276,7 +260,7 @@ We'll also use arrow functions for brevity: // same as above, but response.json() parses the remote content as JSON fetch('/article/promise-chaining/user.json') .then(response => response.json()) - .then(user => alert(user.name)); // iliakan + .then(user => alert(user.name)); // iliakan, got user name ``` Now let's do something with the loaded user. @@ -317,7 +301,7 @@ fetch('/article/promise-chaining/user.json') .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(response => response.json()) *!* - .then(githubUser => new Promise(function(resolve, reject) { + .then(githubUser => new Promise(function(resolve, reject) { // (*) */!* let img = document.createElement('img'); img.src = githubUser.avatar_url; @@ -327,7 +311,7 @@ fetch('/article/promise-chaining/user.json') setTimeout(() => { img.remove(); *!* - resolve(githubUser); + resolve(githubUser); // (**) */!* }, 3000); })) @@ -335,9 +319,11 @@ fetch('/article/promise-chaining/user.json') .then(githubUser => alert(`Finished showing ${githubUser.name}`)); ``` -Now right after `setTimeout` runs `img.remove()`, it calls `resolve(githubUser)`, thus passing the control to the next `.then` in the chain and passing forward the user data. +That is, `.then` handler in the line `(*)` now returns `new Promise`, that becomes settled only after the call of `resolve(githubUser)` in `setTimeout` `(**)`. + +The next `.then` in chain will wait for that. -As a rule, an asynchronous action should always return a promise. +As a good rule, an asynchronous action should always return a promise. That makes it possible to plan actions after it. Even if we don't plan to extend the chain now, we may need it later. 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 3424d099e..624c7e812 100644 --- a/1-js/11-async/04-promise-error-handling/article.md +++ b/1-js/11-async/04-promise-error-handling/article.md @@ -1,11 +1,9 @@ # Error handling with promises -Asynchronous actions may sometimes fail: in case of an error the corresponding promise becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections). +Promise chains are great at error handling. When a promise rejects, the control jumps to the closest rejection handler. That's very convenient in practice. -Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice. - -For instance, in the code below the URL is wrong (no such site) and `.catch` handles the error: +For instance, in the code below the URL to `fetch` is wrong (no such site) and `.catch` handles the error: ```js run *!* @@ -15,17 +13,9 @@ fetch('https://no-such-server.blabla') // rejects .catch(err => alert(err)) // TypeError: failed to fetch (the text may vary) ``` -Or, maybe, everything is all right with the site, but the response is not valid JSON: +As you can see, the `.catch` doesn't have to be immediate. It may appear after one or maybe several `.then`. -```js run -fetch('/') // fetch works fine now, the server responds with the HTML page -*!* - .then(response => response.json()) // rejects: the page is HTML, not a valid json -*/!* - .catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0 -``` - -The easiest way to catch all errors is to append `.catch` to the end of chain: +Or, maybe, everything is all right with the site, but the response is not valid JSON. The easiest way to catch all errors is to append `.catch` to the end of chain: ```js run fetch('/article/promise-chaining/user.json') @@ -48,7 +38,7 @@ fetch('/article/promise-chaining/user.json') */!* ``` -Normally, `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it. +Normally, such `.catch` doesn't trigger at all. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it. ## Implicit try..catch @@ -74,9 +64,9 @@ new Promise((resolve, reject) => { }).catch(alert); // Error: Whoops! ``` -The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection. +The "invisible `try..catch`" around the executor automatically catches the error and turns it into rejected promise. -This happens not only in the executor, but in its handlers as well. If we `throw` inside a `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler. +This happens not only in the executor function, but in its handlers as well. If we `throw` inside a `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler. Here's an example: @@ -106,7 +96,7 @@ The final `.catch` not only catches explicit rejections, but also occasional err ## Rethrowing -As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` handlers as we want, and then use a single `.catch` at the end to handle errors in all of them. +As we already noticed, `.catch` at the end of the chain is similar to `try..catch`. We may have as many `.then` handlers as we want, and then use a single `.catch` at the end to handle errors in all of them. In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises. @@ -150,7 +140,7 @@ new Promise((resolve, reject) => { } }).then(function() { - /* never runs here */ + /* doesn't run here */ }).catch(error => { // (**) alert(`The unknown error has occurred: ${error}`); @@ -159,101 +149,11 @@ new Promise((resolve, reject) => { }); ``` -Then the execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain. - -In the section below we'll see a practical example of rethrowing. - -## Fetch error handling example - -Let's improve error handling for the user-loading example. - -The promise returned by [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) rejects when it's impossible to make a request. For instance, a remote server is not available, or the URL is malformed. But if the remote server responds with error 404, or even error 500, then it's considered a valid response. - -What if the server returns a non-JSON page with error 500 in the line `(*)`? What if there's no such user, and GitHub returns a page with error 404 at `(**)`? - -```js run -fetch('no-such-user.json') // (*) - .then(response => response.json()) - .then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**) - .then(response => response.json()) - .catch(alert); // SyntaxError: Unexpected token < in JSON at position 0 - // ... -``` - - -As of now, the code tries to load the response as JSON no matter what and dies with a syntax error. You can see that by running the example above, as the file `no-such-user.json` doesn't exist. - -That's not good, because the error just falls through the chain, without details: what failed and where. - -So let's add one more step: we should check the `response.status` property that has HTTP status, and if it's not 200, then throw an error. - -```js run -class HttpError extends Error { // (1) - constructor(response) { - super(`${response.status} for ${response.url}`); - this.name = 'HttpError'; - this.response = response; - } -} - -function loadJson(url) { // (2) - return fetch(url) - .then(response => { - if (response.status == 200) { - return response.json(); - } else { - throw new HttpError(response); - } - }) -} - -loadJson('no-such-user.json') // (3) - .catch(alert); // HttpError: 404 for .../no-such-user.json -``` - -1. We make a custom class for HTTP Errors to distinguish them from other types of errors. Besides, the new class has a constructor that accepts `response` object and saves it in the error. So error-handling code will be able to access the response. -2. Then we put together the requesting and error-handling code into a function that fetches the `url` *and* treats any non-200 status as an error. That's convenient, because we often need such logic. -3. Now `alert` shows a more helpful descriptive message. - -The great thing about having our own class for errors is that we can easily check for it in error-handling code using `instanceof`. - -For instance, we can make a request, and then if we get 404 -- ask the user to modify the information. - -The code below loads a user with the given name from GitHub. If there's no such user, then it asks for the correct name: - -```js run -function demoGithubUser() { - let name = prompt("Enter a name?", "iliakan"); - - return loadJson(`https://api.github.com/users/${name}`) - .then(user => { - alert(`Full name: ${user.name}.`); - return user; - }) - .catch(err => { -*!* - if (err instanceof HttpError && err.response.status == 404) { -*/!* - alert("No such user, please reenter."); - return demoGithubUser(); - } else { - throw err; // (*) - } - }); -} - -demoGithubUser(); -``` - -Please note: `.catch` here catches all errors, but it "knows how to handle" only `HttpError 404`. In that particular case it means that there's no such user, and `.catch` just retries in that case. - -For other errors, it has no idea what could go wrong. Maybe a programming error or something. So it just rethrows it in the line `(*)`. +The execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain. ## Unhandled rejections -What happens when an error is not handled? For instance, after the rethrow `(*)` in the example above. - -Or we could just forget to append an error handler to the end of the chain, like here: +What happens when an error is not handled? For instance, we forgot to append `.catch` to the end of the chain, like here: ```js untrusted run refresh new Promise(function() { @@ -264,13 +164,13 @@ new Promise(function() { }); // without .catch at the end! ``` -In case of an error, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. So the error gets "stuck". There's no code to handle it. +In case of an error, the promise becomes rejected, and the execution should jump to the closest rejection handler. But there is none. So the error gets "stuck". There's no code to handle it. -In practice, just like with a regular unhandled errors, it means that something has terribly gone wrong. +In practice, just like with regular unhandled errors in code, it means that something has terribly gone wrong. -What happens when a regular error occurs and is not caught by `try..catch`? The script dies. Similar thing happens with unhandled promise rejections. +What happens when a regular error occurs and is not caught by `try..catch`? The script dies with a message in console. Similar thing happens with unhandled promise rejections. -The JavaScript engine tracks such rejections and generates a global error in that case. You can see it in the console if you run the example above. +JavaScript engine tracks such rejections and generates a global error in that case. You can see it in the console if you run the example above. In the browser we can catch such errors using the event `unhandledrejection`: @@ -294,52 +194,11 @@ If an error occurs, and there's no `.catch`, the `unhandledrejection` handler tr Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report the incident to the server. -In non-browser environments like Node.js there are other similar ways to track unhandled errors. - +In non-browser environments like Node.js there are other ways to track unhandled errors. ## Summary -- `.catch` handles promise rejections of all kinds: be it a `reject()` call, or an error thrown in a 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. +- `.catch` handles errors in promises of all kinds: be it a `reject()` call, or an error thrown in a 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 the them, so that our app never "just dies". - -And finally, if we have load-indication, then `.finally` is a great handler to stop it when the fetch is complete: - -```js run -function demoGithubUser() { - let name = prompt("Enter a name?", "iliakan"); - -*!* - document.body.style.opacity = 0.3; // (1) start the indication -*/!* - - return loadJson(`https://api.github.com/users/${name}`) -*!* - .finally(() => { // (2) stop the indication - document.body.style.opacity = ''; - return new Promise(resolve => setTimeout(resolve)); // (*) - }) -*/!* - .then(user => { - alert(`Full name: ${user.name}.`); - return user; - }) - .catch(err => { - if (err instanceof HttpError && err.response.status == 404) { - alert("No such user, please reenter."); - return demoGithubUser(); - } else { - throw err; - } - }); -} - -demoGithubUser(); -``` - -Here on the line `(1)` we indicate loading by dimming the document. The method doesn't matter, could use any type of indication instead. - -When the promise is settled, be it a successful fetch or an error, `finally` triggers at the line `(2)` and stops the indication. - -There's a little browser trick `(*)` with returning a zero-timeout promise from `finally`. That's because some browsers (like Chrome) need "a bit time" outside promise handlers to paint document changes. So it ensures that the indication is visually stopped before going further on the chain. diff --git a/1-js/11-async/05-promise-api/article.md b/1-js/11-async/05-promise-api/article.md index 2ab52acd7..d439c8238 100644 --- a/1-js/11-async/05-promise-api/article.md +++ b/1-js/11-async/05-promise-api/article.md @@ -2,65 +2,6 @@ There are 5 static methods in the `Promise` class. We'll quickly cover their use cases here. -## Promise.resolve - -The syntax: - -```js -let promise = Promise.resolve(value); -``` - -Returns a resolved promise with the given `value`. - -Same as: - -```js -let promise = new Promise(resolve => resolve(value)); -``` - -The method is used when we already have a value, but would like to have it "wrapped" into a promise. - -For instance, the `loadCached` function below fetches the `url` and remembers the result, so that future calls on the same URL return it immediately: - -```js -function loadCached(url) { - let cache = loadCached.cache || (loadCached.cache = new Map()); - - if (cache.has(url)) { -*!* - return Promise.resolve(cache.get(url)); // (*) -*/!* - } - - return fetch(url) - .then(response => response.text()) - .then(text => { - cache.set(url,text); - return text; - }); -} -``` - -We can use `loadCached(url).then(…)`, because the function is guaranteed to return a promise. That's the purpose `Promise.resolve` serves in the line `(*)`: it makes sure the interface is unified. We can always use `.then` after `loadCached`. - -## Promise.reject - -The syntax: - -```js -let promise = Promise.reject(error); -``` - -Create a rejected promise with the `error`. - -Same as: - -```js -let promise = new Promise((resolve, reject) => reject(error)); -``` - -We cover it here for completeness, rarely used in real code. - ## Promise.all Let's say we want to run many promises to execute in parallel, and wait till all of them are ready. @@ -75,9 +16,9 @@ The syntax is: let promise = Promise.all([...promises...]); ``` -It takes an array of promises (technically can be any iterable, but usually an array) and returns a new promise. +`Promise.all` takes an array of promises (technically can be any iterable, but usually an array) and returns a new promise. -The new promise resolves when all listed promises are settled and has an array of their results. +The new promise resolves when all listed promises are settled and the array of their results becomes its result. For instance, the `Promise.all` below settles after 3 seconds, and then its result is an array `[1, 2, 3]`: @@ -89,7 +30,7 @@ Promise.all([ ]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member ``` -Please note that the relative order is the same. Even though the first promise takes the longest time to resolve, it is still first in the array of results. +Please note that the order of resulting array members is the same as source promises. Even though the first promise takes the longest time to resolve, it's still first in the array of results. A common trick is to map an array of job data into an array of promises, and then wrap that into `Promise.all`. @@ -121,7 +62,7 @@ let requests = names.map(name => fetch(`https://api.github.com/users/${name}`)); Promise.all(requests) .then(responses => { - // all responses are ready, we can show HTTP status codes + // all responses are resolved successfully for(let response of responses) { alert(`${response.url}: ${response.status}`); // shows 200 for every url } @@ -153,13 +94,13 @@ Here the second promise rejects in two seconds. That leads to immediate rejectio ```warn header="In case of an error, other promises are ignored" If one promise rejects, `Promise.all` immediately rejects, completely forgetting about the other ones in the list. Their results are ignored. -For example, if there are multiple `fetch` calls, like in the example above, and one fails, other ones will still continue to execute, but `Promise.all` don't watch them any more. They will probably settle, but the result will be ignored. +For example, if there are multiple `fetch` calls, like in the example above, and one fails, other ones will still continue to execute, but `Promise.all` won't watch them anymore. They will probably settle, but the result will be ignored. `Promise.all` does nothing to cancel them, as there's no concept of "cancellation" in promises. In [another chapter](info:fetch-abort) we'll cover `AbortController` that can help with that, but it's not a part of the Promise API. ``` ````smart header="`Promise.all(iterable)` allows non-promise \"regular\" values in `iterable`" -Normally, `Promise.all(...)` accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it's wrapped in `Promise.resolve`. +Normally, `Promise.all(...)` accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it's passed to the resulting array "as is". For instance, here the results are `[1, 2, 3]`: @@ -168,8 +109,8 @@ Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000) }), - 2, // treated as Promise.resolve(2) - 3 // treated as Promise.resolve(3) + 2, + 3 ]).then(alert); // 1, 2, 3 ``` @@ -180,22 +121,22 @@ So we are able to pass ready values to `Promise.all` where convenient. [recent browser="new"] -`Promise.all` rejects as a whole if any promise rejects. That's good in cases, when we need *all* results to go on: +`Promise.all` rejects as a whole if any promise rejects. That's good for "all or nothing" cases, when we need *all* results to go on: ```js Promise.all([ fetch('/template.html'), fetch('/style.css'), fetch('/data.json') -]).then(render); // render method needs them all +]).then(render); // render method needs results of all fetches ``` -`Promise.allSettled` waits for all promises to settle: even if one rejects, it waits for the others. The resulting array has: +`Promise.allSettled` waits for all promises to settle. The resulting array has: - `{status:"fulfilled", value:result}` for successful responses, - `{status:"rejected", reason:error}` for errors. -For example, we'd like to fetch the information about multiple users. Even if one request fails, we're interested in the others. +For example, we'd like to fetch the information about multiple users. Even if one request fails, we're still interested in the others. Let's use `Promise.allSettled`: @@ -228,7 +169,7 @@ The `results` in the line `(*)` above will be: ] ``` -So, for each promise we get its status and `value/reason`. +So, for each promise we get its status and `value/error`. ### Polyfill @@ -237,18 +178,18 @@ If the browser doesn't support `Promise.allSettled`, it's easy to polyfill: ```js if(!Promise.allSettled) { Promise.allSettled = function(promises) { - return Promise.all(promises.map(p => Promise.resolve(p).then(v => ({ + return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({ state: 'fulfilled', - value: v, - }), r => ({ + value + }), reason => ({ state: 'rejected', - reason: r, + reason })))); }; } ``` -In this code, `promises.map` takes input values, turns into promises (just in case a non-promise was passed) with `p => Promise.resolve(p)`, and then adds `.then` handler to it. +In this code, `promises.map` takes input values, turns into promises (just in case a non-promise was passed) with `p => Promise.resolve(p)`, and then adds `.then` handler to every one. That handler turns a successful result `v` into `{state:'fulfilled', value:v}`, and an error `r` into `{state:'rejected', reason:r}`. That's exactly the format of `Promise.allSettled`. @@ -256,7 +197,7 @@ Then we can use `Promise.allSettled` to get the results or *all* given promises, ## Promise.race -Similar to `Promise.all`, it takes an iterable of promises, but instead of waiting for all of them to finish, it waits for the first result (or error), and goes on with it. +Similar to `Promise.all`, but waits only for the first settled promise, and gets its result (or error). The syntax is: @@ -274,18 +215,70 @@ Promise.race([ ]).then(alert); // 1 ``` -So, the first result/error becomes the result of the whole `Promise.race`. After the first settled promise "wins the race", all further results/errors are ignored. +The first promise here was fastest, so it became the result. After the first settled promise "wins the race", all further results/errors are ignored. + + +## Promise.resolve/reject + +Methods `Promise.resolve` and `Promise.reject` are rarely needed in modern code, because `async/await` syntax (we'll cover it in [a bit later](info:async-await)) makes them somewhat obsolete. + +We cover them here for completeness, and for those who can't use `async/await` for some reason. + +- `Promise.resolve(value)` creates a resolved promise with the result `value`. + +Same as: + +```js +let promise = new Promise(resolve => resolve(value)); +``` + +The method is used for compatibility, when a function is expected to return a promise. + +For example, `loadCached` function below fetches URL and remembers (caches) its content. For future calls with the same URL it immediately gets the previous content from cache, but uses `Promise.resolve` to make a promise of it, so that the returned value is always a promise: + +```js +let cache = new Map(); + +function loadCached(url) { + if (cache.has(url)) { +*!* + return Promise.resolve(cache.get(url)); // (*) +*/!* + } + + return fetch(url) + .then(response => response.text()) + .then(text => { + cache.set(url,text); + return text; + }); +} +``` + +We can write `loadCached(url).then(…)`, because the function is guaranteed to return a promise. We can always use `.then` after `loadCached`. That's the purpose of `Promise.resolve` in the line `(*)`. + +### Promise.reject + +- `Promise.reject(error)` creates a rejected promise with `error`. + +Same as: + +```js +let promise = new Promise((resolve, reject) => reject(error)); +``` + +In practice, this method is almost never used. ## Summary There are 5 static methods of `Promise` class: -1. `Promise.resolve(value)` -- makes a resolved promise with the given value. -2. `Promise.reject(error)` -- makes a rejected promise with the given error. -3. `Promise.all(promises)` -- waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, then it becomes the error of `Promise.all`, and all other results are ignored. -4. `Promise.allSettled(promises)` (a new method) -- waits for all promises to resolve or reject and returns an array of their results as object with: - - `state`: `'fulfilled'` or `'rejected'` +1. `Promise.all(promises)` -- waits for all promises to resolve and returns an array of their results. If any of the given promises rejects, then it becomes the error of `Promise.all`, and all other results are ignored. +2. `Promise.allSettled(promises)` (recently added method) -- waits for all promises to settle and returns their results as array of objects with: + - `state`: `"fulfilled"` or `"rejected"` - `value` (if fulfilled) or `reason` (if rejected). -5. `Promise.race(promises)` -- waits for the first promise to settle, and its result/error becomes the outcome. +3. `Promise.race(promises)` -- waits for the first promise to settle, and its result/error becomes the outcome. +4. `Promise.resolve(value)` -- makes a resolved promise with the given value. +5. `Promise.reject(error)` -- makes a rejected promise with the given error. Of these five, `Promise.all` is probably the most common in practice. diff --git a/1-js/11-async/06-promisify/article.md b/1-js/11-async/06-promisify/article.md index 7c84912b5..6d91c0490 100644 --- a/1-js/11-async/06-promisify/article.md +++ b/1-js/11-async/06-promisify/article.md @@ -2,8 +2,6 @@ Promisification -- is a long word for a simple transform. It's conversion of a function that accepts a callback into a function returning a promise. -To be more precise, we create a wrapper-function that does the same, internally calling the original one, but returns a promise. - Such transforms are often needed in real-life, as many functions and libraries are callback-based. But promises are more convenient. So it makes sense to promisify those. For instance, we have `loadScript(src, callback)` from the chapter . @@ -23,7 +21,7 @@ function loadScript(src, callback) { // loadScript('path/script.js', (err, script) => {...}) ``` -Let's promisify it. The new `loadScriptPromise(src)` function will do the same, but accept only `src` (no callback) and return a promise. +Let's promisify it. The new `loadScriptPromise(src)` function will do the same, but accept only `src` (no `callback`) and return a promise. ```js let loadScriptPromise = function(src) { @@ -39,13 +37,13 @@ let loadScriptPromise = function(src) { // loadScriptPromise('path/script.js').then(...) ``` -Now `loadScriptPromise` fits well in our promise-based code. +Now `loadScriptPromise` fits well in promise-based code. As we can see, it delegates all the work to the original `loadScript`, providing its own callback that translates to promise `resolve/reject`. -As we may need to promisify many functions, it makes sense to use a helper. +In practice we'll probably need to promisify many functions, it makes sense to use a helper. -That's actually very simple -- `promisify(f)` below takes a to-promisify function `f` and returns a wrapper function. +We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function. That wrapper does the same as in the code above: returns a promise and passes the call to the original `f`, tracking the result in a custom callback: @@ -61,7 +59,7 @@ function promisify(f) { } } - args.push(callback); // append our custom callback to the end of arguments + args.push(callback); // append our custom callback to the end of f arguments f.call(this, ...args); // call the original function }); @@ -75,9 +73,9 @@ loadScriptPromise(...).then(...); Here we assume that the original function expects a callback with two arguments `(err, result)`. That's what we encounter most often. Then our custom callback is in exactly the right format, and `promisify` works great for such a case. -But what if the original `f` expects a callback with more arguments `callback(err, res1, res2)`? +But what if the original `f` expects a callback with more arguments `callback(err, res1, res2, ...)`? -Here's a modification of `promisify` that returns an array of multiple callback results: +Here's a more advanced version of `promisify`: if called as `promisify(f, true)`, the promise result will be an array of callback results `[res1, res2, ...]`: ```js // promisify(f, true) to get array of results @@ -105,7 +103,7 @@ f = promisify(f, true); f(...).then(arrayOfResults => ..., err => ...) ``` -In some cases, `err` may be absent at all: `callback(result)`, or there's something exotic in the callback format, then we can promisify such functions without using the helper, manually. +For more exotic callback formats, like those without `err` at all: `callback(result)`, we can promisify such functions without using the helper, manually. 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. diff --git a/1-js/11-async/07-microtask-queue/article.md b/1-js/11-async/07-microtask-queue/article.md index 9e5ac8c88..f444e21ce 100644 --- a/1-js/11-async/07-microtask-queue/article.md +++ b/1-js/11-async/07-microtask-queue/article.md @@ -10,12 +10,12 @@ Here's the demo: ```js run let promise = Promise.resolve(); -promise.then(() => alert("promise done")); +promise.then(() => alert("promise done!")); alert("code finished"); // this alert shows first ``` -If you run it, you see `code finished` first, and then `promise done`. +If you run it, you see `code finished` first, and then `promise done!`. That's strange, because the promise is definitely done from the beginning. @@ -54,7 +54,7 @@ Now the order is as intended. ## Unhandled rejection -Remember "unhandled rejection" event from the chapter ? +Remember `unhandledrejection` event from the chapter ? Now we can see exactly how JavaScript finds out that there was an unhandled rejection diff --git a/1-js/11-async/08-async-await/article.md b/1-js/11-async/08-async-await/article.md index 5bf244496..f498ad08d 100644 --- a/1-js/11-async/08-async-await/article.md +++ b/1-js/11-async/08-async-await/article.md @@ -12,9 +12,9 @@ async function f() { } ``` -The word "async" before a function means one simple thing: a function always returns a promise. Even If a function actually returns a non-promise value, prepending the function definition with the "async" keyword directs JavaScript to automatically wrap that value in a resolved promise. +The word "async" before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically. -For instance, the code above returns a resolved promise with the result of `1`, let's test it: +For instance, this function returns a resolved promise with the result of `1`, let's test it: ```js run async function f() { @@ -24,7 +24,7 @@ async function f() { f().then(alert); // 1 ``` -...We could explicitly return a promise, that would be the same as: +...We could explicitly return a promise, that would be the same: ```js run async function f() { @@ -171,7 +171,7 @@ f(); If `await` gets a non-promise object with `.then`, it calls that method providing native functions `resolve`, `reject` as arguments. Then `await` waits until one of them is called (in the example above it happens in the line `(*)`) and then proceeds with the result. ```` -````smart header="Async methods" +````smart header="Async class methods" To declare an async class method, just prepend it with `async`: ```js run @@ -214,7 +214,7 @@ async function f() { } ``` -In real situations, the promise may take some time before it rejects. So `await` will wait, and then throw an error. +In real situations, the promise may take some time before it rejects. In that case there will be a delay before `await` throws an error. We can catch that error using `try..catch`, the same way as a regular `throw`: @@ -300,7 +300,7 @@ The `async` keyword before a function has two effects: The `await` keyword before a promise makes JavaScript wait until that promise settles, and then: 1. If it's an error, the exception is generated, same as if `throw error` were called at that very place. -2. Otherwise, it returns the result, so we can assign it to a value. +2. Otherwise, it returns the result. Together they provide a great framework to write asynchronous code that is easy both to read and write. 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 18ba84393..af2ad0eed 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 @@ -35,4 +35,4 @@ alert(generator()); // 282475249 alert(generator()); // 1622650073 ``` -That also works. But then we loose ability to iterate with `for..of` and to use generator composition, that may be useful elsewhere. +That also works. But then we lose ability to iterate with `for..of` and to use generator composition, that may be useful elsewhere. diff --git a/1-js/12-generators-iterators/1-generators/article.md b/1-js/12-generators-iterators/1-generators/article.md index f9209e7e6..5b3be20cc 100644 --- a/1-js/12-generators-iterators/1-generators/article.md +++ b/1-js/12-generators-iterators/1-generators/article.md @@ -2,7 +2,7 @@ Regular functions return only one, single value (or nothing). -Generators can return ("yield") multiple values, possibly an infinite number of values, one after another, on-demand. They work great with [iterables](info:iterable), allowing to create data streams with ease. +Generators can return ("yield") multiple values, one after another, on-demand. They work great with [iterables](info:iterable), allowing to create data streams with ease. ## Generator functions @@ -18,22 +18,33 @@ function* generateSequence() { } ``` -The term "generator function" is a bit misleading, because when called it does not execute the code. Instead, it returns a special object, called "generator object". +Generator functions behave differently from regular ones. When such function is called, it doesn't run its code. Instead it returns a special object, called "generator object", to manage the execution. -So it's kind of a "generator constructor". +Here, take a look: + +```js run +function* generateSequence() { + yield 1; + yield 2; + return 3; +} -```js // "generator function" creates "generator object" let generator = generateSequence(); +*!* +alert(generator); // [object Generator] +*/!* ``` -The `generator` object is something like an "frozen function call": +The function code execution hasn't started yet: ![](generateSequence-1.svg) -Upon creation, the code execution is paused at the very beginning. +The main method of a generator is `next()`. When called, it runs the execution till the nearest `yield ` statement (`value` can be omitted, then it's `undefined`). Then the function execution pauses, and the yielded `value` is returned to the outer code. -The main method of a generator is `next()`. When called, it resumes execution till the nearest `yield ` statement. Then the execution pauses, and the value is returned to the outer code. +The result of `next()` is always an object with two properties: +- `value`: the yielded value. +- `done`: `true` if the function code has finished, otherwise `false`. For instance, here we create the generator and get its first yielded value: @@ -53,11 +64,7 @@ let one = generator.next(); alert(JSON.stringify(one)); // {value: 1, done: false} ``` -The result of `next()` is always an object: -- `value`: the yielded value. -- `done`: `false` if the code is not finished yet, otherwise `true`. - -As of now, we got the first value only: +As of now, we got the first value only, and the function execution is on the second line: ![](generateSequence-2.svg) @@ -83,14 +90,10 @@ alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*} Now the generator is done. We should see it from `done:true` and process `value:3` as the final result. -New calls `generator.next()` don't make sense any more. If we make them, they return the same object: `{done: true}`. - -There's no way to "roll back" a generator. But we can create another one by calling `generateSequence()`. - -So far, the most important thing to understand is that generator functions, unlike regular function, do not run the code. They serve as "generator factories". Running `function*` returns a generator, and then we ask it for values. +New calls `generator.next()` don't make sense any more. If we do them, they return the same object: `{done: true}`. ```smart header="`function* f(…)` or `function *f(…)`?" -That's a minor religious question, both syntaxes are correct. +Both syntaxes are correct. But usually the first syntax is preferred, as the star `*` denotes that it's a generator function, it describes the kind, not the name, so it should stick with the `function` keyword. ``` @@ -115,11 +118,11 @@ for(let value of generator) { } ``` -That's a much better-looking way to work with generators than calling `.next().value`, right? +Looks a lot nicer than calling `.next().value`, right? ...But please note: the example above shows `1`, then `2`, and that's all. It doesn't show `3`! -It's because for-of iteration ignores the last `value`, when `done: true`. So, if we want all results to be shown by `for..of`, we must return them with `yield`: +It's because `for..of` iteration ignores the last `value`, when `done: true`. So, if we want all results to be shown by `for..of`, we must return them with `yield`: ```js run function* generateSequence() { @@ -137,7 +140,7 @@ for(let value of generator) { } ``` -Naturally, as generators are iterable, we can call all related functionality, e.g. the spread operator `...`: +As generators are iterable, we can call all related functionality, e.g. the spread operator `...`: ```js run function* generateSequence() { @@ -151,7 +154,7 @@ let sequence = [0, ...generateSequence()]; alert(sequence); // 0, 1, 2, 3 ``` -In the code above, `...generateSequence()` turns the iterable into array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator)) +In the code above, `...generateSequence()` turns the iterable generator object into array of items (read more about the spread operator in the chapter [](info:rest-parameters-spread-operator#spread-operator)) ## Using generators for iterables @@ -185,28 +188,13 @@ let range = { } }; +// iteration over range returns numbers from range.from to range.to alert([...range]); // 1,2,3,4,5 ``` -Using a generator to make iterable sequences is simpler and much more elegant: - -```js run -function* generateSequence(start, end) { - for (let i = start; i <= end; i++) { - yield i; - } -} - -let sequence = [...generateSequence(1,5)]; - -alert(sequence); // 1, 2, 3, 4, 5 -``` - -## Converting Symbol.iterator to generator +We can use a generator function for iteration by providing it as `Symbol.iterator`. -We can add generator-style iteration to any custom object by providing a generator as `Symbol.iterator`. - -Here's the same `range`, but with a much more compact iterator: +Here's the same `range`, but much more compact: ```js run let range = { @@ -229,7 +217,7 @@ That works, because `range[Symbol.iterator]()` now returns a generator, and gene That's not a coincidence, of course. Generators were added to JavaScript language with iterators in mind, to implement them easier. -The last variant with a generator is much more concise than the original iterable code of `range`, and keeps the same functionality. +The variant with a generator is much more concise than the original iterable code of `range`, and keeps the same functionality. ```smart header="Generators may generate values forever" In the examples above we generated finite sequences, but we can also make a generator that yields values forever. For instance, an unending sequence of pseudo-random numbers. @@ -241,18 +229,26 @@ That surely would require a `break` (or `return`) in `for..of` over such generat Generator composition is a special feature of generators that allows to transparently "embed" generators in each other. -For instance, we'd like to generate a sequence of: -- digits `0..9` (character codes 48..57), -- followed by alphabet letters `a..z` (character codes 65..90) -- followed by uppercased letters `A..Z` (character codes 97..122) +For instance, we have a function that generates a sequence of numbers: -We can use the sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let's generate it first. +```js +function* generateSequence(start, end) { + for (let i = start; i <= end; i++) yield i; +} +``` -We already have `function* generateSequence(start, end)`. Let's reuse it to deliver 3 sequences one after another, together they are exactly what we need. +Now we'd like to reuse it for generation of a more complex sequence: +- first, digits `0..9` (with character codes 48..57), +- followed by uppercase alphabet letters `A..Z` (character codes 65..90) +- followed by lowercase alphabet letters `a..z` (character codes 97..122) + +We can use this sequence e.g. to create passwords by selecting characters from it (could add syntax characters as well), but let's generate it first. In a regular function, to combine results from multiple other functions, we call them, store the results, and then join at the end. -For generators, we can do better, like this: +For generators, there's a special `yield*` syntax to "embed" (compose) one generator into another. + +The composed generator: ```js run function* generateSequence(start, end) { @@ -283,7 +279,7 @@ for(let code of generatePasswordCodes()) { alert(str); // 0..9A..Za..z ``` -The special `yield*` directive in the example is responsible for the composition. It *delegates* the execution to another generator. Or, to say it simple, `yield* gen` iterates over the generator `gen` and transparently forwards its yields outside. As if the values were yielded by the outer generator. +The `yield*` directive *delegates* the execution to another generator. This term means that `yield* gen` iterates over the generator `gen` and transparently forwards its yields outside. As if the values were yielded by the outer generator. The result is the same as if we inlined the code from nested generators: @@ -316,15 +312,11 @@ for(let code of generateAlphaNum()) { alert(str); // 0..9A..Za..z ``` -A generator composition is a natural way to insert a flow of one generator into another. - -It works even if the flow of values from the nested generator is infinite. It's simple and doesn't use extra memory to store intermediate results. +A generator composition is a natural way to insert a flow of one generator into another. It doesn't use extra memory to store intermediate results. ## "yield" is a two-way road -Till this moment, generators were like "iterators on steroids". And that's how they are often used. - -But in fact they are much more powerful and flexible. +Till this moment, generators were similar to iterable objects, with a special syntax to generate values. But in fact they are much more powerful and flexible. That's because `yield` is a two-way road: it not only returns the result outside, but also can pass the value inside the generator. @@ -336,7 +328,7 @@ Let's see an example: function* gen() { *!* // Pass a question to the outer code and wait for an answer - let result = yield "2 + 2?"; // (*) + let result = yield "2 + 2 = ?"; // (*) */!* alert(result); @@ -351,39 +343,39 @@ generator.next(4); // --> pass the result into the generator ![](genYield2.svg) -1. The first call `generator.next()` is always without an argument. It starts the execution and returns the result of the first `yield` ("2+2?"). At this point the generator pauses the execution (still on that line). +1. The first call `generator.next()` is always without an argument. It starts the execution and returns the result of the first `yield "2+2=?"`. At this point the generator pauses the execution (still on that line). 2. Then, as shown at the picture above, the result of `yield` gets into the `question` variable in the calling code. 3. On `generator.next(4)`, the generator resumes, and `4` gets in as the result: `let result = 4`. -Please note, the outer code does not have to immediately call`next(4)`. It may take time to calculate the value. That's not a problem: the generator will resume when the call is made. +Please note, the outer code does not have to immediately call`next(4)`. It may take time. That's not a problem: the generator will wait. -This is also a valid code: +For instance: ```js // resume the generator after some time setTimeout(() => generator.next(4), 1000); ``` -As we can see, unlike regular functions, generators and the calling code can exchange results by passing values in `next/yield`. +As we can see, unlike regular functions, a generator and the calling code can exchange results by passing values in `next/yield`. To make things more obvious, here's another example, with more calls: ```js run function* gen() { - let ask1 = yield "2 + 2?"; + let ask1 = yield "2 + 2 = ?"; alert(ask1); // 4 - let ask2 = yield "3 * 3?" + let ask2 = yield "3 * 3 = ?" alert(ask2); // 9 } let generator = gen(); -alert( generator.next().value ); // "2 + 2?" +alert( generator.next().value ); // "2 + 2 = ?" -alert( generator.next(4).value ); // "3 * 3?" +alert( generator.next(4).value ); // "3 * 3 = ?" alert( generator.next(9).done ); // true ``` @@ -408,12 +400,12 @@ As we observed in the examples above, the outer code may pass a value into the g To pass an error into a `yield`, we should call `generator.throw(err)`. In that case, the `err` is thrown in the line with that `yield`. -For instance, here the yield of `"2 + 2?"` leads to an error: +For instance, here the yield of `"2 + 2 = ?"` leads to an error: ```js run function* gen() { try { - let result = yield "2 + 2?"; // (1) + let result = yield "2 + 2 = ?"; // (1) alert("The execution does not reach here, because the exception is thrown above"); } catch(e) { @@ -438,7 +430,7 @@ The current line of the calling code is the line with `generator.throw`, labelle ```js run function* generate() { - let result = yield "2 + 2?"; // Error in this line + let result = yield "2 + 2 = ?"; // Error in this line } let generator = generate(); diff --git a/1-js/12-generators-iterators/2-async-iterators-generators/article.md b/1-js/12-generators-iterators/2-async-iterators-generators/article.md index 82e16a23d..ff8bf8f2f 100644 --- a/1-js/12-generators-iterators/2-async-iterators-generators/article.md +++ b/1-js/12-generators-iterators/2-async-iterators-generators/article.md @@ -1,9 +1,9 @@ # Async iterators and generators -Asynchronous iterators allow to iterate over data that comes asynchronously, on-demand. +Asynchronous iterators allow to iterate over data that comes asynchronously, on-demand. For instance, when we download something chunk-by-chunk over a network. Asynchronous generators make it even more convenient. -For instance, when we download something chunk-by-chunk, and expect data fragments to come asynchronously and would like to iterate over them -- async iterators and generators may come in handy. Let's see a simple example first, to grasp the syntax, and then review a real-life use case. +Let's see a simple example first, to grasp the syntax, and then review a real-life use case. ## Async iterators @@ -21,7 +21,8 @@ let range = { [Symbol.iterator]() { */!* // ...it returns the iterator object: - // onward, for..of works only with that object, asking it for next values + // onward, for..of works only with that object, + // asking it for next values using next() return { current: this.from, last: this.to, @@ -65,12 +66,13 @@ let range = { [Symbol.asyncIterator]() { // (1) */!* // ...it returns the iterator object: - // onward, for await..of works only with that object, asking it for next values + // onward, for await..of works only with that object, + // asking it for next values using next() return { current: this.from, last: this.to, - // next() is called on each iteration by the for..of loop + // next() is called on each iteration by the for await..of loop *!* async next() { // (2) // it should return the value as an object {done:.., value :...} @@ -104,8 +106,8 @@ let range = { As we can see, the structure is similar to regular iterators: 1. To make an object asynchronously iterable, it must have a method `Symbol.asyncIterator` `(1)`. -2. It must return the object with `next()` method returning a promise `(2)`. -3. The `next()` method doesn't have to be `async`, it may be a regular method returning a promise, but `async` allows to use `await` inside. Here we just delay for a second `(3)`. +2. This method must return the object with `next()` method returning a promise `(2)`. +3. The `next()` method doesn't have to be `async`, it may be a regular method returning a promise, but `async` allows to use `await`, so that's convenient. Here we just delay for a second `(3)`. 4. To iterate, we use `for await(let value of range)` `(4)`, namely add "await" after "for". It calls `range[Symbol.asyncIterator]()` once, and then its `next()` for values. Here's a small cheatsheet: @@ -117,7 +119,7 @@ Here's a small cheatsheet: | to loop, use | `for..of` | `for await..of` | -````warn header="The spread operator ... doesn't work asynchronously" +````warn header="The spread operator `...` doesn't work asynchronously" Features that require regular, synchronous iterators, don't work with asynchronous ones. For instance, a spread operator won't work: @@ -146,8 +148,7 @@ for(let value of generateSequence(1, 5)) { } ``` - -Normally, we can't use `await` in generators. All values must come synchronously: there's no place for delay in `for..of`, it's a synchronous construct. +In regular generators we can't use `await`. All values must come synchronously: there's no place for delay in `for..of`, it's a synchronous construct. But what if we need to use `await` in the generator body? To perform network requests, for instance. @@ -182,7 +183,7 @@ Now we have the async generator, iterable with `for await...of`. It's indeed very simple. We add the `async` keyword, and the generator now can use `await` inside of it, rely on promises and other async functions. -Technically, another the difference of an async generator is that its `generator.next()` method is now asynchronous also, it returns promises. +Technically, another difference of an async generator is that its `generator.next()` method is now asynchronous also, it returns promises. In a regular generator we'd use `result = generator.next()` to get values. In an async generator, we should add `await`, like this: @@ -190,7 +191,7 @@ In a regular generator we'd use `result = generator.next()` to get values. In an result = await generator.next(); // result = {value: ..., done: true/false} ``` -## Iterables via async generators +## Async iterables As we already know, to make an object iterable, we should add `Symbol.iterator` to it. @@ -199,7 +200,9 @@ let range = { from: 1, to: 5, *!* - [Symbol.iterator]() { ...return object with next to make range iterable... } + [Symbol.iterator]() { + return + } */!* } ``` @@ -262,7 +265,7 @@ Now values come with a delay of 1 second between them. So far we've seen simple examples, to gain basic understanding. Now let's review a real-life use case. -There are many online APIs that deliver paginated data. For instance, when we need a list of users, then we can fetch it page-by-page: a request returns a pre-defined count (e.g. 100 users), and provides an URL to the next page. +There are many online services that deliver paginated data. For instance, when we need a list of users, a request returns a pre-defined count (e.g. 100 users) - "one page", and provides a URL to the next page. The pattern is very common, it's not about users, but just about anything. For instance, GitHub allows to retrieve commits in the same, paginated fashion: @@ -270,7 +273,7 @@ The pattern is very common, it's not about users, but just about anything. For i - It responds with a JSON of 30 commits, and also provides a link to the next page in the `Link` header. - Then we can use that link for the next request, to get more commits, and so on. -What we'd like to have is a simpler API: an iterable object with commits, so that we could go over them like this: +But we'd like to have a simpler API: an iterable object with commits, so that we could go over them like this: ```js let repo = 'javascript-tutorial/en.javascript.info'; // GitHub repository to get commits from @@ -280,7 +283,7 @@ for await (let commit of fetchCommits(repo)) { } ``` -We'd like `fetchCommits` to get commits for us, making requests whenever needed. And let it care about all pagination stuff, for us it'll be a simple `for await..of`. +We'd like to make a function `fetchCommits(repo)` that gets commits for us, making requests whenever needed. And let it care about all pagination stuff, for us it'll be a simple `for await..of`. With async generators that's pretty easy to implement: @@ -293,7 +296,7 @@ async function* fetchCommits(repo) { headers: {'User-Agent': 'Our script'}, // github requires user-agent header }); - const body = await response.json(); // (2) parses response as JSON (array of commits) + const body = await response.json(); // (2) response is JSON (array of commits) // (3) the URL of the next page is in the headers, extract it let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/); @@ -308,9 +311,9 @@ async function* fetchCommits(repo) { } ``` -1. We use the browser `fetch` method to download from a remote URL. It allows to supply authorization and other headers if needed, here GitHub requires `User-Agent`. +1. We use the browser [fetch](info:fetch) method to download from a remote URL. It allows to supply authorization and other headers if needed, here GitHub requires `User-Agent`. 2. The fetch result is parsed as JSON, that's again a `fetch`-specific method. -3. We can get the next page URL from the `Link` header of the response. It has a special format, so we use a regexp for that. The next page URL may look like this: `https://api.github.com/repositories/93253246/commits?page=2`, it's generated by GitHub itself. +3. We should get the next page URL from the `Link` header of the response. It has a special format, so we use a regexp for that. The next page URL may look like `https://api.github.com/repositories/93253246/commits?page=2`, it's generated by GitHub itself. 4. Then we yield all commits received, and when they finish -- the next `while(url)` iteration will trigger, making one more request. An example of use (shows commit authors in console): @@ -342,20 +345,18 @@ When we expect the data to come asynchronously, with delays, their async counter Syntax differences between async and regular iterators: -| | Iterators | Async iterators | +| | Iterable | Async Iterable | |-------|-----------|-----------------| -| Object method to provide iterator | `Symbol.iterator` | `Symbol.asyncIterator` | -| `next()` return value is | any value | `Promise` | +| Method to provide iterator | `Symbol.iterator` | `Symbol.asyncIterator` | +| `next()` return value is | `{value:…, done: true/false}` | `Promise` that resolves to `{value:…, done: true/false}` | Syntax differences between async and regular generators: | | Generators | Async generators | |-------|-----------|-----------------| | Declaration | `function*` | `async function*` | -| `generator.next()` returns | `{value:…, done: true/false}` | `Promise` that resolves to `{value:…, done: true/false}` | +| `next()` return value is | `{value:…, done: true/false}` | `Promise` that resolves to `{value:…, done: true/false}` | In web-development we often meet streams of data, when it flows chunk-by-chunk. For instance, downloading or uploading a big file. -We can use async generators to process such data, but it's worth to mention that there's also another API called Streams, that provides special interfaces to transform the data and to pass it from one stream to another (e.g. download from one place and immediately send elsewhere). - -Streams API not a part of JavaScript language standard. Streams and async generators complement each other, both are great ways to handle async data flows. +We can use async generators to process such data. It's also noteworthy that in some environments, such as browsers, there's also another API called Streams, that provides special interfaces to work with such streams, to transform the data and to pass it from one stream to another (e.g. download from one place and immediately send elsewhere). diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index a746aa1a0..ef122f30e 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -1,8 +1,7 @@ # Modules, introduction -As our application grows bigger, we want to split it into multiple files, so called 'modules'. -A module usually contains a class or a library of useful functions. +As our application grows bigger, we want to split it into multiple files, so called "modules". A module usually contains a class or a library of functions. For a long time, JavaScript existed without a language-level module syntax. That wasn't a problem, because initially scripts were small and simple, so there was no need. @@ -14,13 +13,15 @@ For instance: - [CommonJS](http://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. 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. +Now all these slowly become 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 it from now on. ## What is a module? -A module is just a file, a single script, as simple as that. +A module is just a file. One script is one module. -There are directives `export` and `import` to interchange functionality between modules, call functions of one module from another one: +Modules can load each other and use special directives `export` and `import` to interchange functionality, call functions of one module from another one: - `export` keyword labels variables and functions that should be accessible from outside the current module. - `import` allows to import functionality from other modules. @@ -44,7 +45,9 @@ alert(sayHi); // function... sayHi('John'); // Hello, John! ``` -In this tutorial we concentrate on the language itself, but we use browser as the demo environment, so let's see how to use modules in the browser. +The `import` directive loads the module by path `./sayHi.js` relative the current file and assigns exported function `sayHi` to the corresponding variable. + +Let's run the example in-browser. As modules support special keywords and features, we must tell the browser that a script should be treated as module, by using the attribute ` ``` -Here we can see it in the browser, but the same is true for any module. - ### Module-level scope Each module has its own top-level scope. In other words, top-level variables and functions from a module are not seen in other scripts. @@ -82,7 +83,7 @@ In the example below, two scripts are imported, and `hello.js` tries to use `use Modules are expected to `export` what they want to be accessible from outside and `import` what they need. -So we should import `user.js` directly into `hello.js` instead of `index.html`. +So we should import `user.js` into `hello.js` and get the required functionality from it instead of relying on global variables. That's the correct variant: @@ -125,10 +126,10 @@ alert("Module is evaluated!"); import `./alert.js`; // Module is evaluated! // 📁 2.js -import `./alert.js`; // (nothing) +import `./alert.js`; // (shows nothing) ``` -In practice, top-level module code is mostly used for initialization. We create data structures, pre-fill them, and if we want something to be reusable -- export it. +In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable -- export it. Now, a more advanced example. @@ -160,9 +161,9 @@ alert(admin.name); // Pete */!* ``` -So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that . +So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that. -Such behavior is great for modules that require configuration. We can set required properties on the first import, and then in further imports it's ready. +Such behavior allows to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready. For instance, `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside: @@ -175,7 +176,7 @@ export function sayHi() { } ``` -Now, in `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself: +In `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself: ```js // 📁 init.js @@ -183,6 +184,8 @@ import {admin} from './admin.js'; admin.name = "Pete"; ``` +Another module can also see `admin.name`: + ```js // 📁 other.js import {admin, sayHi} from './admin.js'; @@ -204,11 +207,13 @@ Its content depends on the environment. In the browser, it contains the url of t ``` -### Top-level "this" is undefined +### In a module, "this" is undefined That's kind of a minor feature, but for completeness we should mention it. -In a module, top-level `this` is undefined, as opposed to a global object in non-module scripts: +In a module, top-level `this` is undefined. + +Compare it to non-module scripts, where `this` is a global object: ```html run height=0 ``` -2. External scripts that are fetched from another origin (e.g. another site) require [CORS](mdn:Web/HTTP/CORS) headers, as described in the chapter . In other words, if a module script is fetched from another origin, the remote server must supply a header `Access-Control-Allow-Origin: *` (may use site domain instead of `*`) to indicate that the fetch is allowed. +2. External scripts that are fetched from another origin (e.g. another site) require [CORS](mdn:Web/HTTP/CORS) headers, as described in the chapter . In other words, if a module script is fetched from another origin, the remote server must supply a header `Access-Control-Allow-Origin` allowing the fetch. ```html @@ -332,13 +339,6 @@ Old browsers do not understand `type="module"`. Scripts of the unknown type are ``` -If we use bundle tools, then as scripts are bundled together into a single file (or few files), `import/export` statements inside those scripts are replaced by special bundler functions. So the resulting "bundled" script does not contain any `import/export`, it doesn't require `type="module"`, and we can put it into a regular script: - -```html - - -``` - ## Build tools In real-life, browser modules are rarely used in their "raw" form. Usually, we bundle them together with a special tool such as [Webpack](https://webpack.js.org/) and deploy to the production server. @@ -355,7 +355,14 @@ Build tools do the following: - Unused exports removed ("tree-shaking"). - Development-specific statements like `console` and `debugger` removed. - Modern, bleeding-edge JavaScript syntax may be transformed to older one with similar functionality using [Babel](https://babeljs.io/). - - The resulting file is minified (spaces removed, variables replaced with shorter named etc). + - The resulting file is minified (spaces removed, variables replaced with shorter names, etc). + +If we use bundle tools, then as scripts are bundled together into a single file (or few files), `import/export` statements inside those scripts are replaced by special bundler functions. So the resulting "bundled" script does not contain any `import/export`, it doesn't require `type="module"`, and we can put it into a regular script: + +```html + + +``` That said, native modules are also usable. So we won't be using Webpack here: you can configure it later. @@ -363,7 +370,7 @@ That said, native modules are also usable. So we won't be using Webpack here: yo To summarize, the core concepts are: -1. A module is a file. To make `import/export` work, browsers need ` -```smart header="Edge spaces and in-between empty text are usually hidden in tools" +```smart header="Spaces at string start/end and space-only text nodes are usually hidden in tools" Browser tools (to be covered soon) that work with DOM usually do not show spaces at the start/end of the text and empty text nodes (line-breaks) between tags. -That's because they are mainly used to decorate HTML, and do not affect how it is shown (in most cases). +Developer tools save screen space this way. -On further DOM pictures we'll sometimes omit them where they are irrelevant, to keep things short. +On further DOM pictures we'll sometimes omit them when they are irrelevant. Such spaces usually do not affect how the document is displayed. ``` - ## Autocorrection -If the browser encounters malformed HTML, it automatically corrects it when making DOM. +If the browser encounters malformed HTML, it automatically corrects it when making the DOM. -For instance, the top tag is always ``. Even if it doesn't exist in the document -- it will exist in the DOM, the browser will create it. The same goes for ``. +For instance, the top tag is always ``. Even if it doesn't exist in the document, it will exist in the DOM, because the browser will create it. The same goes for ``. -As an example, if the HTML file is a single word `"Hello"`, the browser will wrap it into `` and ``, add the required ``, and the DOM will be: +As an example, if the HTML file is the single word `"Hello"`, the browser will wrap it into `` and ``, and add the required ``, and the DOM will be:
@@ -106,7 +117,7 @@ drawHtmlTree(node3, 'div.domtree', 690, 150); While generating the DOM, browsers automatically process errors in the document, close tags and so on. -Such document with unclosed tags: +A document with unclosed tags: ```html no-beautify

Hello @@ -115,7 +126,7 @@ Such document with unclosed tags:

  • Dad ``` -...Will become a normal DOM, as the browser reads tags and restores the missing parts: +...will become a normal DOM as the browser reads tags and restores the missing parts:
    @@ -126,7 +137,7 @@ drawHtmlTree(node4, 'div.domtree', 690, 360); ````warn header="Tables always have ``" -An interesting "special case" is tables. By the DOM specification they must have ``, but HTML text may (officially) omit it. Then the browser creates `` in DOM automatically. +An interesting "special case" is tables. By the DOM specification they must have ``, but HTML text may (officially) omit it. Then the browser creates `` in the DOM automatically. For the HTML: @@ -148,7 +159,9 @@ You see? The `` appeared out of nowhere. You should keep this in mind whi ## Other node types -Let's add more tags and a comment to the page: +There are some other node types besides elements and text nodes. + +For example, comments: ```html @@ -174,7 +187,7 @@ let node6 = {"name":"HTML","nodeType":1,"children":[{"name":"HEAD","nodeType":1, drawHtmlTree(node6, 'div.domtree', 690, 500); -Here we see a new tree node type -- *comment node*, labeled as `#comment`. +We can see here a new tree node type -- *comment node*, labeled as `#comment`, between two text nodes. We may think -- why is a comment added to the DOM? It doesn't affect the visual representation in any way. But there's a rule -- if something's in HTML, then it also must be in the DOM tree. @@ -189,17 +202,15 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu 1. `document` -- the "entry point" into DOM. 2. element nodes -- HTML-tags, the tree building blocks. 3. text nodes -- contain text. -4. comments -- sometimes we can put the information there, it won't be shown, but JS can read it from the DOM. +4. comments -- sometimes we can put information there, it won't be shown, but JS can read it from the DOM. ## 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 DOM at an instant. - -## In the browser inspector +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. Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. -To do so, open the web-page [elks.html](elks.html), turn on the browser developer tools and switch to the Elements tab. +To do so, open the web page [elks.html](elks.html), turn on the browser developer tools and switch to the Elements tab. It should look like this: @@ -209,7 +220,7 @@ You can see the DOM, click on elements, see their details and so on. Please note that the DOM structure in developer tools is simplified. Text nodes are shown just as text. And there are no "blank" (space only) text nodes at all. That's fine, because most of the time we are interested in element nodes. -Clicking the button in the left-upper corner allows to choose a node from the webpage using a mouse (or other pointer devices) and "inspect" it (scroll to it in the Elements tab). This works great when we have a huge HTML page (and corresponding huge DOM) and would like to see the place of a particular element in it. +Clicking the button in the left-upper corner allows us to choose a node from the webpage using a mouse (or other pointer devices) and "inspect" it (scroll to it in the Elements tab). This works great when we have a huge HTML page (and corresponding huge DOM) and would like to see the place of a particular element in it. Another way to do it would be just right-clicking on a webpage and selecting "Inspect" in the context menu. @@ -225,10 +236,12 @@ The best way to study them is to click around. Most values are editable in-place ## Interaction with console -As we explore the DOM, we also may want to apply JavaScript to it. Like: get a node and run some code to modify it, to see the result. Here are few tips to travel between the Elements tab and the console. +As we work the DOM, we also may want to apply JavaScript to it. Like: get a node and run some code to modify it, to see the result. Here are few tips to travel between the Elements tab and the console. -- Select the first `
  • ` in the Elements tab. -- Press `key:Esc` -- it will open console right below the Elements tab. +For the start: + +1. Select the first `
  • ` in the Elements tab. +2. Press `key:Esc` -- it will open console right below the Elements tab. Now the last selected element is available as `$0`, the previously selected is `$1` etc. @@ -236,9 +249,11 @@ We can run commands on them. For instance, `$0.style.background = 'red'` makes t ![](domconsole0.png) -From the other side, if we're in console and have a variable referencing a DOM node, then we can use the command `inspect(node)` to see it in the Elements pane. +That's how to get a node from Elements in Console. + +There's also a road back. If there's a variable referencing a DOM node, then we can use the command `inspect(node)` in Console to see it in the Elements pane. -Or we can just output it in the console and explore "at-place", like `document.body` below: +Or we can just output the DOM node in the console and explore "in-place", like `document.body` below: ![](domconsole1.png) @@ -258,4 +273,4 @@ We can use developer tools to inspect DOM and modify it manually. Here we covered the basics, the most used and important actions to start with. There's an extensive documentation about Chrome Developer Tools at . The best way to learn the tools is to click here and there, read menus: most options are obvious. Later, when you know them in general, read the docs and pick up the rest. -DOM nodes have properties and methods that allow to travel between them, modify, move around the page and more. We'll get down to them in the next chapters. +DOM nodes have properties and methods that allow us to travel between them, modify them, move around the page, and more. We'll get down to them in the next chapters. diff --git a/2-ui/1-document/03-dom-navigation/1-dom-children/task.md b/2-ui/1-document/03-dom-navigation/1-dom-children/task.md index 4a9e741a9..d97f2748a 100644 --- a/2-ui/1-document/03-dom-navigation/1-dom-children/task.md +++ b/2-ui/1-document/03-dom-navigation/1-dom-children/task.md @@ -4,7 +4,7 @@ importance: 5 # DOM children -For the page: +Look at this page: ```html @@ -18,7 +18,7 @@ For the page: ``` -How to access: +For each of the following, give at least one way of how to access them: - The `
    ` DOM node? - The `