diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index b83fbcb94..45b3b72fd 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -29,10 +29,19 @@ Konkretus vaizdas priklauso nuo Chrome versijos, kurią naudoji. Kartais atsiran - Čia mes galim pamatyt raudoną error message. Šiuo atveju skriptas turi nežinomą "lalala" komandą. - Dešinėje yra clickable link'as į `bug.html:12` su skaičiumi eilutės, kurioje yra error'as. +<<<<<<< HEAD Žemiau error message'o yra mėlynas `>` simbolis. Jis parodo "command line", kuriame mes galime rašyti JavaScript komandas. Spausk `key:Enter` kad jas paleisti (`key:Shift+Enter` kad rašyti komandas per daugiau nei vieną eilutę). +======= +Below the error message, there is a blue `>` symbol. It marks a "command line" where we can type JavaScript commands. Press `key:Enter` to run them. +>>>>>>> 10c7807f49122f475f7cda5d07a324247091c080 Dabar mes galime matyti error'us. Kaip pradžiai, to pakanka. Vėliau mes grįšim į developer tools'us ir kalbėsim apie debugginimą chapteryje . +```smart header="Multi-line input" +Usually, when we put a line of code into the console, and then press `key:Enter`, it executes. + +To insert multiple lines, press `key:Shift+Enter`. This way one can enter long fragments of JavaScript code. +``` ## Firefox, Edge ir kiti @@ -50,6 +59,7 @@ Atidaryk Preferences ir eik į "Advanced". Apačioj bus checkbox'as: Dabar `key:Cmd+Opt+C` įjungs konsolę. Taip pat turėk omeny, kad naujas item'as "Develop" atsirado viršutiniam meniu. Jame yra daug komandų ir nustatymų. +<<<<<<< HEAD ```smart header="Kelių eilučių komandos" Dažniausiai, jeigu konsolėje parašome vieną eilutę kodo ir paspaudžiame `key:Enter`, ji suveikia. @@ -57,6 +67,9 @@ Tam, kad parašyti kelias eilutes, spausk `key:Shift+Enter`. Tokiu būdu mes gal ``` ## Reziumė +======= +## Summary +>>>>>>> 10c7807f49122f475f7cda5d07a324247091c080 - Developer tools'ai leidžia mum pamatyti errorus, paleisti komandas, analizuoti kintamuosiuos ir daugiau. - Juos paleisti galime su `key:F12` dauguma naršklių Windows'uose. Chrome, jeigu naudojame Mac, reikaluaja `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (iš pradžių reikia aktyvuoti). diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index b18aab19e..80dd56523 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -94,7 +94,7 @@ But it should be two separate statements, not one. Such a merging in this case i We recommend putting semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again -- *it is possible* to leave out semicolons most of the time. But it's safer -- especially for a beginner -- to use them. -## Comments +## Comments [#code-comments] As time goes on, programs become more and more complex. It becomes necessary to add *comments* which describe what the code does and why. 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 573d76bc5..a96432002 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -42,7 +42,7 @@ Only comments may appear above `"use strict"`. ```warn header="There's no way to cancel `use strict`" There is no directive like `"no use strict"` that reverts the engine to old behavior. -Once we enter strict mode, there's no return. +Once we enter strict mode, there's no going back. ``` ## Browser console diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 0da617373..cce9267db 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -10,9 +10,9 @@ message = 123456; Programming languages that allow such things are called "dynamically typed", meaning that there are data types, but variables are not bound to any of them. -There are seven basic data types in JavaScript. Here, we'll cover them in general and in the next chapters we'll talk about each of them in detail. +There are eight basic data types in JavaScript. Here, we'll cover them in general and in the next chapters we'll talk about each of them in detail. -## A number +## Number ```js let n = 123; @@ -62,14 +62,33 @@ Special numeric values formally belong to the "number" type. Of course they are We'll see more about working with numbers in the chapter . -## A string +## BigInt + +In JavaScript, the "number" type cannot represent integer values larger than 253 (or less than -253 for negatives), that's a technical limitation caused by their internal representation. That's about 16 decimal digits, so for most purposes the limitation isn't a problem, but sometimes we need really big numbers, e.g. for cryptography or microsecond-precision timestamps. + +`BigInt` type was recently added to the language to represent integers of arbitrary length. + +A `BigInt` is created by appending `n` to the end of an integer literal: + +```js +// the "n" at the end means it's a BigInt +const bigInt = 1234567890123456789012345678901234567890n; +``` + +As `BigInt` numbers are rarely needed, we devoted them a separate chapter . + +```smart header="Compatability issues" +Right now `BigInt` is supported in Firefox and Chrome, but not in Safari/IE/Edge. +``` + +## String A string in JavaScript must be surrounded by quotes. ```js let str = "Hello"; let str2 = 'Single quotes are ok too'; -let phrase = `can embed ${str}`; +let phrase = `can embed another ${str}`; ``` In JavaScript, there are 3 types of quotes. @@ -78,7 +97,7 @@ In JavaScript, there are 3 types of quotes. 2. Single quotes: `'Hello'`. 3. Backticks: `Hello`. -Double and single quotes are "simple" quotes. There's no difference between them in JavaScript. +Double and single quotes are "simple" quotes. There's practically no difference between them in JavaScript. Backticks are "extended functionality" quotes. They allow us to embed variables and expressions into a string by wrapping them in `${…}`, for example: @@ -102,12 +121,12 @@ alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (double quotes do n We'll cover strings more thoroughly in the chapter . ```smart header="There is no *character* type." -In some languages, there is a special "character" type for a single character. For example, in the C language and in Java it is `char`. +In some languages, there is a special "character" type for a single character. For example, in the C language and in Java it is called "char". In JavaScript, there is no such type. There's only one type: `string`. A string may consist of only one character or many of them. ``` -## A boolean (logical type) +## Boolean (logical type) The boolean type has only two values: `true` and `false`. @@ -198,6 +217,8 @@ typeof undefined // "undefined" typeof 0 // "number" +typeof 10n // "bigint" + typeof true // "boolean" typeof "foo" // "string" @@ -223,12 +244,12 @@ The last three lines may need additional explanation: 2. The result of `typeof null` is `"object"`. That's wrong. It is an officially recognized error in `typeof`, kept for compatibility. Of course, `null` is not an object. It is a special value with a separate type of its own. So, again, this is an error in the language. 3. The result of `typeof alert` is `"function"`, because `alert` is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently, returning `"function"`. That's not quite correct, but very convenient in practice. - ## Summary -There are 7 basic data types in JavaScript. +There are 8 basic data types in JavaScript. -- `number` for numbers of any kind: integer or floating-point. +- `number` for numbers of any kind: integer or floating-point, integers are limited by ±253. +- `bigint` is for integer numbers of arbitrary length. - `string` for strings. A string may have one or more characters, there's no separate single-character type. - `boolean` for `true`/`false`. - `null` for unknown values -- a standalone type that has a single value `null`. 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 20d093ea4..e7c381252 100644 --- a/1-js/02-first-steps/06-type-conversions/article.md +++ b/1-js/02-first-steps/06-type-conversions/article.md @@ -1,6 +1,6 @@ # Type Conversions -Most of the time, operators and functions automatically convert the values given to them to the right type. +Most of the time, operators and functions automatically convert the values given to them to the right type. For example, `alert` automatically converts any value to a string to show it. Mathematical operations convert values to numbers. @@ -81,18 +81,7 @@ alert( Number(false) ); // 0 Please note that `null` and `undefined` behave differently here: `null` becomes zero while `undefined` becomes `NaN`. -````smart header="Addition '+' concatenates strings" -Almost all mathematical operations convert values to numbers. A notable exception is addition `+`. If one of the added values is a string, the other one is also converted to a string. - -Then, it concatenates (joins) them: - -```js run -alert( 1 + '2' ); // '12' (string to the right) -alert( '1' + 2 ); // '12' (string to the left) -``` - -This only happens when at least one of the arguments is a string. Otherwise, values are converted to numbers. -```` +Most mathematical operators also perform such conversion, we'll see that in the next chapter. ## Boolean Conversion diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md b/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/solution.md similarity index 100% rename from 1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md rename to 1-js/02-first-steps/07-operators/3-primitive-conversions-questions/solution.md diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md b/1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md similarity index 100% rename from 1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md rename to 1-js/02-first-steps/07-operators/3-primitive-conversions-questions/task.md 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 6437b512e..a86a9f73e 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 @@ -13,7 +13,7 @@ null === +"\n0\n" → false Some of the reasons: 1. Obviously, true. -2. Dictionary comparison, hence false. +2. Dictionary comparison, hence false. `"a"` is smaller than `"p"`. 3. Again, dictionary comparison, first char of `"2"` is greater than the first char of `"1"`. 4. Values `null` and `undefined` equal each other only. 5. Strict equality is strict. Different types from both sides lead to false. diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg index cbc8c7840..ca3e0aead 100644 --- a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.svg @@ -1 +1 @@ -BeginCanceledCanceledWelcome!I don't know youWrong passwordWho's there?Password?CancelCancelAdminTheMasterOtherOther \ No newline at end of file +BeginCanceledCanceledWelcome!I don't know youWrong passwordWho's there?Password?CancelCancelAdminTheMasterOtherOther \ No newline at end of file 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 382adadac..b3e3953b8 100644 --- a/1-js/02-first-steps/12-while-for/article.md +++ b/1-js/02-first-steps/12-while-for/article.md @@ -256,7 +256,7 @@ For even values of `i`, the `continue` directive stops executing the body and pa ````smart header="The `continue` directive helps decrease nesting" A loop that shows odd values could look like this: -```js +```js run for (let i = 0; i < 10; i++) { if (i % 2) { @@ -268,7 +268,7 @@ for (let i = 0; i < 10; i++) { From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`. -But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of`if` is longer than a few lines, that may decrease the overall readability. +But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability. ```` ````warn header="No `break/continue` to the right side of '?'" 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 b1881e311..cbc3cc1ed 100644 --- a/1-js/02-first-steps/14-function-basics/article.md +++ b/1-js/02-first-steps/14-function-basics/article.md @@ -266,7 +266,7 @@ There may be many occurrences of `return` in a single function. For instance: ```js run function checkAge(age) { - if (age > 18) { + if (age >= 18) { *!* return true; */!* 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 1b0f4e377..ee7dea4c4 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -4,7 +4,7 @@ Before writing more complex code, let's talk about debugging. [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 has enough features, most other browsers have a similar process`. +We'll be using Chrome here, because it has enough features, most other browsers have a similar process. ## The "Sources" panel @@ -24,11 +24,11 @@ Let's click it and select `hello.js` in the tree view. Here's what should show u ![](chrome-tabs.svg) -Here we can see three zones: +The Sources panel has 3 parts: -1. The **Resources zone** lists HTML, JavaScript, CSS and other files, including images that are attached to the page. Chrome extensions may appear here too. -2. The **Source zone** shows the source code. -3. The **Information and control zone** is for debugging, we'll explore it soon. +1. The **File Navigator** pane lists HTML, JavaScript, CSS and other files, including images that are attached to the page. Chrome extensions may appear here too. +2. The **Code Editor** pane shows the source code. +3. The **JavaScript Debugging** pane is for debugging, we'll explore it soon. Now you could click the same toggler again to hide the resources list and give the code some space. diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg index 6f729fd3a..1f7d21288 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.svg @@ -1 +1 @@ -open sources \ No newline at end of file +open sources \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg index e9b28655f..6fb4332f1 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.svg @@ -1 +1 @@ -here's the listbreakpoints \ No newline at end of file +here's the listbreakpoints \ No newline at end of file diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg index 61f08d25c..0d5bde9c4 100644 --- a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg +++ b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.svg @@ -1 +1 @@ -nested calls \ No newline at end of file +nested calls \ No newline at end of file diff --git a/1-js/03-code-quality/02-coding-style/article.md b/1-js/03-code-quality/02-coding-style/article.md index bdcfec545..37cd67570 100644 --- a/1-js/03-code-quality/02-coding-style/article.md +++ b/1-js/03-code-quality/02-coding-style/article.md @@ -86,7 +86,7 @@ For example: ```js // backtick quotes ` allow to split the string into multiple lines let str = ` - Ecma International's TC39 is a group of JavaScript developers, + ECMA International's TC39 is a group of JavaScript developers, implementers, academics, and more, collaborating with the community to maintain and evolve the definition of JavaScript. `; diff --git a/1-js/03-code-quality/02-coding-style/code-style.svg b/1-js/03-code-quality/02-coding-style/code-style.svg index e8e374048..bd62691c6 100644 --- a/1-js/03-code-quality/02-coding-style/code-style.svg +++ b/1-js/03-code-quality/02-coding-style/code-style.svg @@ -1 +1 @@ -2No space between the function name and parentheses between the parentheses and the parameterIndentation 2 spacesA space after for/if/while…} else { without a line breakSpaces around a nested callAn empty line between logical blocksLines are not very longA semicolon ; is mandatorySpaces around operatorsCurly brace { on the same line, after a spaceA space between parametersA space between parameters \ No newline at end of file +2No space between the function name and parentheses between the parentheses and the parameterIndentation 2 spacesA space after for/if/while…} else { without a line breakSpaces around a nested callAn empty line between logical blocksLines are not very longA semicolon ; is mandatorySpaces around operatorsCurly brace { on the same line, after a spaceA space between parametersA space between parameters \ No newline at end of file diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index e9b5e96c1..68ffcae4d 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -159,8 +159,8 @@ We can select one of two ways to organize the test here: assert.equal(pow(2, 3), 8); }); - it("3 raised to power 3 is 27", function() { - assert.equal(pow(3, 3), 27); + it("3 raised to power 4 is 81", function() { + assert.equal(pow(3, 4), 81); }); }); @@ -182,7 +182,7 @@ The result: [iframe height=250 src="pow-2" edit border="1"] -As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `27`. +As we could expect, the second test failed. Sure, our function always returns `8`, while the `assert` expects `81`. ## Improving the implementation diff --git a/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js index 9a2f8fde7..c803f0e61 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-2.view/test.js @@ -4,8 +4,8 @@ describe("pow", function() { assert.equal(pow(2, 3), 8); }); - it("3 raised to power 3 is 27", function() { - assert.equal(pow(3, 3), 27); + it("3 raised to power 4 is 81", function() { + assert.equal(pow(3, 4), 81); }); }); diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index b399fd428..75db49d2f 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 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. +1. First, the transpiler program, which rewrites the code. The developer runs it on their own computer. It rewrites the code into the older standard. And then the code is delivered to the website for users. Modern project build systems like [webpack](http://webpack.github.io/) provide means to run transpiler automatically on every code change, so that it's very easy to integrate into development process. 2. Second, the polyfill. diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index ea015e7ca..aaafacd6f 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -101,7 +101,9 @@ For multiword properties, the dot access doesn't work: user.likes birds = true ``` -That's because the dot requires the key to be a valid variable identifier. That is: no spaces and other limitations. +JavaScript doesn't understand that. It thinks that we address `user.likes`, and then gives a syntax error when comes across unexpected `birds`. + +The dot requires the key to be a valid variable identifier. That implies: contains no spaces, doesn't start with a digit and doesn't include special characters (`$` и `_` are allowed). There's an alternative "square bracket notation" that works with any string: @@ -203,43 +205,6 @@ Square brackets are much more powerful than the dot notation. They allow any pro So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets. - - -````smart header="Reserved words are allowed as property names" -A variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc. - -But for an object property, there's no such restriction. Any name is fine: - -```js run -let obj = { - for: 1, - let: 2, - return: 3 -}; - -alert( obj.for + obj.let + obj.return ); // 6 -``` - -Basically, any name is allowed, but there's a special one: `"__proto__"` that gets special treatment for historical reasons. For instance, we can't set it to a non-object value: - -```js run -let obj = {}; -obj.__proto__ = 5; -alert(obj.__proto__); // [object Object], didn't work as intended -``` - -As we see from the code, the assignment to a primitive `5` is ignored. - -That can become a source of bugs and even vulnerabilities if we intend to store arbitrary key-value pairs in an object, and allow a visitor to specify the keys. - -In that case the visitor may choose `__proto__` as the key, and the assignment logic will be ruined (as shown above). - -There is a way to make objects treat `__proto__` as a regular property, which we'll cover later, but first we need to know more about objects. - -There's also another data structure [Map](info:map-set), that we'll learn in the chapter , which supports arbitrary keys. -```` - - ## Property value shorthand In real code we often use existing variables as values for property names. @@ -284,7 +249,63 @@ let user = { }; ``` -## Existence check +## Property names limitations + +Property names (keys) must be either strings or symbols (a special type for identifiers, to be covered later). + +Other types are automatically converted to strings. + +For instance, a number `0` becomes a string `"0"` when used as a property key: + +```js run +let obj = { + 0: "test" // same as "0": "test" +}; + +// both alerts access the same property (the number 0 is converted to string "0") +alert( obj["0"] ); // test +alert( obj[0] ); // test (same property) +``` + +**Reserved words are allowed as property names.** + +As we already know, a variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc. + +But for an object property, there's no such restriction. Any name is fine: + +```js run +let obj = { + for: 1, + let: 2, + return: 3 +}; + +alert( obj.for + obj.let + obj.return ); // 6 +``` + +We can use any string as a key, but there's a special property named `__proto__` that gets special treatment for historical reasons. + +For instance, we can't set it to a non-object value: + +```js run +let obj = {}; +obj.__proto__ = 5; // assign a number +alert(obj.__proto__); // [object Object] - the value is an object, didn't work as intended +``` + +As we see from the code, the assignment to a primitive `5` is ignored. + +The nature of `__proto__` will be revealed in detail later in the chapter [](info:prototype-inheritance). + +As for now, it's important to know that such behavior of `__proto__` can become a source of bugs and even vulnerabilities if we intend to store user-provided keys in an object. + +The problem is that a visitor may choose `__proto__` as the key, and the assignment logic will be ruined (as shown above). + +Later we'll see workarounds for the problem: +1. We'll see how to make an objects treat `__proto__` as a regular property in the chapter [](info:prototype-methods). +2. There's also study another data structure [Map](info:map-set) in the chapter , which supports arbitrary keys. + +## Property existance test, "in" operator A notable objects feature is that it's possible to access any property. There will be no error if the property doesn't exist! Accessing a non-existing property just returns `undefined`. It provides a very common way to test whether the property exists -- to get it and compare vs undefined: @@ -322,7 +343,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 the property existance just fine. But there's a special case when it fails, but `"in"` works correctly. +Usually, the strict comparison `"=== undefined"` check the property existence 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`: diff --git a/1-js/04-object-basics/02-garbage-collection/family-delete-refs.svg b/1-js/04-object-basics/02-garbage-collection/family-delete-refs.svg index 133b3908d..2ae1f664c 100644 --- a/1-js/04-object-basics/02-garbage-collection/family-delete-refs.svg +++ b/1-js/04-object-basics/02-garbage-collection/family-delete-refs.svg @@ -1 +1 @@ -<global variable>ObjectObjectwifefamilyname: "John"name: "Ann"motherObjectfatherhusband \ No newline at end of file +<global variable>ObjectObjectwifefamilyname: "John"name: "Ann"motherObjectfatherhusband \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-family.svg b/1-js/04-object-basics/02-garbage-collection/family-no-family.svg index 8ba3b9f9f..655d1982e 100644 --- a/1-js/04-object-basics/02-garbage-collection/family-no-family.svg +++ b/1-js/04-object-basics/02-garbage-collection/family-no-family.svg @@ -1 +1 @@ -<global>ObjectObjectfatherwifename: "John"name: "Ann"motherObjecthusbandfamily: null \ No newline at end of file +<global>ObjectObjectfatherwifename: "John"name: "Ann"motherObjecthusbandfamily: null \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father.svg b/1-js/04-object-basics/02-garbage-collection/family-no-father.svg index 59f0b2afc..b76c868e0 100644 --- a/1-js/04-object-basics/02-garbage-collection/family-no-father.svg +++ b/1-js/04-object-basics/02-garbage-collection/family-no-father.svg @@ -1 +1 @@ -ObjectObjectwifefamilyname: "John"name: "Ann"motherObject<global> \ No newline at end of file +ObjectObjectwifefamilyname: "John"name: "Ann"motherObject<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/family.svg b/1-js/04-object-basics/02-garbage-collection/family.svg index 8fbbc35ea..bec2f4ddc 100644 --- a/1-js/04-object-basics/02-garbage-collection/family.svg +++ b/1-js/04-object-basics/02-garbage-collection/family.svg @@ -1 +1 @@ -ObjectObjectfatherwifefamilyname: "John"name: "Ann"motherObjecthusband<global variable> \ No newline at end of file +ObjectObjectfatherwifefamilyname: "John"name: "Ann"motherObjecthusband<global variable> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.svg index 125031ee4..2563c8185 100644 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.svg +++ b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.svg @@ -1 +1 @@ -<global> \ No newline at end of file +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.svg index b4f1a0870..acd5025e9 100644 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.svg +++ b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.svg @@ -1 +1 @@ -<global> \ No newline at end of file +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.svg index 7477c4ba7..4421ec784 100644 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.svg +++ b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.svg @@ -1 +1 @@ -<global> \ No newline at end of file +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.svg index 6b13c1dc0..74adc8135 100644 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.svg +++ b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.svg @@ -1 +1 @@ -<global> \ No newline at end of file +<global> \ No newline at end of file diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.svg b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.svg index 5cb54dc28..abb127ab2 100644 --- a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.svg +++ b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.svg @@ -1 +1 @@ -<global>unreachables \ No newline at end of file +<global>unreachables \ No newline at end of file diff --git a/1-js/04-object-basics/03-symbol/article.md b/1-js/04-object-basics/03-symbol/article.md index a17f85fe2..305ca24b4 100644 --- a/1-js/04-object-basics/03-symbol/article.md +++ b/1-js/04-object-basics/03-symbol/article.md @@ -18,7 +18,7 @@ let id = Symbol(); Upon creation, we can give symbol a description (also called a symbol name), mostly useful for debugging purposes: -```js run +```js // id is a symbol with the description "id" let id = Symbol("id"); ``` @@ -178,22 +178,6 @@ alert( clone[id] ); // 123 There's no paradox here. That's by design. The idea is that when we clone an object or merge objects, we usually want *all* properties to be copied (including symbols like `id`). -````smart header="Property keys of other types are coerced to strings" -We can only use strings or symbols as keys in objects. Other types are converted to strings. - -For instance, a number `0` becomes a string `"0"` when used as a property key: - -```js run -let obj = { - 0: "test" // same as "0": "test" -}; - -// both alerts access the same property (the number 0 is converted to string "0") -alert( obj["0"] ); // test -alert( obj[0] ); // test (same property) -``` -```` - ## Global symbols As we've seen, usually all symbols are different, even if they have the same name. But sometimes we want same-named symbols to be same entities. For instance, different parts of our application want to access symbol `"id"` meaning exactly the same property. diff --git a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md index ea00c970d..c1aaf4f97 100644 --- a/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md +++ b/1-js/04-object-basics/04-object-methods/4-object-property-this/solution.md @@ -22,6 +22,17 @@ The value of `this` is one for the whole function, code blocks and object litera So `ref: this` actually takes current `this` of the function. +We can rewrite the function and return the same `this` with `undefined` value: + +```js run +function makeUser(){ + return this; // this time there's no object literal +} + +alert( makeUser().name ); // Error: Cannot read property 'name' of undefined +``` +As you can see the result of `alert( makeUser().name )` is the same as the result of `alert( user.ref.name )` from the previous example. + Here's the opposite case: ```js run diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md index 06138a004..6c13acda6 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -7,7 +7,7 @@ Let's look at the key distinctions between primitives and objects. A primitive - Is a value of a primitive type. -- There are 6 primitive types: `string`, `number`, `boolean`, `symbol`, `null` and `undefined`. +- There are 7 primitive types: `string`, `number`, `bigint`, `boolean`, `symbol`, `null` and `undefined`. An object diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index f9ffdd81b..d167b836e 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -1,8 +1,12 @@ # Numbers -All numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers". +In modern JavaScript, there are two types of numbers: -Let's expand upon what we currently know about them. +1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter. + +2. BigInt numbers, to represent integers of arbitrary length. They are sometimes needed, because a regular number can't exceed 253 or be less than -253. As bigints are used in few special areas, we devote them a special chapter . + +So here we'll talk about regular numbers. Let's expand our knowledge of them. ## More ways to write a number @@ -29,14 +33,13 @@ In other words, `"e"` multiplies the number by `1` with the given zeroes count. 1.23e6 = 1.23 * 1000000 ``` - Now let's write something very small. Say, 1 microsecond (one millionth of a second): ```js let ms = 0.000001; ``` -Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say: +Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say the same as: ```js let ms = 1e-6; // six zeroes to the left from 1 @@ -271,13 +274,11 @@ JavaScript doesn't trigger an error in such events. It does its best to fit the ```smart header="Two zeroes" Another funny consequence of the internal representation of numbers is the existence of two zeroes: `0` and `-0`. -That's because a sign is represented by a single bit, so every number can be positive or negative, including a zero. +That's because a sign is represented by a single bit, so it can be set or not set for any number including a zero. In most cases the distinction is unnoticeable, because operators are suited to treat them as the same. ``` - - ## Tests: isFinite and isNaN Remember these two special numeric values? @@ -409,10 +410,10 @@ There are more functions and constants in `Math` object, including trigonometry, ## Summary -To write big numbers: +To write numbers with many zeroes: -- Append `"e"` with the zeroes count to the number. Like: `123e6` is `123` with 6 zeroes. -- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. That's for one-millionth or such. +- Append `"e"` with the zeroes count to the number. Like: `123e6` is the same as `123` with 6 zeroes `123000000`. +- A negative number after `"e"` causes the number to be divided by 1 with given zeroes. E.g. `123e-6` means `0.000123` (`123` millionths). For different numeral systems: diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index 8a2fe14f7..765823d7c 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -534,7 +534,7 @@ The "right" algorithm to do string comparisons is more complex than it may seem, So, the browser needs to know the language to compare. -Luckily, all modern browsers (IE10- requires the additional library [Intl.JS](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA 402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). +Luckily, all modern browsers (IE10- requires the additional library [Intl.js](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). It provides a special method to compare strings in different languages, following their rules. diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js new file mode 100644 index 000000000..8dea23a06 --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/solution.js @@ -0,0 +1,6 @@ +function groupById(array) { + return array.reduce((obj, value) => { + obj[value.id] = value; + return obj; + }, {}) +} diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js new file mode 100644 index 000000000..02299e307 --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/_js.view/test.js @@ -0,0 +1,20 @@ +describe("groupById", function() { + + it("creates an object grouped by id", function() { + let users = [ + {id: 'john', name: "John Smith", age: 20}, + {id: 'ann', name: "Ann Smith", age: 24}, + {id: 'pete', name: "Pete Peterson", age: 31}, + ]; + + assert.deepEqual(groupById(users), { + john: {id: 'john', name: "John Smith", age: 20} + ann: {id: 'ann', name: "Ann Smith", age: 24}, + pete: {id: 'pete', name: "Pete Peterson", age: 31}, + }); + }); + + it("works with an empty array", function() { + assert.deepEqual(groupById(users), {}); + }); +}); diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/solution.md b/1-js/05-data-types/05-array-methods/12-reduce-object/solution.md new file mode 100644 index 000000000..e69de29bb diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md new file mode 100644 index 000000000..421f8cb8b --- /dev/null +++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md @@ -0,0 +1,37 @@ +importance: 4 + +--- + +# Create keyed object from array + +Let's say we received an array of users in the form `{id:..., name:..., age... }`. + +Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values. + +For example: + +```js +let users = [ + {id: 'john', name: "John Smith", age: 20}, + {id: 'ann', name: "Ann Smith", age: 24}, + {id: 'pete', name: "Pete Peterson", age: 31}, +]; + +let usersById = groupById(users); + +/* +// after the call we have: + +usersById = { + john: {id: 'john', name: "John Smith", age: 20} + ann: {id: 'ann', name: "Ann Smith", age: 24}, + pete: {id: 'pete', name: "Pete Peterson", age: 31}, +} +*/ +``` + +Such function is really handy when working with server data. + +In this task we assume that `id` is unique. There may be no two array items with the same `id`. + +Please use array `.reduce` method in the solution. diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 5a1a7762a..301696440 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -384,7 +384,7 @@ The order became `1, 15, 2`. Incorrect. But why? **The items are sorted as strings by default.** -Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed `"2" > "15"`. +Literally, all elements are converted to strings for comparisons. For strings, lexicographic ordering is applied and indeed `"2" > "15"`. To use our own sorting order, we need to supply a function as the argument of `arr.sort()`. @@ -431,7 +431,6 @@ By the way, if we ever want to know which elements are compared -- nothing preve The algorithm may compare an element with multiple others in the process, but it tries to make as few comparisons as possible. - ````smart header="A comparison function may return any number" Actually, a comparison function is only required to return a positive number to say "greater" and a negative number to say "less". @@ -456,6 +455,22 @@ arr.sort( (a, b) => a - b ); This works exactly the same as the longer version above. ```` +````smart header="Use `localeCompare` for strings" +Remember [strings](info:string#correct-comparisons) comparison algorithm? It compares letters by their codes by default. + +For many alphabets, it's better to use `str.localeCompare` method to correctly sort letters, such as `Ö`. + +For example, let's sort a few countries in German: + +```js run +let countries = ['Österreich', 'Andorra', 'Vietnam']; + +alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (wrong) + +alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (correct!) +``` +```` + ### reverse The method [arr.reverse](mdn:js/Array/reverse) reverses the order of elements in `arr`. @@ -530,7 +545,7 @@ The methods [arr.reduce](mdn:js/Array/reduce) and [arr.reduceRight](mdn:js/Array The syntax is: ```js -let value = arr.reduce(function(previousValue, item, index, array) { +let value = arr.reduce(function(accumulator, item, index, array) { // ... }, [initial]); ``` @@ -539,14 +554,16 @@ The function is applied to all array elements one after another and "carries on" Arguments: -- `previousValue` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided). +- `accumulator` -- is the result of the previous function call, equals `initial` the first time (if `initial` is provided). - `item` -- is the current array item. - `index` -- is its position. - `array` -- is the array. As function is applied, the result of the previous function call is passed to the next one as the first argument. -Sounds complicated, but it's not if you think about the first argument as the "accumulator" that stores the combined result of all previous execution. And at the end it becomes the result of `reduce`. +So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end it becomes the result of `reduce`. + +Sounds complicated? The easiest way to grasp that is by example. @@ -611,7 +628,6 @@ let arr = []; arr.reduce((sum, current) => sum + current); ``` - So it's advised to always specify the initial value. The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index b55f8f018..bc4340f36 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -12,7 +12,7 @@ If an object isn't technically an array, but represents a collection (list, set) We can easily grasp the concept of iterables by making one of our own. -For instance, we have an object, that is not an array, but looks suitable for `for..of`. +For instance, we have an object that is not an array, but looks suitable for `for..of`. Like a `range` object that represents an interval of numbers: @@ -294,7 +294,7 @@ Objects that can be used in `for..of` are called *iterable*. - Technically, iterables must implement the method named `Symbol.iterator`. - The result of `obj[Symbol.iterator]` is called an *iterator*. It handles the further iteration process. - - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the iteration end, otherwise the `value` is the next value. + - An iterator must have the method named `next()` that returns an object `{done: Boolean, value: any}`, here `done:true` denotes the end of the iteration process, otherwise the `value` is the next value. - The `Symbol.iterator` method is called automatically by `for..of`, but we also can do it directly. - Built-in iterables like strings or arrays, also implement `Symbol.iterator`. - String iterator knows about surrogate pairs. diff --git a/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md index 4c8af1f24..160675185 100644 --- a/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md +++ b/1-js/05-data-types/07-map-set/02-filter-anagrams/solution.md @@ -36,7 +36,7 @@ Letter-sorting is done by the chain of calls in the line `(*)`. For convenience let's split it into multiple lines: ```js -let sorted = arr[i] // PAN +let sorted = word // PAN .toLowerCase() // pan .split('') // ['p','a','n'] .sort() // ['a','n','p'] diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index c4d7c21a4..aaed5b454 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -41,6 +41,12 @@ alert( map.size ); // 3 As we can see, unlike objects, keys are not converted to strings. Any type of key is possible. +```smart header="`map[key]` isn't the right way to use a `Map`" +Although `map[key]` also works, e.g. we can set `map[key] = 2`, this is treating `map` as a plain JavaScript object, so it implies all corresponding limitations (no object keys and so on). + +So we should use `map` methods: `set`, `get` and so on. +``` + **Map can also use objects as keys.** For instance: diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 11ff9d5eb..bcc5e5e6f 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -141,7 +141,6 @@ And here's another part of the code, maybe another file using it: let john = { name: "John" }; countUser(john); // count his visits -countUser(john); // later john leaves us john = null; diff --git a/1-js/05-data-types/11-date/8-format-date-relative/solution.md b/1-js/05-data-types/11-date/8-format-date-relative/solution.md index 2507c840c..718618528 100644 --- a/1-js/05-data-types/11-date/8-format-date-relative/solution.md +++ b/1-js/05-data-types/11-date/8-format-date-relative/solution.md @@ -62,6 +62,8 @@ function formatDate(date) { year = year.toString().slice(-2); month = month < 10 ? '0' + month : month; dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth; + hour = hour < 10 ? '0' + hour : hour; + minutes = minutes < 10 ? '0' + minutes : minutes; if (diffSec < 1) { return 'right now'; diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg index 28f0c9b13..59e6a52c4 100644 --- a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg +++ b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.svg @@ -1 +1 @@ -fib ( 5 )fib(4)fib(3)fib(3)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1) \ No newline at end of file +fib ( 5 )fib(4)fib(3)fib(3)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1)fib(1)fib(2)fib(0)fib(1) \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg index 346c436a3..edec23912 100644 --- a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg +++ b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.svg @@ -1 +1 @@ -value"new item"nextvalue1nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file +value"new item"nextvalue1nextvalue2nextvalue3nextvalue4nextnulllist \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/recursion-pow.svg b/1-js/06-advanced-functions/01-recursion/recursion-pow.svg index 910b2aa50..8bd4a43fe 100644 --- a/1-js/06-advanced-functions/01-recursion/recursion-pow.svg +++ b/1-js/06-advanced-functions/01-recursion/recursion-pow.svg @@ -1 +1 @@ -pow(x,n)xx * pow(x, n-1)n == 1 ?YesNorecursive call until n==1 \ No newline at end of file +pow(x,n)xx * pow(x, n-1)n == 1 ?YesNorecursive call until n==1 \ No newline at end of file diff --git a/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg b/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg index 9a3296655..f47f0668b 100644 --- a/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg +++ b/1-js/06-advanced-functions/01-recursion/recursive-salaries.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md similarity index 89% rename from 1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md rename to 1-js/06-advanced-functions/02-rest-parameters-spread/article.md index a14f0fb73..74c85d4f4 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -1,4 +1,4 @@ -# Rest parameters and spread operator +# Rest parameters and spread syntax Many JavaScript built-in functions support an arbitrary number of arguments. @@ -122,7 +122,7 @@ As we remember, arrow functions don't have their own `this`. Now we know they do ```` -## Spread operator [#spread-operator] +## Spread syntax [#spread-syntax] We've just seen how to get an array from the list of parameters. @@ -148,7 +148,7 @@ alert( Math.max(arr) ); // NaN And surely we can't manually list items in the code `Math.max(arr[0], arr[1], arr[2])`, because we may be unsure how many there are. As our script executes, there could be a lot, or there could be none. And that would get ugly. -*Spread operator* to the rescue! It looks similar to rest parameters, also using `...`, but does quite the opposite. +*Spread syntax* to the rescue! It looks similar to rest parameters, also using `...`, but does quite the opposite. When `...arr` is used in the function call, it "expands" an iterable object `arr` into the list of arguments. @@ -169,7 +169,7 @@ let arr2 = [8, 3, -8, 1]; alert( Math.max(...arr1, ...arr2) ); // 8 ``` -We can even combine the spread operator with normal values: +We can even combine the spread syntax with normal values: ```js run @@ -179,7 +179,7 @@ let arr2 = [8, 3, -8, 1]; alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25 ``` -Also, the spread operator can be used to merge arrays: +Also, the spread syntax can be used to merge arrays: ```js run let arr = [3, 5, 1]; @@ -192,9 +192,9 @@ let merged = [0, ...arr, 2, ...arr2]; alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2) ``` -In the examples above we used an array to demonstrate the spread operator, but any iterable will do. +In the examples above we used an array to demonstrate the spread syntax, but any iterable will do. -For instance, here we use the spread operator to turn the string into array of characters: +For instance, here we use the spread syntax to turn the string into array of characters: ```js run let str = "Hello"; @@ -202,7 +202,7 @@ let str = "Hello"; alert( [...str] ); // H,e,l,l,o ``` -The spread operator internally uses iterators to gather elements, the same way as `for..of` does. +The spread syntax internally uses iterators to gather elements, the same way as `for..of` does. So, for a string, `for..of` returns characters and `...str` becomes `"H","e","l","l","o"`. The list of characters is passed to array initializer `[...str]`. @@ -220,24 +220,24 @@ The result is the same as `[...str]`. But there's a subtle difference between `Array.from(obj)` and `[...obj]`: - `Array.from` operates on both array-likes and iterables. -- The spread operator operates only on iterables. +- The spread syntax works only with iterables. So, for the task of turning something into an array, `Array.from` tends to be more universal. ## Summary -When we see `"..."` in the code, it is either rest parameters or the spread operator. +When we see `"..."` in the code, it is either rest parameters or the spread syntax. There's an easy way to distinguish between them: - When `...` is at the end of function parameters, it's "rest parameters" and gathers the rest of the list of arguments into an array. -- When `...` occurs in a function call or alike, it's called a "spread operator" and expands an array into a list. +- When `...` occurs in a function call or alike, it's called a "spread syntax" and expands an array into a list. Use patterns: - Rest parameters are used to create functions that accept any number of arguments. -- The spread operator is used to pass an array to functions that normally require a list of many arguments. +- The spread syntax is used to pass an array to functions that normally require a list of many arguments. Together they help to travel between a list and an array of parameters with ease. diff --git a/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md new file mode 100644 index 000000000..7cbd85ab7 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/solution.md @@ -0,0 +1,5 @@ +The answer is: **Pete**. + +A function gets outer variables as they are now, it uses the most recent values. + +Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one. diff --git a/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md new file mode 100644 index 000000000..819189773 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/1-closure-latest-changes/task.md @@ -0,0 +1,23 @@ +importance: 5 + +--- + +# Does a function pickup latest changes? + +The function sayHi uses an external variable name. When the function runs, which value is it going to use? + +```js +let name = "John"; + +function sayHi() { + alert("Hi, " + name); +} + +name = "Pete"; + +sayHi(); // what will it show: "John" or "Pete"? +``` + +Such situations are common both in browser and server-side development. A function may be scheduled to execute later than it is created, for instance after a user action or a network request. + +So, the question is: does it pick up the latest changes? diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/solution.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/solution.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/solution.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/solution.js diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/source.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/source.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/source.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/source.js diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/_js.view/test.js b/1-js/06-advanced-functions/03-closure/10-make-army/_js.view/test.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/_js.view/test.js rename to 1-js/06-advanced-functions/03-closure/10-make-army/_js.view/test.js diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.svg b/1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.svg rename to 1-js/06-advanced-functions/03-closure/10-make-army/lexenv-makearmy.svg diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/solution.md b/1-js/06-advanced-functions/03-closure/10-make-army/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/8-make-army/solution.md rename to 1-js/06-advanced-functions/03-closure/10-make-army/solution.md diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/task.md b/1-js/06-advanced-functions/03-closure/10-make-army/task.md similarity index 86% rename from 1-js/06-advanced-functions/03-closure/8-make-army/task.md rename to 1-js/06-advanced-functions/03-closure/10-make-army/task.md index ede8fd045..93e64f2d0 100644 --- a/1-js/06-advanced-functions/03-closure/8-make-army/task.md +++ b/1-js/06-advanced-functions/03-closure/10-make-army/task.md @@ -31,5 +31,5 @@ army[5](); // and number 5 also outputs 10... // ... all shooters show 10 instead of their 0, 1, 2, 3... ``` -Why all shooters show the same? Fix the code so that they work as intended. +Why do all of the shooters show the same value? Fix the code so that they work as intended. diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg similarity index 99% rename from 1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg rename to 1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg index e5b7f83e7..5cdf7f1a4 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-work.svg +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/lexenv-nested-work.svg @@ -1 +1 @@ -makeWorker: function name: "John"<empty>outerouterouternullname: "Pete" \ No newline at end of file +makeWorker: function name: "John"<empty>outerouterouternullname: "Pete" \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md new file mode 100644 index 000000000..0a522132f --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/solution.md @@ -0,0 +1,9 @@ +The answer is: **Pete**. + +The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference: + +![](lexenv-nested-work.svg) + +So, the result is `"Pete"` here. + +But if there were no `let name` in `makeWorker()`, then the search would go outside and take the global variable as we can see from the chain above. In that case the result would be `"John"`. diff --git a/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md new file mode 100644 index 000000000..d12a385c8 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/2-closure-variable-access/task.md @@ -0,0 +1,29 @@ +importance: 5 + +--- + +# Which variables are available? + +The function `makeWorker` below makes another function and returns it. That new function can be called from somewhere else. + +Will it have access to the outer variables from its creation place, or the invocation place, or both? + +```js +function makeWorker() { + let name = "Pete"; + + return function() { + alert(name); + }; +} + +let name = "John"; + +// create a function +let work = makeWorker(); + +// call it +work(); // what will it show? +``` + +Which value it will show? "Pete" or "John"? diff --git a/1-js/06-advanced-functions/03-closure/1-counter-independent/solution.md b/1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/1-counter-independent/solution.md rename to 1-js/06-advanced-functions/03-closure/3-counter-independent/solution.md diff --git a/1-js/06-advanced-functions/03-closure/1-counter-independent/task.md b/1-js/06-advanced-functions/03-closure/3-counter-independent/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/1-counter-independent/task.md rename to 1-js/06-advanced-functions/03-closure/3-counter-independent/task.md diff --git a/1-js/06-advanced-functions/03-closure/2-counter-object-independent/solution.md b/1-js/06-advanced-functions/03-closure/4-counter-object-independent/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/2-counter-object-independent/solution.md rename to 1-js/06-advanced-functions/03-closure/4-counter-object-independent/solution.md diff --git a/1-js/06-advanced-functions/03-closure/2-counter-object-independent/task.md b/1-js/06-advanced-functions/03-closure/4-counter-object-independent/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/2-counter-object-independent/task.md rename to 1-js/06-advanced-functions/03-closure/4-counter-object-independent/task.md diff --git a/1-js/06-advanced-functions/03-closure/3-function-in-if/solution.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/3-function-in-if/solution.md rename to 1-js/06-advanced-functions/03-closure/5-function-in-if/solution.md diff --git a/1-js/06-advanced-functions/03-closure/3-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/3-function-in-if/task.md rename to 1-js/06-advanced-functions/03-closure/5-function-in-if/task.md diff --git a/1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md b/1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md rename to 1-js/06-advanced-functions/03-closure/6-closure-sum/solution.md diff --git a/1-js/06-advanced-functions/03-closure/4-closure-sum/task.md b/1-js/06-advanced-functions/03-closure/6-closure-sum/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/4-closure-sum/task.md rename to 1-js/06-advanced-functions/03-closure/6-closure-sum/task.md diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md new file mode 100644 index 000000000..f20ac9e5d --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/solution.md @@ -0,0 +1,40 @@ +The result is: **error**. + +Try running it: + +```js run +let x = 1; + +function func() { +*!* + console.log(x); // ReferenceError: Cannot access 'x' before initialization +*/!* + let x = 2; +} + +func(); +``` + +In this example we can observe the peculiar difference between a "non-existing" and "unitialized" variable. + +As you may have read in the article [](info:closure), a variable starts in the "uninitialized" state from the moment when the execution enters a code block (or a function). And it stays uninitalized until the corresponding `let` statement. + +In other words, a variable technically exists, but can't be used before `let`. + +The code above demonstrates it. + +```js +function func() { +*!* + // the local variable x is known to the engine from the beginning of the function, + // but "unitialized" (unusable) until let ("dead zone") + // hence the error +*/!* + + console.log(x); // ReferenceError: Cannot access 'vx before initialization + + let x = 2; +} +``` + +This zone of temporary unusability of a variable (from the beginning of the code block till `let`) is sometimes called the "dead zone". diff --git a/1-js/06-advanced-functions/03-closure/7-let-scope/task.md b/1-js/06-advanced-functions/03-closure/7-let-scope/task.md new file mode 100644 index 000000000..fb7445e66 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/7-let-scope/task.md @@ -0,0 +1,21 @@ +importance: 4 + +--- + +# Is variable visible? + +What will be the result of this code? + +```js +let x = 1; + +function func() { + console.log(x); // ? + + let x = 2; +} + +func(); +``` + +P.S. There's a pitfall in this task. The solution is not obvious. diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/solution.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/solution.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/solution.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/solution.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/source.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/source.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/source.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/source.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/test.js b/1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/test.js similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/_js.view/test.js rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/_js.view/test.js diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md b/1-js/06-advanced-functions/03-closure/8-filter-through-function/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/solution.md diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/task.md b/1-js/06-advanced-functions/03-closure/8-filter-through-function/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/6-filter-through-function/task.md rename to 1-js/06-advanced-functions/03-closure/8-filter-through-function/task.md diff --git a/1-js/06-advanced-functions/03-closure/7-sort-by-field/solution.md b/1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/7-sort-by-field/solution.md rename to 1-js/06-advanced-functions/03-closure/9-sort-by-field/solution.md diff --git a/1-js/06-advanced-functions/03-closure/7-sort-by-field/task.md b/1-js/06-advanced-functions/03-closure/9-sort-by-field/task.md similarity index 100% rename from 1-js/06-advanced-functions/03-closure/7-sort-by-field/task.md rename to 1-js/06-advanced-functions/03-closure/9-sort-by-field/task.md diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index 64104541c..0f3bc8cc8 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -1,203 +1,98 @@ -# Closure +# Variable scope -JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, copied to another variable or passed as an argument to another function and called from a totally different place later. +JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, passed as an argument to another function and called from a totally different place of code later. -We know that a function can access variables outside of it, this feature is used quite often. +We already know that a function can access variables outside of it. -But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created? +Now let's expand our knowledge to include more complex scenarios. -Also, what happens when a function travels to another place in the code and is called from there -- does it get access to the outer variables of the new place? +```smart header="We'll talk about `let/const` variables here" +In JavaScript, there are 3 ways to declare a variable: `let`, `const` (the modern ones), and `var` (the remnant of the past). -Different languages behave differently here, and in this chapter we cover the behaviour of JavaScript. - -## A couple of questions - -Let's consider two situations to begin with, and then study the internal mechanics piece-by-piece, so that you'll be able to answer the following questions and more complex ones in the future. - -1. The function `sayHi` uses an external variable `name`. When the function runs, which value is it going to use? - - ```js - let name = "John"; - - function sayHi() { - alert("Hi, " + name); - } - - name = "Pete"; - - *!* - sayHi(); // what will it show: "John" or "Pete"? - */!* - ``` - - Such situations are common both in browser and server-side development. A function may be scheduled to execute later than it is created, for instance after a user action or a network request. - - So, the question is: does it pick up the latest changes? - - -2. The function `makeWorker` makes another function and returns it. That new function can be called from somewhere else. Will it have access to the outer variables from its creation place, or the invocation place, or both? - - ```js - function makeWorker() { - let name = "Pete"; - - return function() { - alert(name); - }; - } - - let name = "John"; - - // create a function - let work = makeWorker(); - - // call it - *!* - work(); // what will it show? "Pete" (name where created) or "John" (name where called)? - */!* - ``` - - -## Lexical Environment - -To understand what's going on, let's first discuss what a "variable" actually is. - -In JavaScript, every running function, code block `{...}`, and the script as a whole have an internal (hidden) associated object known as the *Lexical Environment*. - -The Lexical Environment object consists of two parts: - -1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`). -2. A reference to the *outer lexical environment*, the one associated with the outer code. - -**A "variable" is just a property of the special internal object, `Environment Record`. "To get or change a variable" means "to get or change a property of that object".** - -For instance, in this simple code, there is only one Lexical Environment: - -![lexical environment](lexical-environment-global.svg) - -This is a so-called global Lexical Environment, associated with the whole script. - -On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, so it points to `null`. - -And that's how it changes when a variable is defined and assigned: - -![lexical environment](lexical-environment-global-2.svg) - -Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution: - -1. When the script starts, the Lexical Environment is empty. -2. The `let phrase` definition appears. It has been assigned no value, so `undefined` is stored. -3. `phrase` is assigned a value. -4. `phrase` changes value. - -Everything looks simple for now, right? - -To summarize: - -- A variable is a property of a special internal object, associated with the currently executing block/function/script. -- Working with variables is actually working with the properties of that object. - -### Function Declaration - -Until now, we only observed variables. Now enter Function Declarations. - -**Unlike `let` variables, they are fully initialized not when the execution reaches them, but earlier, when a Lexical Environment is created.** - -For top-level functions, it means the moment when the script is started. - -That is why we can call a function declaration before it is defined. - -The code below demonstrates that the Lexical Environment is non-empty from the beginning. It has `say`, because that's a Function Declaration. And later it gets `phrase`, declared with `let`: - -![lexical environment](lexical-environment-global-3.svg) - - -### Inner and outer Lexical Environment - -Now let's go on and explore what happens when a function accesses an outer variable. - -During the call, `say()` uses the outer variable `phrase`. Let's look at the details of what's going on. - -When a function runs, a new Lexical Environment is created automatically to store local variables and parameters of the call. - -For instance, for `say("John")`, it looks like this (the execution is at the line, labelled with an arrow): - - - -![lexical environment](lexical-environment-simple.svg) +- In this article we'll use `let` variables in examples. +- Variables, declared with `const`, behave the same, so this article is about `const` too. +- The old `var` has some notable differences, they will be covered in the article . +``` -So, during the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global): +## Code blocks -- The inner Lexical Environment corresponds to the current execution of `say`. +If a variable is declared inside a code block `{...}`, it's only visible inside that block. - It has a single property: `name`, the function argument. We called `say("John")`, so the value of `name` is `"John"`. -- The outer Lexical Environment is the global Lexical Environment. +For example: - It has `phrase` variable and the function itself. +```js run +{ + // do some job with local variables that should not be seen outside -The inner Lexical Environment has a reference to the `outer` one. + let message = "Hello"; // only visible in this block -**When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.** + alert(message); // Hello +} -If a variable is not found anywhere, that's an error in strict mode. Without `use strict`, an assignment to a non-existing variable like `user = "John"` creates a new global variable `user`. That's for backwards compatibility. +alert(message); // Error: message is not defined +``` -Let's see how the search proceeds in our example: +We can use this to isolate a piece of code that does its own task, with variables that only belong to it: -- When the `alert` inside `say` wants to access `name`, it finds it immediately in the function Lexical Environment. -- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the reference to the enclosing Lexical Environment and finds it there. +```js run +{ + // show message + let message = "Hello"; + alert(message); +} -![lexical environment lookup](lexical-environment-simple-lookup.svg) +{ + // show another message + let message = "Goodbye"; + alert(message); +} +``` -Now we can give the answer to the first question from the beginning of the chapter. +````smart header="There'd be an error without blocks" +Please note, without separate blocks there would be an error, if we use `let` with the existing variable name: -**A function gets outer variables as they are now, it uses the most recent values.** +```js run +// show message +let message = "Hello"; +alert(message); -Old variable values are not saved anywhere. When a function wants a variable, it takes the current value from its own Lexical Environment or the outer one. +// show another message +*!* +let message = "Goodbye"; // Error: variable already declared +*/!* +alert(message); +``` +```` -So the answer to the first question is `Pete`: +For `if`, `for`, `while` and so on, variables declared in `{...}` are also only visible inside: ```js run -let name = "John"; +if (true) { + let phrase = "Hello!"; -function sayHi() { - alert("Hi, " + name); + alert(phrase); // Hello! } -name = "Pete"; // (*) - -*!* -sayHi(); // Pete -*/!* +alert(phrase); // Error, no such variable! ``` +Here, after `if` finishes, the `alert` below won't see the `phrase`, hence the error. -The execution flow of the code above: - -1. The global Lexical Environment has `name: "John"`. -2. At the line `(*)` the global variable is changed. Now it has `name: "Pete"`. -3. When the function `sayHi()` is executed it takes `name` from outside, the global Lexical Environment, where its value is already `"Pete"`. +That's great, as it allows us to create block-local variables, specific to an `if` branch. +The similar thing holds true for `for` and `while` loops: -```smart header="One call -- one Lexical Environment" -Please note that a new function Lexical Environment is created each time a function runs. - -And if a function is called multiple times, then each invocation will have its own Lexical Environment, with local variables and parameters specific for that very run. -``` +```js run +for (let i = 0; i < 3; i++) { + // the variable i is only visible inside this for + alert(i); // 0, then 1, then 2 +} -```smart header="Lexical Environment is a specification object" -"Lexical Environment" is a specification object: it only exists "theoretically" in the [language specification](https://tc39.es/ecma262/#sec-lexical-environments) to describe how things work. We can't get this object in our code and manipulate it directly. JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, as long as the visible behavior remains as described. +alert(i); // Error, no such variable ``` +Visually, `let i` is outside of `{...}`. But the `for` construct is special here: the variable, declared inside it, is considered a part of the block. ## Nested functions @@ -223,32 +118,16 @@ function sayHiBye(firstName, lastName) { Here the *nested* function `getFullName()` is made for convenience. It can access the outer variables and so can return the full name. Nested functions are quite common in JavaScript. -What's much more interesting, a nested function can be returned: either as a property of a new object (if the outer function creates an object with methods) or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables. - -For instance, here the nested function is assigned to the new object by the [constructor function](info:constructor-new): - -```js run -// constructor function returns a new object -function User(name) { - - // the object method is created as a nested function - this.sayHi = function() { - alert(name); - }; -} - -let user = new User("John"); -user.sayHi(); // the method "sayHi" code has access to the outer "name" -``` +What's much more interesting, a nested function can be returned: either as a property of a new object or as a result by itself. It can then be used somewhere else. No matter where, it still has access to the same outer variables. -And here we just create and return a "counting" function: +Below, `makeCounter` creates the "counter" function that returns the next number on each invocation: ```js run function makeCounter() { let count = 0; return function() { - return count++; // has access to the outer "count" + return count++; }; } @@ -259,314 +138,195 @@ alert( counter() ); // 1 alert( counter() ); // 2 ``` -Let's go on with the `makeCounter` example. It creates the "counter" function that returns the next number on each invocation. Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [pseudorandom number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator), and more. - -How does the counter work internally? - -When the inner function runs, the variable in `count++` is searched from inside out. For the example above, the order will be: +Despite being simple, slightly modified variants of that code have practical uses, for instance, as a [random number generator](https://en.wikipedia.org/wiki/Pseudorandom_number_generator) to generate random values for automated tests. -![](lexical-search-order.svg) +How does this work? If we create multiple counters, will they be independent? What's going on with the variables here? -1. The locals of the nested function... -2. The variables of the outer function... -3. And so on until it reaches global variables. +Undestanding such things is great for the overall knowledge of JavaScript and beneficial for more complex scenarios. So let's go a bit in-depth. -In this example `count` is found on step `2`. When an outer variable is modified, it's changed where it's found. So `count++` finds the outer variable and increases it in the Lexical Environment where it belongs. Like if we had `let count = 1`. - -Here are two questions to consider: - -1. Can we somehow reset the counter `count` from the code that doesn't belong to `makeCounter`? E.g. after `alert` calls in the example above. -2. If we call `makeCounter()` multiple times -- it returns many `counter` functions. Are they independent or do they share the same `count`? - -Try to answer them before you continue reading. - -... - -All done? - -Okay, let's go over the answers. - -1. There is no way: `count` is a local function variable, we can't access it from the outside. -2. For every call to `makeCounter()` a new function Lexical Environment is created, with its own `count`. So the resulting `counter` functions are independent. - -Here's the demo: - -```js run -function makeCounter() { - let count = 0; - return function() { - return count++; - }; -} - -let counter1 = makeCounter(); -let counter2 = makeCounter(); +## Lexical Environment -alert( counter1() ); // 0 -alert( counter1() ); // 1 +```warn header="Here be dragons!" +The in-depth technical explanation lies ahead. -alert( counter2() ); // 0 (independent) +As far as I'd like to avoid low-level language details, any understanding without them would be lacking and incomplete, so get ready. ``` +For clarity, the explanation is split into multiple steps. -Hopefully, the situation with outer variables is clear now. For most situations such understanding is enough. There are few details in the specification that we omitted for brevity. So in the next section we cover even more details. - -## Environments in detail - -Here's what's going on in the `makeCounter` example step-by-step. Follow it to make sure that you understand how it works in detail. - -Please note the additional `[[Environment]]` property is covered here. We didn't mention it before for simplicity. - -1. When the script has just started, there is only the global Lexical Environment: - - ![](lexenv-nested-makecounter-1.svg) - - At that starting moment there is only the `makeCounter` function, because it's a Function Declaration. It did not run yet. - - **All functions "on birth" receive a hidden property `[[Environment]]` with a reference to the Lexical Environment of their creation.** - - We didn't talk about it before. That's how the function knows where it was made. - - Here, `makeCounter` is created in the global Lexical Environment, so `[[Environment]]` keeps a reference to it. - - In other words, a function is "imprinted" with a reference to the Lexical Environment where it was born. And `[[Environment]]` is the hidden function property that has that reference. - -2. The code runs on, the new global variable `counter` is declared and gets the result of the `makeCounter()` call. Here's a snapshot of the moment when the execution is on the first line inside `makeCounter()`: - - ![](lexenv-nested-makecounter-2.svg) +### Step 1. Variables - At the moment of the call of `makeCounter()`, the Lexical Environment is created, to hold its variables and arguments. - - As all Lexical Environments, it stores two things: - 1. An Environment Record with local variables. In our case `count` is the only local variable (appearing when the line with `let count` is executed). - 2. The outer lexical reference, which is set to the value of `[[Environment]]` of the function. Here `[[Environment]]` of `makeCounter` references the global Lexical Environment. - - So, now we have two Lexical Environments: the first one is global, the second one is for the current `makeCounter` call, with the outer reference to global. - -3. During the execution of `makeCounter()`, a tiny nested function is created. - - It doesn't matter whether the function is created using Function Declaration or Function Expression. All functions get the `[[Environment]]` property that references the Lexical Environment in which they were made. So our new tiny nested function gets it as well. - - For our new nested function the value of `[[Environment]]` is the current Lexical Environment of `makeCounter()` (where it was born): +In JavaScript, every running function, code block `{...}`, and the script as a whole have an internal (hidden) associated object known as the *Lexical Environment*. - ![](lexenv-nested-makecounter-3.svg) +The Lexical Environment object consists of two parts: - Please note that on this step the inner function was created, but not yet called. The code inside `return count++;` is not running. +1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`). +2. A reference to the *outer lexical environment*, the one associated with the outer code. -4. As the execution goes on, the call to `makeCounter()` finishes, and the result (the tiny nested function) is assigned to the global variable `counter`: +**A "variable" is just a property of the special internal object, `Environment Record`. "To get or change a variable" means "to get or change a property of that object".** - ![](lexenv-nested-makecounter-4.svg) +In this simple code without functions, there is only one Lexical Environment: - That function has only one line: `return count++`, that will be executed when we run it. +![lexical environment](lexical-environment-global.svg) -5. When `counter()` is called, a new Lexical Environment is created for the call. It's empty, as `counter` has no local variables by itself. But the `[[Environment]]` of `counter` is used as the `outer` reference for it, that provides access to the variables of the former `makeCounter()` call where it was created: +This is the so-called *global* Lexical Environment, associated with the whole script. - ![](lexenv-nested-makecounter-5.svg) +On the picture above, the rectangle means Environment Record (variable store) and the arrow means the outer reference. The global Lexical Environment has no outer reference, that's why the arrow points to `null`. - Now when the call looks for `count` variable, it first searches its own Lexical Environment (empty), then the Lexical Environment of the outer `makeCounter()` call, where it finds it. +As the code starts executing and goes on, the Lexical Environment changes. - Please note how memory management works here. Although `makeCounter()` call finished some time ago, its Lexical Environment was retained in memory, because there's a nested function with `[[Environment]]` referencing it. +Here's a little bit longer code: - Generally, a Lexical Environment object lives as long as there is a function which may use it. And only when there are none remaining, it is cleared. +![lexical environment](closure-variable-phrase.svg) -6. The call to `counter()` not only returns the value of `count`, but also increases it. Note that the modification is done "in place". The value of `count` is modified exactly in the environment where it was found. +Rectangles on the right-hand side demonstrate how the global Lexical Environment changes during the execution: - ![](lexenv-nested-makecounter-6.svg) +1. When the script starts, the Lexical Environment is pre-populated with all declared variables. + - Initially, they are in the "Uninitialized" state. That's a special internal state, it means that the engine knows about the variable, but won't allow to use it before `let`. It's almost the same as if the variable didn't exist. +2. Then `let phrase` definition appears. There's no assignment yet, so its value is `undefined`. We can use the variable since this moment. +3. `phrase` is assigned a value. +4. `phrase` changes the value. -7. Next `counter()` invocations do the same. +Everything looks simple for now, right? -The answer to the second question from the beginning of the chapter should now be obvious. +- A variable is a property of a special internal object, associated with the currently executing block/function/script. +- Working with variables is actually working with the properties of that object. -The `work()` function in the code below gets `name` from the place of its origin through the outer lexical environment reference: +```smart header="Lexical Environment is a specification object" +"Lexical Environment" is a specification object: it only exists "theoretically" in the [language specification](https://tc39.es/ecma262/#sec-lexical-environments) to describe how things work. We can't get this object in our code and manipulate it directly. -![](lexenv-nested-work.svg) +JavaScript engines also may optimize it, discard variables that are unused to save memory and perform other internal tricks, as long as the visible behavior remains as described. +``` -So, the result is `"Pete"` here. +### Step 2. Function Declarations -But if there were no `let name` in `makeWorker()`, then the search would go outside and take the global variable as we can see from the chain above. In that case it would be `"John"`. +A function is also a value, like a variable. -```smart header="Closures" -There is a general programming term "closure", that developers generally should know. +**The difference is that a Function Declaration is instantly fully initialized.** -A [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exclusion, to be covered in ). +When a Lexical Environment is created, a Function Declaration immediately becomes a ready-to-use function (unlike `let`, that is unusable till the declaration). -That is: they automatically remember where they were created using a hidden `[[Environment]]` property, and all of them can access outer variables. +That's why we can use a function, declared as Function Declaration, even before the declaration itself. -When on an interview, a frontend developer gets a question about "what's a closure?", a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the `[[Environment]]` property and how Lexical Environments work. -``` +For example, here's the initial state of the global Lexical Environment when we add a function: -## Code blocks and loops, IIFE +![](closure-function-declaration.svg) -The examples above concentrated on functions. But a Lexical Environment exists for any code block `{...}`. +Naturally, this behavior only applies to Function Declarations, not Function Expressions where we assign a function to a variable, such as `let say = function(name)...`. -A Lexical Environment is created when a code block runs and contains block-local variables. Here are a couple of examples. +### Step 3. Inner and outer Lexical Environment -### If +When a function runs, at the beginning of the call, a new Lexical Environment is created automatically to store local variables and parameters of the call. -In the example below, the `user` variable exists only in the `if` block: +For instance, for `say("John")`, it looks like this (the execution is at the line, labelled with an arrow): -![](lexenv-if.svg) - -When the execution gets into the `if` block, the new "if-only" Lexical Environment is created for it. - -It has the reference to the outer one, so `phrase` can be found. But all variables and Function Expressions, declared inside `if`, reside in that Lexical Environment and can't be seen from the outside. - -For instance, after `if` finishes, the `alert` below won't see the `user`, hence the error. +![](lexical-environment-simple.svg) -### For, while +During the function call we have two Lexical Environments: the inner one (for the function call) and the outer one (global): -For a loop, every iteration has a separate Lexical Environment. If a variable is declared in `for(let ...)`, then it's also in there: +- The inner Lexical Environment corresponds to the current execution of `say`. It has a single property: `name`, the function argument. We called `say("John")`, so the value of the `name` is `"John"`. +- The outer Lexical Environment is the global Lexical Environment. It has the `phrase` variable and the function itself. -```js run -for (let i = 0; i < 10; i++) { - // Each loop has its own Lexical Environment - // {i: value} -} +The inner Lexical Environment has a reference to the `outer` one. -alert(i); // Error, no such variable -``` +**When the code wants to access a variable -- the inner Lexical Environment is searched first, then the outer one, then the more outer one and so on until the global one.** -Please note: `let i` is visually outside of `{...}`. The `for` construct is special here: each iteration of the loop has its own Lexical Environment with the current `i` in it. +If a variable is not found anywhere, that's an error in strict mode (without `use strict`, an assignment to a non-existing variable creates a new global variable, for compatibility with old code). -Again, similarly to `if`, after the loop `i` is not visible. +In this example the search proceeds as follows: -### Code blocks +- For the `name` variable, the `alert` inside `say` finds it immediately in the inner Lexical Environment. +- When it wants to access `phrase`, then there is no `phrase` locally, so it follows the reference to the outer Lexical Environment and finds it there. -We also can use a "bare" code block `{…}` to isolate variables into a "local scope". +![lexical environment lookup](lexical-environment-simple-lookup.svg) -For instance, in a web browser all scripts (except with `type="module"`) share the same global area. So if we create a global variable in one script, it becomes available to others. But that becomes a source of conflicts if two scripts use the same variable name and overwrite each other. -That may happen if the variable name is a widespread word, and script authors are unaware of each other. +### Step 4. Returning a function -If we'd like to avoid that, we can use a code block to isolate the whole script or a part of it: +Let's return to the `makeCounter` example. -```js run -{ - // do some job with local variables that should not be seen outside - - let message = "Hello"; +```js +function makeCounter() { + let count = 0; - alert(message); // Hello + return function() { + return count++; + }; } -alert(message); // Error: message is not defined +let counter = makeCounter(); ``` -The code outside of the block (or inside another script) doesn't see variables inside the block, because the block has its own Lexical Environment. +At the beginning of each `makeCounter()` call, a new Lexical Environment object is created, to store variables for this `makeCounter` run. -### IIFE +So we have two nested Lexical Environments, just like in the example above: -In the past, there were no block-level lexical environments in JavaScript. +![](closure-makecounter.svg) -So programmers had to invent something. And what they did was called "immediately-invoked function expressions" (abbreviated as IIFE). +What's different is that, during the execution of `makeCounter()`, a tiny nested function is created of only one line: `return count++`. We don't run it yet, only create. -That's not a thing we should use nowadays, but you can find them in old scripts, so it's better to understand them. +All functions remember the Lexical Environment in which they were made. Technically, there's no magic here: all functions have the hidden property named `[[Environment]]`, that keeps the reference to the Lexical Environment where the function was created: -An IIFE looks like this: +![](closure-makecounter-environment.svg) -```js run -(function() { +So, `counter.[[Environment]]` has the reference to `{count: 0}` Lexical Environment. That's how the function remembers where it was created, no matter where it's called. The `[[Environment]]` reference is set once and forever at function creation time. - let message = "Hello"; +Later, when `counter()` is called, a new Lexical Environment is created for the call, and its outer Lexical Environment reference is taken from `counter.[[Environment]]`: - alert(message); // Hello +![](closure-makecounter-nested-call.svg) -})(); -``` +Now when the code inside `counter()` looks for `count` variable, it first searches its own Lexical Environment (empty, as there are no local variables there), then the Lexical Environment of the outer `makeCounter()` call, where finds it and changes. -Here a Function Expression is created and immediately called. So the code executes right away and has its own private variables. +**A variable is updated in the Lexical Environment where it lives.** -The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error: +Here's the state after the execution: -```js run -// Try to declare and immediately call a function -function() { // <-- Error: Unexpected token ( +![](closure-makecounter-nested-call-2.svg) - let message = "Hello"; +If we call `counter()` multiple times, the `count` variable will be increased to `2`, `3` and so on, at the same place. - alert(message); // Hello - -}(); -``` - -Even if we say: "okay, let's add a name", that won't work, as JavaScript does not allow Function Declarations to be called immediately: - -```js run -// syntax error because of parentheses below -function go() { - -}(); // <-- can't call Function Declaration immediately -``` - -So, the parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression: it needs no name and can be called immediately. - -There exist other ways besides parentheses to tell JavaScript that we mean a Function Expression: - -```js run -// Ways to create IIFE - -(function() { - alert("Parentheses around the function"); -}*!*)*/!*(); +```smart header="Closure" +There is a general programming term "closure", that developers generally should know. -(function() { - alert("Parentheses around the whole thing"); -}()*!*)*/!*; +A [closure](https://en.wikipedia.org/wiki/Closure_(computer_programming)) is a function that remembers its outer variables and can access them. In some languages, that's not possible, or a function should be written in a special way to make it happen. But as explained above, in JavaScript, all functions are naturally closures (there is only one exception, to be covered in ). -*!*!*/!*function() { - alert("Bitwise NOT operator starts the expression"); -}(); +That is: they automatically remember where they were created using a hidden `[[Environment]]` property, and then their code can access outer variables. -*!*+*/!*function() { - alert("Unary plus starts the expression"); -}(); +When on an interview, a frontend developer gets a question about "what's a closure?", a valid answer would be a definition of the closure and an explanation that all functions in JavaScript are closures, and maybe a few more words about technical details: the `[[Environment]]` property and how Lexical Environments work. ``` -In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code. - ## Garbage collection -Usually, a Lexical Environment is cleaned up and deleted after the function runs. For instance: +Usually, a Lexical Environment is removed from memory with all the variables after the function call finishes. That's because there are no references to it. As any JavaScript object, it's only kept in memory while it's reachable. -```js -function f() { - let value1 = 123; - let value2 = 456; -} - -f(); -``` +...But if there's a nested function that is still reachable after the end of a function, then it has `[[Environment]]` property that references the lexical environment. -Here, two values are technically the properties of the Lexical Environment. But after `f()` finishes, that Lexical Environment becomes unreachable, so it's deleted from the memory. +In that case the Lexical Environment is still reachable even after the completion of the function, so it stays alive. -...But if there's a nested function that is still reachable after the end of `f`, then it has `[[Environment]]` property that references the outer lexical environment, so it's also reachable and alive: +For example: ```js function f() { let value = 123; - function g() { alert(value); } - -*!* - return g; -*/!* + return function() { + alert(value); + } } -let func = f(); // func gets a reference to g -// so it stays and memory and its outer lexical environment stays as well +let g = f(); // g.[[Environment]] stores a reference to the Lexical Environment +// of the corresponding f() call ``` Please note that if `f()` is called many times, and resulting functions are saved, then all corresponding Lexical Environment objects will also be retained in memory. All 3 of them in the code below: @@ -585,20 +345,20 @@ let arr = [f(), f(), f()]; A Lexical Environment object dies when it becomes unreachable (just like any other object). In other words, it exists only while there's at least one nested function referencing it. -In the code below, after `g` becomes unreachable, its enclosing Lexical Environment (and hence the `value`) is cleaned from memory; +In the code below, after the nested function is removed, its enclosing Lexical Environment (and hence the `value`) is cleaned from memory; ```js function f() { let value = 123; - function g() { alert(value); } - - return g; + return function() { + alert(value); + } } -let func = f(); // while func has a reference to g, it stays in memory +let g = f(); // while g function exists, the value stays in memory -func = null; // ...and now the memory is cleaned up +g = null; // ...and now the memory is cleaned up ``` ### Real-life optimizations @@ -649,9 +409,6 @@ let g = f(); g(); ``` -```warn header="See ya!" This feature of V8 is good to know. If you are debugging with Chrome/Opera, sooner or later you will meet it. -That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. -You always can check for it by running the examples on this page. -``` +That is not a bug in the debugger, but rather a special feature of V8. Perhaps it will be changed sometime. You always can check for it by running the examples on this page. diff --git a/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg b/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg new file mode 100644 index 000000000..97f76e569 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-function-declaration.svg @@ -0,0 +1 @@ +outernullexecution startphrase: <uninitialized> say: function... \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg new file mode 100644 index 000000000..b9060bc8a --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-environment.svg @@ -0,0 +1 @@ +null[[Environment]]makeCounter: function counter: undefinedcount: 0outerouter \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg new file mode 100644 index 000000000..3e4206ca6 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call-2.svg @@ -0,0 +1 @@ +count: 1<empty>nullouterouteroutermakeCounter: function counter: functionmodified here \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg new file mode 100644 index 000000000..e1bb8cc8f --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter-nested-call.svg @@ -0,0 +1 @@ +count: 0<empty>nullouterouteroutermakeCounter: function counter: function \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-makecounter.svg b/1-js/06-advanced-functions/03-closure/closure-makecounter.svg new file mode 100644 index 000000000..2a1c4a729 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-makecounter.svg @@ -0,0 +1 @@ +makeCounter: function counter: undefinedcount: 0nullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() callouterouter \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg b/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg new file mode 100644 index 000000000..741c05448 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/closure-variable-phrase.svg @@ -0,0 +1 @@ +phrase: "Bye"phrase: "Hello"phrase: undefinedphrase: <uninitialized>outernullexecution start \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-if.svg b/1-js/06-advanced-functions/03-closure/lexenv-if.svg index 48dd7d1bf..b644fe154 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-if.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-if.svg @@ -1 +1 @@ -phrase: "Hello"outerouternulluser: "John" \ No newline at end of file +phrase: "Hello"outerouternulluser: "John" \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg index f70fb41f0..66e5200f8 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-2.svg @@ -1 +1 @@ -makeCounter: functioncounter: undefinedcount: 0outerouternullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() call \ No newline at end of file +makeCounter: functioncounter: undefinedcount: 0outerouternullglobal LexicalEnvironmentLexicalEnvironment of makeCounter() call \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg index 9a3298ca3..28c526c4f 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-3.svg @@ -1 +1 @@ -makeCounter: functioncounter: undefinedcount: 0outerouternull[[Environment]] \ No newline at end of file +makeCounter: functioncounter: undefinedcount: 0outerouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg index 02d8a93fb..acc1e8fb9 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-4.svg @@ -1 +1 @@ -makeCounter: functioncounter: functioncount: 0outerouternull[[Environment]] \ No newline at end of file +makeCounter: functioncounter: functioncount: 0outerouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg index 6c8f66495..cf91c331d 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-5.svg @@ -1 +1 @@ -makeCounter: functioncounter: functioncount: 0<empty>outerouterouternull[[Environment]] \ No newline at end of file +makeCounter: functioncounter: functioncount: 0<empty>outerouterouternull[[Environment]] \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg index 3b0ac9d0c..def542ceb 100644 --- a/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg +++ b/1-js/06-advanced-functions/03-closure/lexenv-nested-makecounter-6.svg @@ -1 +1 @@ -makeCounter: functioncounter: functioncount: 1outerouternull[[Environment]]modified here \ No newline at end of file +makeCounter: functioncounter: functioncount: 1outerouternull[[Environment]]modified here \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg index aac8c5927..9620f0485 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-global.svg @@ -1 +1 @@ -phrase: "Hello"outernullLexicalEnvironment \ No newline at end of file +phrase: "Hello"outernullLexical Environment \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg index 40e2b92ad..ff0486ede 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-simple-lookup.svg @@ -1 +1 @@ -say: function phrase: "Hello"name: "John"outerouternull \ No newline at end of file +say: function phrase: "Hello"name: "John"outerouternull \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg b/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg index a962f0e5f..abd77fff9 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-environment-simple.svg @@ -1 +1 @@ -say: function phrase: "Hello"name: "John"outerouternullLexicalEnvironment for the call \ No newline at end of file +say: function phrase: "Hello"name: "John"outerouternullLexical Environment of the call \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/lexical-search-order.svg b/1-js/06-advanced-functions/03-closure/lexical-search-order.svg index 44db4b0c1..89a9d110a 100644 --- a/1-js/06-advanced-functions/03-closure/lexical-search-order.svg +++ b/1-js/06-advanced-functions/03-closure/lexical-search-order.svg @@ -1 +1 @@ -123 \ No newline at end of file +123 \ No newline at end of file diff --git a/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg b/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg new file mode 100644 index 000000000..674437196 --- /dev/null +++ b/1-js/06-advanced-functions/03-closure/variable-scope-lookup.svg @@ -0,0 +1 @@ +functionUser(name){this.sayHi=function(){alert(name);};}letuser=newUser("John");user.sayHi(); \ No newline at end of file diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 02fd43fe5..ab8068f8b 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -1,6 +1,12 @@ # The old "var" +```smart header="This article is for understanding old scripts" +The information in this article is useful for understanding old scripts. + +That's not how we write a new code. +``` + In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration: 1. `let` @@ -188,6 +194,74 @@ Because all `var` declarations are processed at the function start, we can refer In both examples above `alert` runs without an error, because the variable `phrase` exists. But its value is not yet assigned, so it shows `undefined`. +### IIFE + +As in the past there was only `var`, and it has no block-level visibility, programmers invented a way to emulate it. What they did was called "immediately-invoked function expressions" (abbreviated as IIFE). + +That's not something we should use nowadays, but you can find them in old scripts. + +An IIFE looks like this: + +```js run +(function() { + + let message = "Hello"; + + alert(message); // Hello + +})(); +``` + +Here a Function Expression is created and immediately called. So the code executes right away and has its own private variables. + +The Function Expression is wrapped with parenthesis `(function {...})`, because when JavaScript meets `"function"` in the main code flow, it understands it as the start of a Function Declaration. But a Function Declaration must have a name, so this kind of code will give an error: + +```js run +// Try to declare and immediately call a function +function() { // <-- Error: Unexpected token ( + + let message = "Hello"; + + alert(message); // Hello + +}(); +``` + +Even if we say: "okay, let's add a name", that won't work, as JavaScript does not allow Function Declarations to be called immediately: + +```js run +// syntax error because of parentheses below +function go() { + +}(); // <-- can't call Function Declaration immediately +``` + +So, the parentheses around the function is a trick to show JavaScript that the function is created in the context of another expression, and hence it's a Function Expression: it needs no name and can be called immediately. + +There exist other ways besides parentheses to tell JavaScript that we mean a Function Expression: + +```js run +// Ways to create IIFE + +(function() { + alert("Parentheses around the function"); +}*!*)*/!*(); + +(function() { + alert("Parentheses around the whole thing"); +}()*!*)*/!*; + +*!*!*/!*function() { + alert("Bitwise NOT operator starts the expression"); +}(); + +*!*+*/!*function() { + alert("Unary plus starts the expression"); +}(); +``` + +In all the above cases we declare a Function Expression and run it immediately. Let's note again: nowadays there's no reason to write such code. + ## Summary There are two main differences of `var` compared to `let/const`: @@ -195,6 +269,6 @@ There are two main differences of `var` compared to `let/const`: 1. `var` variables have no block scope, they are visible minimum at the function level. 2. `var` declarations are processed at function start (script start for globals). -There's one more minor difference related to the global object, we'll cover that in the next chapter. +There's one more very minor difference related to the global object, that we'll cover in the next chapter. These differences make `var` worse than `let` most of the time. Block-level variables is such a great thing. That's why `let` was introduced in the standard long ago, and is now a major way (along with `const`) to declare a variable. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index 373500e13..f9bc89169 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 @@ -149,8 +149,8 @@ let user = { name: "John" }; let admin = { name: "Admin" }; // use call to pass different objects as "this" -sayHi.call( user ); // this = John -sayHi.call( admin ); // this = Admin +sayHi.call( user ); // John +sayHi.call( admin ); // Admin ``` And here we use `call` to call `say` with the given context and phrase: @@ -299,13 +299,13 @@ The only syntax difference between `call` and `apply` is that `call` expects a l So these two calls are almost equivalent: ```js -func.call(context, ...args); // pass an array as list with spread operator +func.call(context, ...args); // pass an array as list with spread syntax 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 spread syntax `...` 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. diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 16a50942d..787c7d68e 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -279,7 +279,7 @@ What if we'd like to fix some arguments, but not the context `this`? For example The native `bind` does not allow that. We can't just omit the context and jump to arguments. -Fortunately, a helper function `partial` for binding only arguments can be easily implemented. +Fortunately, a function `partial` for binding only arguments can be easily implemented. Like this: @@ -313,7 +313,7 @@ The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that call - Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`) - Then gives it `...args` -- arguments given to the wrapper (`"Hello"`) -So easy to do it with the spread operator, right? +So easy to do it with the spread syntax, right? Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library. diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index e894f0662..3593bffae 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -66,7 +66,7 @@ Object.defineProperty(obj, propertyName, descriptor) : The object and its property to apply the descriptor. `descriptor` -: Property descriptor to apply. +: Property descriptor object to apply. If the property exists, `defineProperty` updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed `false`. diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md index 726529c5b..889994cf3 100644 --- a/1-js/07-object-properties/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -94,11 +94,7 @@ alert(user.name); // Alice alert(user.surname); // Cooper ``` -As the result, we have a "virtual" property `fullName`. It is readable and writable, but in fact does not exist. - -```smart header="No way to handle `delete`" -There's no similar method to handle deletion of an accessor property. Only getter/setter methods may exist. -``` +As the result, we have a "virtual" property `fullName`. It is readable and writable. ## Accessor descriptors @@ -138,7 +134,7 @@ alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ``` -Please note once again that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both. +Please note that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both. If we try to supply both `get` and `value` in the same descriptor, there will be an error: diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 69e7c5f5c..e04e09434 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -16,7 +16,7 @@ The prototype is a little bit "magical". When we want to read a property from `o The property `[[Prototype]]` is internal and hidden, but there are many ways to set it. -One of them is to use `__proto__`, like this: +One of them is to use the special name `__proto__`, like this: ```js run let animal = { diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg index caa5ec04b..36cc81cd9 100644 --- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg @@ -1 +1 @@ -toString: function other object methodsObject.prototypenullslice: function other array methods[[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]]Array.prototypecall: function other function methodsFunction.prototypetoFixed: function other number methodsNumber.prototype[1, 2, 3]function f(args) { ... }5 \ No newline at end of file +toString: function other object methodsObject.prototypenullslice: function other array methods[[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]]Array.prototypecall: function other function methodsFunction.prototypetoFixed: function other number methodsNumber.prototype[1, 2, 3]function f(args) { ... }5 \ No newline at end of file diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index 83ee2c4fe..3d35cfb0d 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -264,7 +264,7 @@ class User { let user = new User("John"); alert(user.name); // John -user = new User(""); // Name too short. +user = new User(""); // Name is too short. ``` The class declaration creates getters and setters in `User.prototype`, like this: diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg index 2f994f7b7..3471904ab 100644 --- a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg +++ b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg @@ -1 +1 @@ -constructor: Animal run: function stop: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitnew Rabbit[[Prototype]][[Prototype]]prototypeprototypename: "White Rabbit"constructorconstructorextends \ No newline at end of file +constructor: Animal run: function stop: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitnew Rabbit[[Prototype]][[Prototype]]prototypeprototypename: "White Rabbit"constructorconstructorextends \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 1000fdd84..f031b4163 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -499,7 +499,7 @@ In the example below a non-method syntax is used for comparison. `[[HomeObject]] ```js run let animal = { - eat: function() { // intentially writing like this instead of eat() {... + eat: function() { // intentionally writing like this instead of eat() {... // ... } }; diff --git a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg index 8ad09f484..f13d441c9 100644 --- a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg +++ b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg @@ -1 +1 @@ -sayHiplantsayHitreesayHianimalrabbit[[HomeObject]]sayHi \ No newline at end of file +sayHiplantsayHitreesayHianimalrabbit[[HomeObject]]sayHi \ No newline at end of file diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index dd09a0262..ab08f2ded 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -20,7 +20,7 @@ User.staticMethod(); // true That actually does the same as assigning it as a property directly: ```js run -class User() { } +class User { } User.staticMethod = function() { alert(this === User); diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index 6d77e8ed7..60ed0ef1b 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 @@ -279,7 +279,7 @@ With private fields that's impossible: `this['#name']` doesn't work. That's a sy ## Summary -In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)"). +In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)). It gives the following benefits: diff --git a/1-js/09-classes/06-instanceof/instanceof.svg b/1-js/09-classes/06-instanceof/instanceof.svg index 8b63f2070..78bff9f12 100644 --- a/1-js/09-classes/06-instanceof/instanceof.svg +++ b/1-js/09-classes/06-instanceof/instanceof.svg @@ -1 +1 @@ -Animal.prototypeObject.prototypeRabbit.prototype[[Prototype]]rabbit[[Prototype]][[Prototype]]null[[Prototype]]= Animal.prototype? \ No newline at end of file +Animal.prototypeObject.prototypeRabbit.prototype[[Prototype]]rabbit[[Prototype]][[Prototype]]null[[Prototype]]= Animal.prototype? \ No newline at end of file diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index 09b1ee398..162fc33a0 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -4,7 +4,7 @@ No matter how great we are at programming, sometimes our scripts have errors. Th Usually, a script "dies" (immediately stops) in case of an error, printing it to console. -But there's a syntax construct `try..catch` that allows to "catch" errors and, instead of dying, do something more reasonable. +But there's a syntax construct `try..catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. ## The "try..catch" syntax @@ -26,13 +26,13 @@ It works like this: 1. First, the code in `try {...}` is executed. 2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. -3. If an error occurs, then `try` execution is stopped, and the control flows to the beginning of `catch(err)`. The `err` variable (can use any name for it) will contain an error object with details about what happened. +3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch(err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. ![](try-catch-flow.svg) -So, an error inside the `try {…}` block does not kill the script: we have a chance to handle it in `catch`. +So, an error inside the `try {…}` block does not kill the script -- we have a chance to handle it in `catch`. -Let's see examples. +Let's look at some examples. - An errorless example: shows `alert` `(1)` and `(2)`: @@ -87,7 +87,7 @@ try { The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. -So, `try..catch` can only handle errors that occur in the valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". +So, `try..catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". ```` @@ -298,7 +298,7 @@ try { *!* alert(e.name); // SyntaxError */!* - alert(e.message); // Unexpected token o in JSON at position 2 + alert(e.message); // Unexpected token b in JSON at position 2 } ``` diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg index ab17dcda0..ac816e356 100644 --- a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg +++ b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg @@ -1 +1 @@ -BeginNo ErrorsAn error occured in the codeIgnore catch blockIgnore the rest of tryExecute catch blocktry { }// code... \ No newline at end of file +BeginNo ErrorsAn error occured in the codeIgnore catch blockIgnore the rest of tryExecute catch blocktry { }// code... \ No newline at end of file diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index daab93316..97adc61c0 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -2,10 +2,12 @@ # Introduction: callbacks -```warn header="We use browser methods here" -To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods; specifically, loading scripts and performing simple document manipulations. +```warn header="We use browser methods in examples here" +To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods: specifically, loading scripts and performing simple document manipulations. -If you're not familiar with these methods, and their usage in the examples is confusing, or if you would just like to understand them better, you may want to read a few chapters from the [next part](/document) of the tutorial. +If you're not familiar with these methods, and their usage in the examples is confusing, you may want to read a few chapters from the [next part](/document) of the tutorial. + +Although, we'll try to make things clear anyway. There won't be anything really complex browser-wise. ``` Many actions in JavaScript are *asynchronous*. In other words, we initiate them now, but they finish later. @@ -18,13 +20,15 @@ Take a look at the function `loadScript(src)`, that loads a script with the give ```js function loadScript(src) { + // creates a - - - + table.tBodies[0].append(...sortedRows); + diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html b/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html index e41eb229f..9071c88ee 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html +++ b/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html @@ -1,33 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - -
NameSurnameAge
JohnSmith10
PeteBrown15
AnnLee5
+ + + + + + + + + + + + + + + + + + + + +
NameSurnameAge
JohnSmith10
PeteBrown15
AnnLee5
.........
- - - - + diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/task.md b/2-ui/1-document/07-modifying-document/12-sort-table/task.md index 41d6fca29..7cdba35bc 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/task.md +++ b/2-ui/1-document/07-modifying-document/12-sort-table/task.md @@ -6,33 +6,29 @@ importance: 5 There's a table: +```html run - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + +
NameSurnameAge
JohnSmith10
PeteBrown15
AnnLee5
.........
NameSurnameAge
JohnSmith10
PeteBrown15
AnnLee5
.........
+``` There may be more rows in it. diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index b624da47c..c4796a1d4 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -540,7 +540,7 @@ So if we need to add a lot of text into HTML dynamically, and we're at page load - `"beforeend"` -- insert `html` into `elem`, at the end, - `"afterend"` -- insert `html` right after `elem`. - Also there are also similar methods, `elem.insertAdjacentText` and `elem.insertAdjacentElement`, that insert text strings and elements, but they are rarely used. + Also there are similar methods, `elem.insertAdjacentText` and `elem.insertAdjacentElement`, that insert text strings and elements, but they are rarely used. - To append HTML to the page before it has finished loading: - `document.write(html)` diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg index 1811adea7..6e1fb4874 100644 --- a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg +++ b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg @@ -1 +1 @@ -ol.afterol.appendol.prependol.before(…nodes or strings) \ No newline at end of file +ol.afterol.appendol.prependol.before(…nodes or strings) \ No newline at end of file diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent.svg b/2-ui/1-document/07-modifying-document/insert-adjacent.svg index 4b247fd72..64beee037 100644 --- a/2-ui/1-document/07-modifying-document/insert-adjacent.svg +++ b/2-ui/1-document/07-modifying-document/insert-adjacent.svg @@ -1 +1 @@ -ol.insertAdjacentHTML(*, html)afterendbeforeendafterbeginbeforebegin \ No newline at end of file +ol.insertAdjacentHTML(*, html)afterendbeforeendafterbeginbeforebegin \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg index f5562f97a..ca8bbc3bd 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index 102f9c585..024e9a4d9 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -268,7 +268,7 @@ Elements have the following geometry properties: - `offsetParent` -- is the nearest positioned ancestor or `td`, `th`, `table`, `body`. - `offsetLeft/offsetTop` -- coordinates relative to the upper-left edge of `offsetParent`. - `offsetWidth/offsetHeight` -- "outer" width/height of an element including borders. -- `clientLeft/clientTop` -- the distance from the upper-left outer corner the inner corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so `clientLeft` includes its width too. +- `clientLeft/clientTop` -- the distances from the upper-left outer corner to the upper-left inner (content + padding) corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so `clientLeft` includes its width too. - `clientWidth/clientHeight` -- the width/height of the content including paddings, but without the scrollbar. - `scrollWidth/scrollHeight` -- the width/height of the content, just like `clientWidth/clientHeight`, but also include scrolled-out, invisible part of the element. - `scrollLeft/scrollTop` -- width/height of the scrolled out upper part of the element, starting from its upper-left corner. diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.svg b/2-ui/1-document/09-size-and-scroll/metric-all.svg index 273b38528..3c77f09ac 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-all.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-all.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index 7631676d6..10898dbf7 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -40,7 +40,7 @@ In modern HTML we should always write `DOCTYPE`. ## Width/height of the document -Theoretically, as the root document element is `documentElement.clientWidth/Height`, and it encloses all the content, we could measure document full size as `documentElement.scrollWidth/scrollHeight`. +Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure document full size as `document.documentElement.scrollWidth/scrollHeight`. But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Sounds like a nonsense, weird, right? @@ -62,7 +62,7 @@ Why so? Better don't ask. These inconsistencies come from ancient times, not a " DOM elements have their current scroll state in `elem.scrollLeft/scrollTop`. -For document scroll `document.documentElement.scrollLeft/Top` works in most browsers, except oldler WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. +For document scroll `document.documentElement.scrollLeft/Top` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties `window.pageXOffset/pageYOffset`: diff --git a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html index d627bee84..56c95d5ec 100644 --- a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html +++ b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html @@ -28,8 +28,8 @@ let box = elem.getBoundingClientRect(); return { - top: box.top + pageYOffset, - left: box.left + pageXOffset + top: box.top + window.pageYOffset, + left: box.left + window.pageXOffset }; } diff --git a/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html b/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html index 7e841397b..b89db3790 100644 --- a/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html +++ b/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html @@ -26,8 +26,8 @@ let box = elem.getBoundingClientRect(); return { - top: box.top + pageYOffset, - left: box.left + pageXOffset + top: box.top + window.pageYOffset, + left: box.left + window.pageXOffset }; } diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index 939ba9141..b4b577cd0 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -75,23 +75,23 @@ Please note: ```smart header="Why derived properties are needed? Why does `top/left` exist if there's `x/y`?" Mathematically, a rectangle is uniquely defined with its starting point `(x,y)` and the direction vector `(width,height)`. So the additional derived properties are for convenience. -Technically it's possible for `width/height` to be negative, that allows for "directed" rectangle, e.g. to represent mouse selection with properly marked start and end. +Technically it's possible for `width/height` to be negative, that allows for "directed" rectangle, e.g. to represent mouse selection with properly marked start and end. + +Negative `width/height` values mean that the rectangle starts at its bottom-right corner and then "grows" left-upwards. Here's a rectangle with negative `width` and `height` (e.g. `width=-200`, `height=-100`): ![](coordinates-negative.svg) -The rectangle starts at its bottom-right corner and then spans left/up, as negative `width/height` lead it backwards by coordinates. - -As you can see, `left/top` are not `x/y` here. So these are actually not duplicates. Their formula can be adjusted to cover negative `width/height`, that's simple enough, but rarely needed, as the result of `elem.getBoundingClientRect()` always has positive width/height. +As you can see, `left/top` do not equal `x/y` in such case. -Here we mention negative `width/height` only for you to understand why these seemingly duplicate properties are not actually duplicates. +In practice though, `elem.getBoundingClientRect()` always returns positive width/height, here we mention negative `width/height` only for you to understand why these seemingly duplicate properties are not actually duplicates. ``` ```warn header="Internet Explorer and Edge: no support for `x/y`" Internet Explorer and Edge don't support `x/y` properties for historical reasons. -So we can either make a polywill (add getters in `DomRect.prototype`) or just use `top/left`, as they are always the same as `x/y` for positive `width/height`, in particular in the result of `elem.getBoundingClientRect()`. +So we can either make a polyfill (add getters in `DomRect.prototype`) or just use `top/left`, as they are always the same as `x/y` for positive `width/height`, in particular in the result of `elem.getBoundingClientRect()`. ``` ```warn header="Coordinates right/bottom are different from CSS position properties" @@ -215,8 +215,8 @@ function getCoords(elem) { let box = elem.getBoundingClientRect(); return { - top: box.top + pageYOffset, - left: box.left + pageXOffset + top: box.top + window.pageYOffset, + left: box.left + window.pageXOffset }; } ``` diff --git a/2-ui/1-document/11-coordinates/coordinates-negative.svg b/2-ui/1-document/11-coordinates/coordinates-negative.svg index 347c96d8a..3d3380110 100644 --- a/2-ui/1-document/11-coordinates/coordinates-negative.svg +++ b/2-ui/1-document/11-coordinates/coordinates-negative.svg @@ -1 +1 @@ -bottom(x,y)(x,y)leftrightIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at top(width,height) \ No newline at end of file +bottom(x,y)(x,y)leftrightIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at top(width,height) \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/coordinates.svg b/2-ui/1-document/11-coordinates/coordinates.svg index 2d789c1cf..5e0b6f185 100644 --- a/2-ui/1-document/11-coordinates/coordinates.svg +++ b/2-ui/1-document/11-coordinates/coordinates.svg @@ -1 +1 @@ -heightbottomxleftywidthrightIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at top \ No newline at end of file +heightbottomxleftywidthrightIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at top \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/document-and-window-coordinates-scrolled.svg b/2-ui/1-document/11-coordinates/document-and-window-coordinates-scrolled.svg index 204f02c85..8b4270ff0 100644 --- a/2-ui/1-document/11-coordinates/document-and-window-coordinates-scrolled.svg +++ b/2-ui/1-document/11-coordinates/document-and-window-coordinates-scrolled.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.😍pageYclientYpageXclientXIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.pageYclientYpageXclientX😍 \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.😍pageYclientYpageXclientXIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.pageYclientYpageXclientX😍 \ No newline at end of file diff --git a/2-ui/2-events/01-introduction-browser-events/article.md b/2-ui/2-events/01-introduction-browser-events/article.md index 7f160154e..c9db9dfb5 100644 --- a/2-ui/2-events/01-introduction-browser-events/article.md +++ b/2-ui/2-events/01-introduction-browser-events/article.md @@ -293,36 +293,20 @@ As we can see in the example above, we can set handlers *both* using a DOM-prope ````warn header="For some events, handlers only work with `addEventListener`" There exist events that can't be assigned via a DOM-property. Must use `addEventListener`. -For instance, the event `transitionend` (CSS animation finished) is like that. +For instance, the event `DOMContentLoaded`, that triggers when the document is loaded and DOM is built. -Try the code below. In most browsers only the second handler works, not the first one. - -```html run - - - - - +```js +document.addEventListener("DOMContentLoaded", function() { + alert("DOM built"); // this way it works +}); ``` +So `addEventListener` is more universal. Although, such events are an exception rather than the rule. ```` ## Event object diff --git a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg b/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg index 580022a9f..e3d749898 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg +++ b/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg @@ -1 +1 @@ -123Most deeply nested element \ No newline at end of file +123Most deeply nested element \ No newline at end of file diff --git a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md index 73102a92b..3001b9915 100644 --- a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md +++ b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md @@ -22,8 +22,10 @@ In this task we assume that all elements with `data-tooltip` have only text insi Details: +- The distance between the element and the tooltip should be `5px`. +- The tooltip should be centered relative to the element, if possible. - The tooltip should not cross window edges. Normally it should be above the element, but if the element is at the page top and there's no space for the tooltip, then below it. -- The tooltip is given in the `data-tooltip` attribute. It can be arbitrary HTML. +- The tooltip content is given in the `data-tooltip` attribute. It can be arbitrary HTML. You'll need two events here: - `mouseover` triggers when a pointer comes over an element. diff --git a/2-ui/2-events/03-event-delegation/article.md b/2-ui/2-events/03-event-delegation/article.md index 3d8beda0f..1fbb3f3bd 100644 --- a/2-ui/2-events/03-event-delegation/article.md +++ b/2-ui/2-events/03-event-delegation/article.md @@ -121,7 +121,7 @@ The first idea may be to assign a separate handler to each button. But there's a The handler reads the attribute and executes the method. Take a look at the working example: -```html autorun height=60 run +```html autorun height=60 run untrusted