diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index 536fac47ae..2f4f518f3e 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -35,7 +35,7 @@ The terms above are good to remember because they are used in developer articles Engines are complicated. But the basics are easy. 1. The engine (embedded if it's a browser) reads ("parses") the script. -2. Then it converts ("compiles") the script to the machine language. +2. Then it converts ("compiles") the script to machine code. 3. And then the machine code runs, pretty fast. The engine applies optimizations at each step of the process. It even watches the compiled script as it runs, analyzes the data that flows through it, and further optimizes the machine code based on that knowledge. @@ -43,7 +43,7 @@ The engine applies optimizations at each step of the process. It even watches th ## What can in-browser JavaScript do? -Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or CPU, because it was initially created for browsers which do not require it. +Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or the CPU, because it was initially created for browsers which do not require it. JavaScript's capabilities greatly depend on the environment it's running in. For instance, [Node.js](https://wikipedia.org/wiki/Node.js) supports functions that allow JavaScript to read/write arbitrary files, perform network requests, etc. @@ -59,7 +59,7 @@ For instance, in-browser JavaScript is able to: ## What CAN'T in-browser JavaScript do? -JavaScript's abilities in the browser are limited for the sake of a user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. +JavaScript's abilities in the browser are limited to protect the user's safety. The aim is to prevent an evil webpage from accessing private information or harming the user's data. Examples of such restrictions include: @@ -67,17 +67,17 @@ Examples of such restrictions include: Modern browsers allow it to work with files, but the access is limited and only provided if the user does certain actions, like "dropping" a file into a browser window or selecting it via an `` tag. - There are ways to interact with camera/microphone and other devices, but they require a user's explicit permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the [NSA](https://en.wikipedia.org/wiki/National_Security_Agency). -- Different tabs/windows generally do not know about each other. Sometimes they do, for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other if they come from different sites (from a different domain, protocol or port). + There are ways to interact with the camera/microphone and other devices, but they require a user's explicit permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the [NSA](https://en.wikipedia.org/wiki/National_Security_Agency). +- Different tabs/windows generally do not know about each other. Sometimes they do, for example when one window uses JavaScript to open the other one. But even in this case, JavaScript from one page may not access the other page if they come from different sites (from a different domain, protocol or port). - This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and contain a special JavaScript code that handles it. We'll cover that in the tutorial. + This is called the "Same Origin Policy". To work around that, *both pages* must agree for data exchange and must contain special JavaScript code that handles it. We'll cover that in the tutorial. - This limitation is, again, for the user's safety. A page from `http://anysite.com` which a user has opened must not be able to access another browser tab with the URL `http://gmail.com` and steal information from there. + This limitation is, again, for the user's safety. A page from `http://anysite.com` which a user has opened must not be able to access another browser tab with the URL `http://gmail.com`, for example, and steal information from there. - JavaScript can easily communicate over the net to the server where the current page came from. But its ability to receive data from other sites/domains is crippled. Though possible, it requires explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's a safety limitation. ![](limitations.svg) -Such limits do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow plugin/extensions which may ask for extended permissions. +Such limitations do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow plugins/extensions which may ask for extended permissions. ## What makes JavaScript unique? @@ -92,7 +92,7 @@ JavaScript is the only browser technology that combines these three things. That's what makes JavaScript unique. That's why it's the most widespread tool for creating browser interfaces. -That said, JavaScript also allows to create servers, mobile applications, etc. +That said, JavaScript can be used to create servers, mobile applications, etc. ## Languages "over" JavaScript @@ -100,20 +100,20 @@ The syntax of JavaScript does not suit everyone's needs. Different people want d That's to be expected, because projects and requirements are different for everyone. -So recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run in the browser. +So, recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run in the browser. Modern tools make the transpilation very fast and transparent, actually allowing developers to code in another language and auto-converting it "under the hood". Examples of such languages: -- [CoffeeScript](https://coffeescript.org/) is a "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it. +- [CoffeeScript](https://coffeescript.org/) is "syntactic sugar" for JavaScript. It introduces shorter syntax, allowing us to write clearer and more precise code. Usually, Ruby devs like it. - [TypeScript](https://www.typescriptlang.org/) is concentrated on adding "strict data typing" to simplify the development and support of complex systems. It is developed by Microsoft. - [Flow](https://flow.org/) also adds data typing, but in a different way. Developed by Facebook. - [Dart](https://www.dartlang.org/) is a standalone language that has its own engine that runs in non-browser environments (like mobile apps), but also can be transpiled to JavaScript. Developed by Google. - [Brython](https://brython.info/) is a Python transpiler to JavaScript that enables the writing of applications in pure Python without JavaScript. - [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) is a modern, concise and safe programming language that can target the browser or Node. -There are more. Of course, even if we use one of transpiled languages, we should also know JavaScript to really understand what we're doing. +There are more. Of course, even if we use one of these transpiled languages, we should also know JavaScript to really understand what we're doing. ## Summary diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index 5a86f2a7f1..ca61947412 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -29,11 +29,11 @@ The main difference between a "lightweight editor" and an "IDE" is that an IDE w In practice, lightweight editors may have a lot of plugins including directory-level syntax analyzers and autocompleters, so there's no strict border between a lightweight editor and an IDE. -The following options deserve your attention: +There are many options, for instance: -- [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). +- [Sublime Text](https://www.sublimetext.com/) (cross-platform, shareware). - [Notepad++](https://notepad-plus-plus.org/) (Windows, free). -- [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +- [Vim](https://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. ## Let's not argue @@ -42,3 +42,8 @@ The editors in the lists above are those that either I or my friends whom I cons There are other great editors in our big world. Please choose the one you like the most. The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. + +The author's personal opinion: + +- I'd use [Visual Studio Code](https://code.visualstudio.com/) if I develop mostly frontend. +- Otherwise, if it's mostly another language/platform and partially frontend, then consider other editors, such as XCode (Mac), Visual Studio (Windows) or Jetbrains family (Webstorm, PHPStorm, RubyMine etc, depending on the language). diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index 5fd18f90a8..f3c208a745 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -12,13 +12,14 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` -Here we have a constant `birthday` date and the `age` is calculated from `birthday` with the help of some code (it is not provided for shortness, and because details don't matter here). +Here we have a constant `birthday` for the date, and also the `age` constant. + +The `age` is calculated from `birthday` using `someCode()`, which means a function call that we didn't explain yet (we will soon!), but the details don't matter here, the point is that `age` is calculated somehow based on the `birthday`. Would it be right to use upper case for `birthday`? For `age`? Or even for both? ```js -const BIRTHDAY = '18.04.1982'; // make uppercase? +const BIRTHDAY = '18.04.1982'; // make birthday uppercase? -const AGE = someCode(BIRTHDAY); // make uppercase? +const AGE = someCode(BIRTHDAY); // make age uppercase? ``` - diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 4c2d09de34..ec6c3a1c63 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -151,11 +151,11 @@ So, we should declare a variable once and then refer to it without `let`. ```` ```smart header="Functional languages" -It's interesting to note that there exist [functional](https://en.wikipedia.org/wiki/Functional_programming) programming languages, like [Scala](http://www.scala-lang.org/) or [Erlang](http://www.erlang.org/) that forbid changing variable values. +It's interesting to note that there exist so-called [pure functional](https://en.wikipedia.org/wiki/Purely_functional_programming) programming languages, such as [Haskell](https://en.wikipedia.org/wiki/Haskell), that forbid changing variable values. In such languages, once the value is stored "in the box", it's there forever. If we need to store something else, the language forces us to create a new box (declare a new variable). We can't reuse the old one. -Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. Studying such a language (even if you're not planning to use it soon) is recommended to broaden the mind. +Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. ``` ## Variable naming [#variable-naming] @@ -198,7 +198,7 @@ Variables named `apple` and `APPLE` are two different variables. ``` ````smart header="Non-Latin letters are allowed, but not recommended" -It is possible to use any language, including cyrillic letters or even hieroglyphs, like this: +It is possible to use any language, including cyrillic letters, Chinese logograms and so on, like this: ```js let имя = '...'; diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index a697548a6f..26f3bcd53d 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -224,7 +224,7 @@ The `symbol` type is used to create unique identifiers for objects. We have to m ## The typeof operator [#type-typeof] -The `typeof` operator returns the type of the argument. It's useful when we want to process values of different types differently or just want to do a quick check. +The `typeof` operator returns the type of the operand. It's useful when we want to process values of different types differently or just want to do a quick check. A call to `typeof x` returns a string with the type name: diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md index e92e97c80f..ea8e0f8184 100644 --- a/1-js/02-first-steps/07-type-conversions/article.md +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -70,7 +70,7 @@ Numeric conversion rules: |`undefined`|`NaN`| |`null`|`0`| |true and false | `1` and `0` | -| `string` | Whitespaces from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | +| `string` | Whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | Examples: @@ -130,7 +130,7 @@ The conversion follows the rules: |`undefined`|`NaN`| |`null`|`0`| |true / false | `1 / 0` | -| `string` | The string is read "as is", whitespaces from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | +| `string` | The string is read "as is", whitespaces (includes spaces, tabs `\t`, newlines `\n` etc.) from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | **`Boolean Conversion`** -- Occurs in logical operations. Can be performed with `Boolean(value)`. diff --git a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index dfd061cb68..7370b66aff 100644 --- a/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -22,4 +22,4 @@ undefined + 1 = NaN // (6) 4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it). 5. `null` becomes `0` after the numeric conversion. 6. `undefined` becomes `NaN` after the numeric conversion. -7. Space characters, are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`. +7. Space characters are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`. diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md index fbf2cbf9a8..d52c37a172 100644 --- a/1-js/02-first-steps/08-operators/article.md +++ b/1-js/02-first-steps/08-operators/article.md @@ -50,8 +50,9 @@ The result of `a % b` is the [remainder](https://en.wikipedia.org/wiki/Remainder For instance: ```js run -alert( 5 % 2 ); // 1, a remainder of 5 divided by 2 -alert( 8 % 3 ); // 2, a remainder of 8 divided by 3 +alert( 5 % 2 ); // 1, the remainder of 5 divided by 2 +alert( 8 % 3 ); // 2, the remainder of 8 divided by 3 +alert( 8 % 4 ); // 0, the remainder of 8 divided by 4 ``` ### Exponentiation ** @@ -68,7 +69,7 @@ alert( 2 ** 3 ); // 2³ = 8 alert( 2 ** 4 ); // 2⁴ = 16 ``` -Just like in maths, the exponentiation operator is defined for non-integer numbers as well. +Just like in maths, the exponentiation operator is defined for non-integer numbers as well. For example, a square root is an exponentiation by ½: @@ -80,7 +81,7 @@ alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root) ## String concatenation with binary + -Let's meet features of JavaScript operators that are beyond school arithmetics. +Let's meet the features of JavaScript operators that are beyond school arithmetics. Usually, the plus operator `+` sums numbers. @@ -194,18 +195,18 @@ Here's an extract from the [precedence table](https://developer.mozilla.org/en-U | Precedence | Name | Sign | |------------|------|------| | ... | ... | ... | -| 15 | unary plus | `+` | -| 15 | unary negation | `-` | -| 14 | exponentiation | `**` | -| 13 | multiplication | `*` | -| 13 | division | `/` | -| 12 | addition | `+` | -| 12 | subtraction | `-` | +| 14 | unary plus | `+` | +| 14 | unary negation | `-` | +| 13 | exponentiation | `**` | +| 12 | multiplication | `*` | +| 12 | division | `/` | +| 11 | addition | `+` | +| 11 | subtraction | `-` | | ... | ... | ... | | 2 | assignment | `=` | | ... | ... | ... | -As we can see, the "unary plus" has a priority of `15` which is higher than the `12` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. +As we can see, the "unary plus" has a priority of `14` which is higher than the `11` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. ## Assignment @@ -303,9 +304,9 @@ Such operators have the same precedence as a normal assignment, so they run afte ```js run let n = 2; -n *= 3 + 5; +n *= 3 + 5; // right part evaluated first, same as n *= 8 -alert( n ); // 16 (right part evaluated first, same as n *= 8) +alert( n ); // 16 ``` ## Increment/decrement diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index 51514062f1..82e8800b9c 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -68,7 +68,7 @@ if (cond) { ## The "else" clause -The `if` statement may contain an optional "else" block. It executes when the condition is falsy. +The `if` statement may contain an optional `else` block. It executes when the condition is falsy. For example: ```js run @@ -181,9 +181,9 @@ alert( message ); It may be difficult at first to grasp what's going on. But after a closer look, we can see that it's just an ordinary sequence of tests: 1. The first question mark checks whether `age < 3`. -2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon '":"', checking `age < 18`. -3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon '":"', checking `age < 100`. -4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon '":"', returning `'What an unusual age!'`. +2. If true -- it returns `'Hi, baby!'`. Otherwise, it continues to the expression after the colon ":", checking `age < 18`. +3. If that's true -- it returns `'Hello!'`. Otherwise, it continues to the expression after the next colon ":", checking `age < 100`. +4. If that's true -- it returns `'Greetings!'`. Otherwise, it continues to the expression after the last colon ":", returning `'What an unusual age!'`. Here's how this looks using `if..else`: diff --git a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md index 2d046aa90e..b2bc64f836 100644 --- a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md +++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md @@ -29,7 +29,7 @@ For example, here we show `user` if its value isn't `null/undefined`, otherwise ```js run let user; -alert(user ?? "Anonymous"); // Anonymous (user not defined) +alert(user ?? "Anonymous"); // Anonymous (user is undefined) ``` Here's the example with `user` assigned to a name: @@ -37,7 +37,7 @@ Here's the example with `user` assigned to a name: ```js run let user = "John"; -alert(user ?? "Anonymous"); // John (user defined) +alert(user ?? "Anonymous"); // John (user is not null/undefined) ``` We can also use a sequence of `??` to select the first value from a list that isn't `null/undefined`. @@ -106,7 +106,7 @@ In practice, the zero height is often a valid value, that shouldn't be replaced ## Precedence -The precedence of the `??` operator is the same as `||`. They both equal `4` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). +The precedence of the `??` operator is the same as `||`. They both equal `3` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table). That means that, just like `||`, the nullish coalescing operator `??` is evaluated before `=` and `?`, but after most other operations, such as `+`, `*`. diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md index 79dee432d0..415fed3e0a 100644 --- a/1-js/02-first-steps/15-function-basics/article.md +++ b/1-js/02-first-steps/15-function-basics/article.md @@ -24,7 +24,7 @@ The `function` keyword goes first, then goes the *name of the function*, then a ```js function name(parameter1, parameter2, ... parameterN) { - ...body... + // body } ``` @@ -206,7 +206,13 @@ function showMessage(from, *!*text = "no text given"*/!*) { showMessage("Ann"); // Ann: no text given ``` -Now if the `text` parameter is not passed, it will get the value `"no text given"` +Now if the `text` parameter is not passed, it will get the value `"no text given"`. + +The default value also jumps in if the parameter exists, but strictly equals `undefined`, like this: + +```js +showMessage("Ann", undefined); // Ann: no text given +``` Here `"no text given"` is a string, but it can be a more complex expression, which is only evaluated and assigned if the parameter is missing. So, this is also possible: @@ -259,7 +265,7 @@ function showMessage(from, text) { ### Alternative default parameters -Sometimes it makes sense to assign default values for parameters not in the function declaration, but at a later stage. +Sometimes it makes sense to assign default values for parameters at a later stage after the function declaration. We can check if the parameter is passed during the function execution, by comparing it with `undefined`: @@ -454,7 +460,7 @@ These examples assume common meanings of prefixes. You and your team are free to ```smart header="Ultrashort function names" Functions that are used *very often* sometimes have ultrashort names. -For example, the [jQuery](http://jquery.com) framework defines a function with `$`. The [Lodash](http://lodash.com/) library has its core function named `_`. +For example, the [jQuery](https://jquery.com/) framework defines a function with `$`. The [Lodash](https://lodash.com/) library has its core function named `_`. These are exceptions. Generally function names should be concise and descriptive. ``` diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md index d502c9d27b..b952d59435 100644 --- a/1-js/02-first-steps/16-function-expressions/article.md +++ b/1-js/02-first-steps/16-function-expressions/article.md @@ -360,7 +360,7 @@ welcome(); // ok now ```smart header="When to choose Function Declaration versus Function Expression?" -As a rule of thumb, when we need to declare a function, the first to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared. +As a rule of thumb, when we need to declare a function, the first thing to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared. That's also better for readability, as it's easier to look up `function f(…) {…}` in the code than `let f = function(…) {…};`. Function Declarations are more "eye-catching". diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md index 79f1903706..4c2b1aa5e3 100644 --- a/1-js/03-code-quality/05-testing-mocha/article.md +++ b/1-js/03-code-quality/05-testing-mocha/article.md @@ -69,7 +69,7 @@ The flow of development usually looks like this: 1. An initial spec is written, with tests for the most basic functionality. 2. An initial implementation is created. -3. To check whether it works, we run the testing framework [Mocha](http://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works. +3. To check whether it works, we run the testing framework [Mocha](https://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works. 4. Now we have a working initial implementation with tests. 5. We add more use cases to the spec, probably not yet supported by the implementations. Tests start to fail. 6. Go to 3, update the implementation till tests give no errors. @@ -79,15 +79,15 @@ So, the development is *iterative*. We write the spec, implement it, make sure t Let's see this development flow in our practical case. -The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use few JavaScript libraries to run the tests, just to see that they are working (they will all fail). +The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use a few JavaScript libraries to run the tests, just to see that they are working (they will all fail). ## The spec in action Here in the tutorial we'll be using the following JavaScript libraries for tests: -- [Mocha](http://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. -- [Chai](http://chaijs.com) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. -- [Sinon](http://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. +- [Mocha](https://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests. +- [Chai](https://www.chaijs.com/) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`. +- [Sinon](https://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later. These libraries are suitable for both in-browser and server-side testing. Here we'll consider the browser variant. @@ -338,14 +338,14 @@ The newly added tests fail, because our implementation does not support them. Th ```smart header="Other assertions" Please note the assertion `assert.isNaN`: it checks for `NaN`. -There are other assertions in [Chai](http://chaijs.com) as well, for instance: +There are other assertions in [Chai](https://www.chaijs.com/) as well, for instance: - `assert.equal(value1, value2)` -- checks the equality `value1 == value2`. - `assert.strictEqual(value1, value2)` -- checks the strict equality `value1 === value2`. - `assert.notEqual`, `assert.notStrictEqual` -- inverse checks to the ones above. - `assert.isTrue(value)` -- checks that `value === true` - `assert.isFalse(value)` -- checks that `value === false` -- ...the full list is in the [docs](http://chaijs.com/api/assert/) +- ...the full list is in the [docs](https://www.chaijs.com/api/assert/) ``` So we should add a couple of lines to `pow`: diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 83a12fb103..42dccbebf3 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -1,7 +1,7 @@ # Polyfills and transpilers -The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](http://www.ecma-international.org/publications/standards/Ecma-262.htm). +The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/). Teams behind JavaScript engines have their own ideas about what to implement first. They may decide to implement proposals that are in draft and postpone things that are already in the spec, because they are less interesting or just harder to do. @@ -73,7 +73,7 @@ JavaScript is a highly dynamic language. Scripts may add/modify any function, ev Two interesting polyfill libraries are: - [core js](https://github.com/zloirock/core-js) that supports a lot, allows to include only needed features. -- [polyfill.io](http://polyfill.io) service that provides a script with polyfills, depending on the features and user's browser. +- [polyfill.io](https://polyfill.io/) service that provides a script with polyfills, depending on the features and user's browser. ## Summary diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md index 7fe8a26a6a..e80f748ab0 100644 --- a/1-js/04-object-basics/02-object-copy/article.md +++ b/1-js/04-object-basics/02-object-copy/article.md @@ -100,6 +100,30 @@ alert( a == b ); // false For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake. +````smart header="Const objects can be modified" +An important side effect of storing objects as references is that an object declared as `const` *can* be modified. + +For instance: + +```js run +const user = { + name: "John" +}; + +*!* +user.name = "Pete"; // (*) +*/!* + +alert(user.name); // Pete +``` + +It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change. + +In other words, the `const user` gives an error only if we try to set `user=...` as a whole. + +That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter . +```` + ## Cloning and merging, Object.assign [#cloning-and-merging-object-assign] So, copying an object variable creates one more reference to the same object. @@ -136,16 +160,17 @@ We can also use the method [Object.assign](https://developer.mozilla.org/en-US/d The syntax is: ```js -Object.assign(dest, [src1, src2, src3...]) +Object.assign(dest, ...sources) ``` - The first argument `dest` is a target object. -- Further arguments `src1, ..., srcN` (can be as many as needed) are source objects. -- It copies the properties of all source objects `src1, ..., srcN` into the target `dest`. In other words, properties of all arguments starting from the second are copied into the first object. -- The call returns `dest`. +- Further arguments is a list of source objects. -For instance, we can use it to merge several objects into one: -```js +It copies the properties of all source objects into the target `dest`, and then returns it as the result. + +For example, we have `user` object, let's add a couple of permissions to it: + +```js run let user = { name: "John" }; let permissions1 = { canView: true }; @@ -157,6 +182,9 @@ Object.assign(user, permissions1, permissions2); */!* // now user = { name: "John", canView: true, canEdit: true } +alert(user.name); // John +alert(user.canView); // true +alert(user.canEdit); // true ``` If the copied property name already exists, it gets overwritten: @@ -169,9 +197,9 @@ Object.assign(user, { name: "Pete" }); alert(user.name); // now user = { name: "Pete" } ``` -We also can use `Object.assign` to replace `for..in` loop for simple cloning: +We also can use `Object.assign` to perform a simple object cloning: -```js +```js run let user = { name: "John", age: 30 @@ -180,9 +208,12 @@ let user = { *!* let clone = Object.assign({}, user); */!* + +alert(clone.name); // John +alert(clone.age); // 30 ``` -It copies all properties of `user` into the empty object and returns it. +Here it copies all properties of `user` into the empty object and returns it. There are also other methods of cloning an object, e.g. using the [spread syntax](info:rest-parameters-spread) `clone = {...user}`, covered later in the tutorial. @@ -219,37 +250,71 @@ let clone = Object.assign({}, user); alert( user.sizes === clone.sizes ); // true, same object // user and clone share sizes -user.sizes.width++; // change a property from one place -alert(clone.sizes.width); // 51, get the result from the other one +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 60, get the result from the other one ``` -To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning". +To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning. -We can use recursion to implement it. Or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). -````smart header="Const objects can be modified" -An important side effect of storing objects as references is that an object declared as `const` *can* be modified. +### structuredClone -For instance: +The call `structuredClone(object)` clones the `object` with all nested properties. + +Here's how we can use it in our example: ```js run -const user = { - name: "John" +let user = { + name: "John", + sizes: { + height: 182, + width: 50 + } }; *!* -user.name = "Pete"; // (*) +let clone = structuredClone(user); */!* -alert(user.name); // Pete +alert( user.sizes === clone.sizes ); // false, different objects + +// user and clone are totally unrelated now +user.sizes.width = 60; // change a property from one place +alert(clone.sizes.width); // 50, not related ``` -It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change. +The `structuredClone` method can clone most data types, such as objects, arrays, primitive values. -In other words, the `const user` gives an error only if we try to set `user=...` as a whole. +It also supports circular references, when an object property references the object itself (directly or via a chain or references). -That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter . -```` +For instance: + +```js run +let user = {}; +// let's create a circular reference: +// user.me references the user itself +user.me = user; + +let clone = structuredClone(user); +alert(clone.me === clone); // true +``` + +As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well. + +Although, there are cases when `structuredClone` fails. + +For instance, when an object has a function property: + +```js run +// error +structuredClone({ + f: function() {} +}); +``` + +Function properties aren't supported. + +To handle such complex cases we may need to use a combination of cloning methods, write custom code or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com). ## Summary @@ -257,4 +322,4 @@ Objects are assigned and copied by reference. In other words, a variable stores All operations via copied references (like adding/removing properties) are performed on the same single object. -To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). +To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function `structuredClone` or use a custom cloning implementation, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep). diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md index 50ada67135..1b576d6293 100644 --- a/1-js/04-object-basics/03-garbage-collection/article.md +++ b/1-js/04-object-basics/03-garbage-collection/article.md @@ -205,8 +205,8 @@ Modern engines implement advanced algorithms of garbage collection. A general book "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al) covers some of them. -If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). +If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection). -The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](http://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. +The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](https://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects. In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language. diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md index d80113acc8..e932a201ad 100644 --- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md +++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md @@ -10,8 +10,8 @@ Is it possible to create functions `A` and `B` so that `new A() == new B()`? function A() { ... } function B() { ... } -let a = new A; -let b = new B; +let a = new A(); +let b = new B(); alert( a == b ); // true ``` diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md index 60e7c373e0..c862bec40d 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md @@ -6,7 +6,7 @@ importance: 5 Create a constructor function `Calculator` that creates objects with 3 methods: -- `read()` asks for two values using `prompt` and remembers them in object properties. +- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively. - `sum()` returns the sum of these properties. - `mul()` returns the multiplication product of these properties. diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md index f3e9c3ec02..a335464f1e 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -171,7 +171,7 @@ alert( new SmallUser().name ); // John Usually constructors don't have a `return` statement. Here we mention the special behavior with returning objects mainly for the sake of completeness. ````smart header="Omitting parentheses" -By the way, we can omit parentheses after `new`, if it has no arguments: +By the way, we can omit parentheses after `new`: ```js let user = new User; // <-- no parentheses diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md index acc4e78b4e..0a16b53998 100644 --- a/1-js/04-object-basics/09-object-toprimitive/article.md +++ b/1-js/04-object-basics/09-object-toprimitive/article.md @@ -136,8 +136,8 @@ As we can see from the code, `user` becomes a self-descriptive string or a money If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`: -- For the `"string"` hint: call `toString` method, and if it doesn't exist, then `valueOf` (so `toString` has the priority for string conversions). -- For other hints: `valueOf`, and if it doesn't exist, then `toString` (so `valueOf` has the priority for maths). +- For the `"string"` hint: call `toString` method, and if it doesn't exist or if it returns an object instead of a primitive value, then call `valueOf` (so `toString` has the priority for string conversions). +- For other hints: call `valueOf`, and if it doesn't exist or if it returns an object instead of a primitive value, then call `toString` (so `valueOf` has the priority for maths). Methods `toString` and `valueOf` come from ancient times. They are not symbols (symbols did not exist that long ago), but rather "regular" string-named methods. They provide an alternative "old-style" way to implement the conversion. @@ -226,7 +226,7 @@ As we know already, many operators and functions perform type conversions, e.g. If we pass an object as an argument, then there are two stages of calculations: 1. The object is converted to a primitive (using the rules described above). -2. If the necessary for further calculations, the resulting primitive is also converted. +2. If necessary for further calculations, the resulting primitive is also converted. For instance: diff --git a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md index a17a4671a1..4bcd74512a 100644 --- a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md +++ b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md @@ -28,6 +28,6 @@ Note that `63.5` has no precision loss at all. That's because the decimal part ` ```js run -alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 +alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(rounded) -> 6.4 ``` diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 3fec8848fc..c704bd980f 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -334,15 +334,44 @@ alert( isFinite(num) ); Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`. -```smart header="Compare with `Object.is`" +````smart header="`Number.isNaN` and `Number.isFinite`" +[Number.isNaN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) and [Number.isFinite](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite) methods are the more "strict" versions of `isNaN` and `isFinite` functions. They do not autoconvert their argument into a number, but check if it belongs to the `number` type instead. + +- `Number.isNaN(value)` returns `true` if the argument belongs to the `number` type and it is `NaN`. In any other case it returns `false`. + + ```js run + alert( Number.isNaN(NaN) ); // true + alert( Number.isNaN("str" / 2) ); // true + + // Note the difference: + alert( Number.isNaN("str") ); // false, because "str" belongs to the string type, not the number type + alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion + ``` + +- `Number.isFinite(value)` returns `true` if the argument belongs to the `number` type and it is not `NaN/Infinity/-Infinity`. In any other case it returns `false`. + + ```js run + alert( Number.isFinite(123) ); // true + alert( Number.isFinite(Infinity) ); // false + alert( Number.isFinite(2 / 0) ); // false + + // Note the difference: + alert( Number.isFinite("123") ); // false, because "123" belongs to the string type, not the number type + alert( isFinite("123") ); // true, because isFinite converts string "123" into a number 123 + ``` + +In a way, `Number.isNaN` and `Number.isFinite` are simpler and more straightforward than `isNaN` and `isFinite` functions. In practice though, `isNaN` and `isFinite` are mostly used, as they're shorter to write. +```` + +```smart header="Comparison with `Object.is`" There is a special built-in method `Object.is` that compares values like `===`, but is more reliable for two edge cases: 1. It works with `NaN`: `Object.is(NaN, NaN) === true`, that's a good thing. -2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's true, because internally the number has a sign bit that may be different even if all other bits are zeroes. +2. Values `0` and `-0` are different: `Object.is(0, -0) === false`, technically that's correct, because internally the number has a sign bit that may be different even if all other bits are zeroes. In all other cases, `Object.is(a, b)` is the same as `a === b`. -This way of comparison is often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). +We mention `Object.is` here, because it's often used in JavaScript specification. When an internal algorithm needs to compare two values for being exactly the same, it uses `Object.is` (internally called [SameValue](https://tc39.github.io/ecma262/#sec-samevalue)). ``` @@ -435,7 +464,9 @@ For different numeral systems: For regular number tests: - `isNaN(value)` converts its argument to a number and then tests it for being `NaN` -- `isFinite(value)` converts its argument to a number and returns `true` if it's a regular number, not `NaN/Infinity/-Infinity` +- `Number.isNaN(value)` checks whether its argument belongs to the `number` type, and if so, tests it for being `NaN` +- `isFinite(value)` converts its argument to a number and then tests it for not being `NaN/Infinity/-Infinity` +- `Number.isFinite(value)` checks whether its argument belongs to the `number` type, and if so, tests it for not being `NaN/Infinity/-Infinity` For converting values like `12pt` and `100px` to a number: diff --git a/1-js/05-data-types/03-string/1-ucfirst/solution.md b/1-js/05-data-types/03-string/1-ucfirst/solution.md index f7a332d0dc..be5dd2aafe 100644 --- a/1-js/05-data-types/03-string/1-ucfirst/solution.md +++ b/1-js/05-data-types/03-string/1-ucfirst/solution.md @@ -8,12 +8,7 @@ let newStr = str[0].toUpperCase() + str.slice(1); There's a small problem though. If `str` is empty, then `str[0]` is `undefined`, and as `undefined` doesn't have the `toUpperCase()` method, we'll get an error. -There are two variants here: - -1. Use `str.charAt(0)`, as it always returns a string (maybe empty). -2. Add a test for an empty string. - -Here's the 2nd variant: +The easiest way out is to add a test for an empty string, like this: ```js run demo function ucFirst(str) { @@ -24,4 +19,3 @@ function ucFirst(str) { alert( ucFirst("john") ); // John ``` - diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index b3535866de..60ce2b6f0b 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -50,7 +50,7 @@ let guestList = "Guests: // Error: Unexpected token ILLEGAL Single and double quotes come from ancient times of language creation, when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile. -Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`. The function `func` is called automatically, receives the string and embedded expressions and can process them. This is called "tagged templates". This feature makes it easier to implement custom templating, but is rarely used in practice. You can read more about it in the [manual](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). +Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`. The function `func` is called automatically, receives the string and embedded expressions and can process them. This feature is called "tagged templates", it's rarely seen, but you can read about it in the MDN: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates). ## Special characters @@ -59,10 +59,10 @@ It is still possible to create multiline strings with single and double quotes b ```js run let guestList = "Guests:\n * John\n * Pete\n * Mary"; -alert(guestList); // a multiline list of guests +alert(guestList); // a multiline list of guests, same as above ``` -For example, these two lines are equal, just written differently: +As a simpler example, these two lines are equal, just written differently: ```js run let str1 = "Hello\nWorld"; // two lines using a "newline symbol" @@ -74,33 +74,26 @@ World`; alert(str1 == str2); // true ``` -There are other, less common "special" characters. - -Here's the full list: +There are other, less common special characters: | Character | Description | |-----------|-------------| |`\n`|New line| |`\r`|In Windows text files a combination of two characters `\r\n` represents a new break, while on non-Windows OS it's just `\n`. That's for historical reasons, most Windows software also understands `\n`. | -|`\'`, `\"`|Quotes| +|`\'`, `\"`, \\`|Quotes| |`\\`|Backslash| |`\t`|Tab| -|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- kept for compatibility, not used nowadays. | -|`\xXX`|Unicode character with the given hexadecimal Unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.| -|`\uXXXX`|A Unicode symbol with the hex code `XXXX` in UTF-16 encoding, for instance `\u00A9` -- is a Unicode for the copyright symbol `©`. It must be exactly 4 hex digits. | -|`\u{X…XXXXXX}` (1 to 6 hex characters)|A Unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two Unicode symbols, taking 4 bytes. This way we can insert long codes. | +|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- mentioned for completeness, coming from old times, not used nowadays (you can forget them right now). | + +As you can see, all special characters start with a backslash character `\`. It is also called an "escape character". -Examples with Unicode: +Because it's so special, if we need to show an actual backslash `\` within the string, we need to double it: ```js run -alert( "\u00A9" ); // © -alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long Unicode) -alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode) +alert( `The backslash: \\` ); // The backslash: \ ``` -All special characters start with a backslash character `\`. It is also called an "escape character". - -We might also use it if we wanted to insert a quote into the string. +So-called "escaped" quotes `\'`, `\"`, \\` are used to insert a quote into the same-quoted string. For instance: @@ -113,18 +106,10 @@ As you can see, we have to prepend the inner quote by the backslash `\'`, becaus Of course, only the quotes that are the same as the enclosing ones need to be escaped. So, as a more elegant solution, we could switch to double quotes or backticks instead: ```js run -alert( `I'm the Walrus!` ); // I'm the Walrus! +alert( "I'm the Walrus!" ); // I'm the Walrus! ``` -Note that the backslash `\` serves for the correct reading of the string by JavaScript, then disappears. The in-memory string has no `\`. You can clearly see that in `alert` from the examples above. - -But what if we need to show an actual backslash `\` within the string? - -That's possible, but we need to double it like `\\`: - -```js run -alert( `The backslash: \\` ); // The backslash: \ -``` +Besides these special characters, there's also a special notation for Unicode codes `\u…`, it's rarely used and is covered in the optional chapter about [Unicode](info:unicode). ## String length @@ -139,33 +124,36 @@ Note that `\n` is a single "special" character, so the length is indeed `3`. ```warn header="`length` is a property" People with a background in some other languages sometimes mistype by calling `str.length()` instead of just `str.length`. That doesn't work. -Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. +Please note that `str.length` is a numeric property, not a function. There is no need to add parenthesis after it. Not `.length()`, but `.length`. ``` ## Accessing characters -To get a character at position `pos`, use square brackets `[pos]` or call the method [str.charAt(pos)](mdn:js/String/charAt). The first character starts from the zero position: +To get a character at position `pos`, use square brackets `[pos]` or call the method [str.at(pos)](mdn:js/String/at). The first character starts from the zero position: ```js run let str = `Hello`; // the first character alert( str[0] ); // H -alert( str.charAt(0) ); // H +alert( str.at(0) ); // H // the last character alert( str[str.length - 1] ); // o +alert( str.at(-1) ); ``` -The square brackets are a modern way of getting a character, while `charAt` exists mostly for historical reasons. +As you can see, the `.at(pos)` method has a benefit of allowing negative position. If `pos` is negative, then it's counted from the end of the string. -The only difference between them is that if no character is found, `[]` returns `undefined`, and `charAt` returns an empty string: +So `.at(-1)` means the last character, and `.at(-2)` is the one before it, etc. + +The square brackets always return `undefined` for negative indexes, for instance: ```js run let str = `Hello`; -alert( str[1000] ); // undefined -alert( str.charAt(1000) ); // '' (an empty string) +alert( str[-2] ); // undefined +alert( str.at(-2) ); // l ``` We can also iterate over characters using `for..of`: @@ -310,45 +298,6 @@ if (str.indexOf("Widget") != -1) { } ``` -#### The bitwise NOT trick - -One of the old tricks used here is the [bitwise NOT](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT) `~` operator. It converts the number to a 32-bit integer (removes the decimal part if exists) and then reverses all bits in its binary representation. - -In practice, that means a simple thing: for 32-bit integers `~n` equals `-(n+1)`. - -For instance: - -```js run -alert( ~2 ); // -3, the same as -(2+1) -alert( ~1 ); // -2, the same as -(1+1) -alert( ~0 ); // -1, the same as -(0+1) -*!* -alert( ~-1 ); // 0, the same as -(-1+1) -*/!* -``` - -As we can see, `~n` is zero only if `n == -1` (that's for any 32-bit signed integer `n`). - -So, the test `if ( ~str.indexOf("...") )` is truthy only if the result of `indexOf` is not `-1`. In other words, when there is a match. - -People use it to shorten `indexOf` checks: - -```js run -let str = "Widget"; - -if (~str.indexOf("Widget")) { - alert( 'Found it!' ); // works -} -``` - -It is usually not recommended to use language features in a non-obvious way, but this particular trick is widely used in old code, so we should understand it. - -Just remember: `if (~str.indexOf(...))` reads as "if found". - -To be precise though, as big numbers are truncated to 32 bits by `~` operator, there exist other numbers that give `0`, the smallest is `~4294967295=0`. That makes such check correct only if a string is not that long. - -Right now we can see this trick only in the old code, as modern JavaScript provides `.includes` method (see below). - ### includes, startsWith, endsWith The more modern method [str.includes(substr, pos)](mdn:js/String/includes) returns `true/false` depending on whether `str` contains `substr` within. @@ -407,9 +356,9 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and ``` `str.substring(start [, end])` -: Returns the part of the string *between* `start` and `end`. +: Returns the part of the string *between* `start` and `end` (not including `end`). - This is almost the same as `slice`, but it allows `start` to be greater than `end`. + This is almost the same as `slice`, but it allows `start` to be greater than `end` (in this case it simply swaps `start` and `end` values). For instance: @@ -445,18 +394,22 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters ``` + This method resides in the [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) of the language specification. It means that only browser-hosted Javascript engines should support it, and it's not recommended to use it. In practice, it's supported everywhere. + Let's recap these methods to avoid any confusion: | method | selects... | negatives | |--------|-----------|-----------| | `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives | -| `substring(start, end)` | between `start` and `end` | negative values mean `0` | +| `substring(start, end)` | between `start` and `end` (not including `end`)| negative values mean `0` | | `substr(start, length)` | from `start` get `length` characters | allows negative `start` | ```smart header="Which one to choose?" All of them can do the job. Formally, `substr` has a minor drawback: it is described not in the core JavaScript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere. -Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. So, it's enough to remember solely `slice` of these three methods. +Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. + +So, for practical use it's enough to remember only `slice`. ``` ## Comparing strings @@ -479,17 +432,18 @@ Although, there are some oddities. This may lead to strange results if we sort these country names. Usually people would expect `Zealand` to come after `Österreich` in the list. -To understand what happens, let's review the internal representation of strings in JavaScript. +To understand what happens, we should be aware that strings in Javascript are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. -All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. There are special methods that allow to get the character for the code and back. +There are special methods that allow to get the character for the code and back: `str.codePointAt(pos)` -: Returns the code for the character at position `pos`: +: Returns a decimal number representing the code for the character at position `pos`: ```js run // different case letters have different codes - alert( "z".codePointAt(0) ); // 122 alert( "Z".codePointAt(0) ); // 90 + alert( "z".codePointAt(0) ); // 122 + alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value) ``` `String.fromCodePoint(code)` @@ -497,13 +451,7 @@ All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). Th ```js run alert( String.fromCodePoint(90) ); // Z - ``` - - We can also add Unicode characters by their codes using `\u` followed by the hex code: - - ```js run - // 90 is 5a in hexadecimal system - alert( '\u005a' ); // Z + alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument) ``` Now let's see the characters with codes `65..220` (the latin alphabet and a little bit extra) by making a string of them: @@ -515,6 +463,7 @@ for (let i = 65; i <= 220; i++) { str += String.fromCodePoint(i); } alert( str ); +// Output: // ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„ // ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ ``` @@ -534,7 +483,7 @@ The "right" algorithm to do string comparisons is more complex than it may seem, So, the browser needs to know the language to compare. -Luckily, all modern browsers (IE10- requires the additional library [Intl.js](https://github.com/andyearnshaw/Intl.js/)) support the internationalization standard [ECMA-402](http://www.ecma-international.org/ecma-402/1.0/ECMA-402.pdf). +Luckily, modern browsers support the internationalization standard [ECMA-402](https://www.ecma-international.org/publications-and-standards/standards/ecma-402/). It provides a special method to compare strings in different languages, following their rules. @@ -552,119 +501,11 @@ alert( 'Österreich'.localeCompare('Zealand') ); // -1 This method actually has two additional arguments specified in [the documentation](mdn:js/String/localeCompare), which allows it to specify the language (by default taken from the environment, letter order depends on the language) and setup additional rules like case sensitivity or should `"a"` and `"á"` be treated as the same etc. -## Internals, Unicode - -```warn header="Advanced knowledge" -The section goes deeper into string internals. This knowledge will be useful for you if you plan to deal with emoji, rare mathematical or hieroglyphic characters or other rare symbols. - -You can skip the section if you don't plan to support them. -``` - -### Surrogate pairs - -All frequently used characters have 2-byte codes. Letters in most european languages, numbers, and even most hieroglyphs, have a 2-byte representation. - -But 2 bytes only allow 65536 combinations and that's not enough for every possible symbol. So rare symbols are encoded with a pair of 2-byte characters called "a surrogate pair". - -The length of such symbols is `2`: - -```js run -alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X -alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY -alert( '𩷶'.length ); // 2, a rare Chinese hieroglyph -``` - -Note that surrogate pairs did not exist at the time when JavaScript was created, and thus are not correctly processed by the language! - -We actually have a single symbol in each of the strings above, but the `length` shows a length of `2`. - -`String.fromCodePoint` and `str.codePointAt` are few rare methods that deal with surrogate pairs right. They recently appeared in the language. Before them, there were only [String.fromCharCode](mdn:js/String/fromCharCode) and [str.charCodeAt](mdn:js/String/charCodeAt). These methods are actually the same as `fromCodePoint/codePointAt`, but don't work with surrogate pairs. - -Getting a symbol can be tricky, because surrogate pairs are treated as two characters: - -```js run -alert( '𝒳'[0] ); // strange symbols... -alert( '𝒳'[1] ); // ...pieces of the surrogate pair -``` - -Note that pieces of the surrogate pair have no meaning without each other. So the alerts in the example above actually display garbage. - -Technically, surrogate pairs are also detectable by their codes: if a character has the code in the interval of `0xd800..0xdbff`, then it is the first part of the surrogate pair. The next character (second part) must have the code in interval `0xdc00..0xdfff`. These intervals are reserved exclusively for surrogate pairs by the standard. - -In the case above: - -```js run -// charCodeAt is not surrogate-pair aware, so it gives codes for parts - -alert( '𝒳'.charCodeAt(0).toString(16) ); // d835, between 0xd800 and 0xdbff -alert( '𝒳'.charCodeAt(1).toString(16) ); // dcb3, between 0xdc00 and 0xdfff -``` - -You will find more ways to deal with surrogate pairs later in the chapter . There are probably special libraries for that too, but nothing famous enough to suggest here. - -### Diacritical marks and normalization - -In many languages, there are symbols that are composed of the base character with a mark above/under it. - -For instance, the letter `a` can be the base character for: `àáâäãåā`. Most common "composite" character have their own code in the UTF-16 table. But not all of them, because there are too many possible combinations. - -To support arbitrary compositions, UTF-16 allows us to use several Unicode characters: the base character followed by one or many "mark" characters that "decorate" it. - -For instance, if we have `S` followed by the special "dot above" character (code `\u0307`), it is shown as Ṡ. - -```js run -alert( 'S\u0307' ); // Ṡ -``` - -If we need an additional mark above the letter (or below it) -- no problem, just add the necessary mark character. - -For instance, if we append a character "dot below" (code `\u0323`), then we'll have "S with dots above and below": `Ṩ`. - -For example: - -```js run -alert( 'S\u0307\u0323' ); // Ṩ -``` - -This provides great flexibility, but also an interesting problem: two characters may visually look the same, but be represented with different Unicode compositions. - -For instance: - -```js run -let s1 = 'S\u0307\u0323'; // Ṩ, S + dot above + dot below -let s2 = 'S\u0323\u0307'; // Ṩ, S + dot below + dot above - -alert( `s1: ${s1}, s2: ${s2}` ); - -alert( s1 == s2 ); // false though the characters look identical (?!) -``` - -To solve this, there exists a "Unicode normalization" algorithm that brings each string to the single "normal" form. - -It is implemented by [str.normalize()](mdn:js/String/normalize). - -```js run -alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true -``` - -It's funny that in our situation `normalize()` actually brings together a sequence of 3 characters to one: `\u1e68` (S with two dots). - -```js run -alert( "S\u0307\u0323".normalize().length ); // 1 - -alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true -``` - -In reality, this is not always the case. The reason being that the symbol `Ṩ` is "common enough", so UTF-16 creators included it in the main table and gave it the code. - -If you want to learn more about normalization rules and variants -- they are described in the appendix of the Unicode standard: [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/), but for most practical purposes the information from this section is enough. - ## Summary - There are 3 types of quotes. Backticks allow a string to span multiple lines and embed expressions `${…}`. -- Strings in JavaScript are encoded using UTF-16. -- We can use special characters like `\n` and insert letters by their Unicode using `\u...`. -- To get a character, use: `[]`. +- We can use special characters, such as a line break `\n`. +- To get a character, use: `[]` or `at` method. - To get a substring, use: `slice` or `substring`. - To lowercase/uppercase a string, use: `toLowerCase/toUpperCase`. - To look for a substring, use: `indexOf`, or `includes/startsWith/endsWith` for simple checks. @@ -677,3 +518,5 @@ There are several other helpful methods in strings: - ...and more to be found in the [manual](mdn:js/String). Strings also have methods for doing search/replace with regular expressions. But that's big topic, so it's explained in a separate tutorial section . + +Also, as of now it's important to know that strings are based on Unicode encoding, and hence there're issues with comparisons. There's more about Unicode in the chapter . diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md index befd802967..7e1ca3bde2 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md +++ b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md @@ -59,7 +59,7 @@ alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 The solution has a time complexity of [O(n2)](https://en.wikipedia.org/wiki/Big_O_notation). In other words, if we increase the array size 2 times, the algorithm will work 4 times longer. -For big arrays (1000, 10000 or more items) such algorithms can lead to a serious sluggishness. +For big arrays (1000, 10000 or more items) such algorithms can lead to serious sluggishness. # Fast solution @@ -91,4 +91,4 @@ alert( getMaxSubSum([-1, -2, -3]) ); // 0 The algorithm requires exactly 1 array pass, so the time complexity is O(n). -You can find more detail information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. +You can find more detailed information about the algorithm here: [Maximum subarray problem](http://en.wikipedia.org/wiki/Maximum_subarray_problem). If it's still not obvious why that works, then please trace the algorithm on the examples above, see how it works, that's better than any words. diff --git a/1-js/05-data-types/04-array/2-create-array/task.md b/1-js/05-data-types/04-array/2-create-array/task.md index 16d14071f7..d4551c79cc 100644 --- a/1-js/05-data-types/04-array/2-create-array/task.md +++ b/1-js/05-data-types/04-array/2-create-array/task.md @@ -8,7 +8,7 @@ Let's try 5 array operations. 1. Create an array `styles` with items "Jazz" and "Blues". 2. Append "Rock-n-Roll" to the end. -3. Replace the value in the middle by "Classics". Your code for finding the middle value should work for any arrays with odd length. +3. Replace the value in the middle with "Classics". Your code for finding the middle value should work for any arrays with odd length. 4. Strip off the first value of the array and show it. 5. Prepend `Rap` and `Reggae` to the array. diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md index 1040937857..4db1a16b6b 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -255,15 +255,15 @@ alert( arr.includes(1) ); // true Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero. -If we want to check if `item` exists in the array, and don't need the exact index, then `arr.includes` is preferred. +If we want to check if `item` exists in the array, and don't need the index, then `arr.includes` is preferred. The method [arr.lastIndexOf](mdn:js/Array/lastIndexOf) is the same as `indexOf`, but looks for from right to left. ```js run let fruits = ['Apple', 'Orange', 'Apple'] -alert( arr.indexOf('Apple') ); // 0 (first Apple) -alert( arr.lastIndexOf('Apple') ); // 2 (last Apple) +alert( fruits.indexOf('Apple') ); // 0 (first Apple) +alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple) ``` ````smart header="The `includes` method handles `NaN` correctly" @@ -338,8 +338,6 @@ alert(users.findIndex(user => user.name == 'John')); // 0 alert(users.findLastIndex(user => user.name == 'John')); // 3 ``` - - ### filter The `find` method looks for a single (first) element that makes the function return `true`. diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index ce9074cc78..76f74036c0 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -218,7 +218,7 @@ alert(arr.pop()); // World (method works) The same happens for an iterable: -```js +```js run // assuming that range is taken from the example above let arr = Array.from(range); alert(arr); // 1,2,3,4,5 (array toString conversion works) @@ -233,7 +233,7 @@ The optional second argument `mapFn` can be a function that will be applied to e For instance: -```js +```js run // assuming that range is taken from the example above // square each number diff --git a/1-js/05-data-types/07-map-set/article.md b/1-js/05-data-types/07-map-set/article.md index bd6cad5629..37f5e48c2d 100644 --- a/1-js/05-data-types/07-map-set/article.md +++ b/1-js/05-data-types/07-map-set/article.md @@ -10,17 +10,17 @@ But that's not enough for real life. That's why `Map` and `Set` also exist. ## Map -[Map](mdn:js/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. +[Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) is a collection of keyed data items, just like an `Object`. But the main difference is that `Map` allows keys of any type. Methods and properties are: -- `new Map()` -- creates the map. -- `map.set(key, value)` -- stores the value by the key. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +- [`new Map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element (the key/value pair) by the key. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. For instance: @@ -100,14 +100,13 @@ map.set('1', 'str1') ``` ```` - ## Iteration over Map For looping over a `map`, there are 3 methods: -- `map.keys()` -- returns an iterable for keys, -- `map.values()` -- returns an iterable for values, -- `map.entries()` -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. +- [`map.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) -- returns an iterable for keys, +- [`map.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) -- returns an iterable for values, +- [`map.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) -- returns an iterable for entries `[key, value]`, it's used by default in `for..of`. For instance: @@ -162,7 +161,7 @@ let map = new Map([ alert( map.get('1') ); // str1 ``` -If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](mdn:js/Object/entries) that returns an array of key/value pairs for an object exactly in that format. +If we have a plain object, and we'd like to create a `Map` from it, then we can use built-in method [Object.entries(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) that returns an array of key/value pairs for an object exactly in that format. So we can create a map from an object like this: @@ -233,16 +232,16 @@ That's the same, because `Object.fromEntries` expects an iterable object as the ## Set -A `Set` is a special type collection - "set of values" (without keys), where each value may occur only once. +A [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) is a special type collection - "set of values" (without keys), where each value may occur only once. Its main methods are: -- `new Set(iterable)` -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. -- `set.add(value)` -- adds a value, returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value, returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. The main feature is that repeated calls of `set.add(value)` with the same value don't do anything. That's the reason why each value appears in a `Set` only once. @@ -272,7 +271,7 @@ for (let user of set) { } ``` -The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](mdn:js/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. +The alternative to `Set` could be an array of users, and the code to check for duplicates on every insertion using [arr.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find). But the performance would be much worse, because this method walks through the whole array checking every element. `Set` is much better optimized internally for uniqueness checks. ## Iteration over Set @@ -291,42 +290,42 @@ set.forEach((value, valueAgain, set) => { Note the funny thing. The callback function passed in `forEach` has 3 arguments: a `value`, then *the same value* `valueAgain`, and then the target object. Indeed, the same value appears in the arguments twice. -That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But may help to replace `Map` with `Set` in certain cases with ease, and vice versa. +That's for compatibility with `Map` where the callback passed `forEach` has three arguments. Looks a bit strange, for sure. But this may help to replace `Map` with `Set` in certain cases with ease, and vice versa. The same methods `Map` has for iterators are also supported: -- `set.keys()` -- returns an iterable object for values, -- `set.values()` -- same as `set.keys()`, for compatibility with `Map`, -- `set.entries()` -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. +- [`set.keys()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys) -- returns an iterable object for values, +- [`set.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/values) -- same as `set.keys()`, for compatibility with `Map`, +- [`set.entries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/entries) -- returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`. ## Summary -`Map` -- is a collection of keyed values. +[`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) -- is a collection of keyed values. Methods and properties: -- `new Map([iterable])` -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. -- `map.set(key, value)` -- stores the value by the key, returns the map itself. -- `map.get(key)` -- returns the value by the key, `undefined` if `key` doesn't exist in map. -- `map.has(key)` -- returns `true` if the `key` exists, `false` otherwise. -- `map.delete(key)` -- removes the value by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`. -- `map.clear()` -- removes everything from the map. -- `map.size` -- returns the current element count. +- [`new Map([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Map) -- creates the map, with optional `iterable` (e.g. array) of `[key,value]` pairs for initialization. +- [`map.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set) -- stores the value by the key, returns the map itself. +- [`map.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get) -- returns the value by the key, `undefined` if `key` doesn't exist in map. +- [`map.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/has) -- returns `true` if the `key` exists, `false` otherwise. +- [`map.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/delete) -- removes the element by the key, returns `true` if `key` existed at the moment of the call, otherwise `false`. +- [`map.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/clear) -- removes everything from the map. +- [`map.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) -- returns the current element count. The differences from a regular `Object`: - Any keys, objects can be keys. - Additional convenient methods, the `size` property. -`Set` -- is a collection of unique values. +[`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) -- is a collection of unique values. Methods and properties: -- `new Set([iterable])` -- creates the set, with optional `iterable` (e.g. array) of values for initialization. -- `set.add(value)` -- adds a value (does nothing if `value` exists), returns the set itself. -- `set.delete(value)` -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. -- `set.has(value)` -- returns `true` if the value exists in the set, otherwise `false`. -- `set.clear()` -- removes everything from the set. -- `set.size` -- is the elements count. +- [`new Set([iterable])`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/Set) -- creates the set, with optional `iterable` (e.g. array) of values for initialization. +- [`set.add(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/add) -- adds a value (does nothing if `value` exists), returns the set itself. +- [`set.delete(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/delete) -- removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`. +- [`set.has(value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/has) -- returns `true` if the value exists in the set, otherwise `false`. +- [`set.clear()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear) -- removes everything from the set. +- [`set.size`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size) -- is the elements count. Iteration over `Map` and `Set` is always in the insertion order, so we can't say that these collections are unordered, but we can't reorder elements or directly get an element by its number. diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 8d5a869818..9795017d41 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,8 +1,10 @@ + # WeakMap and WeakSet As we know from the chapter , JavaScript engine keeps a value in memory while it is "reachable" and can potentially be used. For instance: + ```js let john = { name: "John" }; @@ -54,13 +56,13 @@ john = null; // overwrite the reference */!* ``` -`WeakMap` is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. Let's see what it means on examples. ## WeakMap -The first difference between `Map` and `WeakMap` is that keys must be objects, not primitive values: +The first difference between [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) and [`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is that keys must be objects, not primitive values: ```js run let weakMap = new WeakMap(); @@ -94,10 +96,10 @@ Compare it with the regular `Map` example above. Now if `john` only exists as th `WeakMap` has only the following methods: -- `weakMap.get(key)` -- `weakMap.set(key, value)` -- `weakMap.delete(key)` -- `weakMap.has(key)` +- [`weakMap.set(key, value)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/set) +- [`weakMap.get(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/get) +- [`weakMap.delete(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/delete) +- [`weakMap.has(key)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap/has) Why such a limitation? That's for technical reasons. If an object has lost all other references (like `john` in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified *when the cleanup happens*. @@ -182,6 +184,7 @@ function process(obj) { let result = /* calculations of the result for */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -221,6 +224,7 @@ function process(obj) { let result = /* calculate the result for */ obj; cache.set(obj, result); + return result; } return cache.get(obj); @@ -242,11 +246,11 @@ obj = null; ## WeakSet -`WeakSet` behaves similarly: +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) behaves similarly: - It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). - An object exists in the set while it is reachable from somewhere else. -- Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations. +- Like `Set`, it supports [`add`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/add), [`has`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/has) and [`delete`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Weakset/delete), but not `size`, `keys()` and no iterations. Being "weak", it also serves as additional storage. But not for arbitrary data, rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. @@ -280,9 +284,9 @@ The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterati ## Summary -`WeakMap` is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. +[`WeakMap`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap) is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. -`WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. +[`WeakSet`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet) is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. Their main advantages are that they have weak reference to objects, so they can easily be removed by garbage collector. diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md index 2266c0779e..6958a3a97e 100644 --- a/1-js/05-data-types/11-date/article.md +++ b/1-js/05-data-types/11-date/article.md @@ -376,7 +376,7 @@ for (let i = 0; i < 10; i++) { ```warn header="Be careful doing microbenchmarking" Modern JavaScript engines perform many optimizations. They may tweak results of "artificial tests" compared to "normal usage", especially when we benchmark something very small, such as how an operator works, or a built-in function. So if you seriously want to understand performance, then please study how the JavaScript engine works. And then you probably won't need microbenchmarks at all. -The great pack of articles about V8 can be found at . +The great pack of articles about V8 can be found at . ``` ## Date.parse from a string diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md index 503745356d..25bb52fe3b 100644 --- a/1-js/05-data-types/12-json/article.md +++ b/1-js/05-data-types/12-json/article.md @@ -451,7 +451,7 @@ let json = `{ Besides, JSON does not support comments. Adding a comment to JSON makes it invalid. -There's another format named [JSON5](http://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. +There's another format named [JSON5](https://json5.org/), which allows unquoted keys, comments etc. But this is a standalone library, not in the specification of the language. The regular JSON is that strict not because its developers are lazy, but to allow easy, reliable and very fast implementations of the parsing algorithm. diff --git a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md index 3a281ef3f5..11667f940b 100644 --- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md +++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md @@ -37,4 +37,4 @@ P.S. Naturally, the formula is the fastest solution. It uses only 3 operations f The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower. -P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory, so counting `sumTo(100000)` becomes possible. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. +P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function, with no other calculations performed, then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size. diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index b7992f1621..5ae8944742 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -61,7 +61,7 @@ When `pow(x, n)` is called, the execution splits into two branches: if n==1 = x / pow(x, n) = - \ + \ else = x * pow(x, n - 1) ``` @@ -285,7 +285,7 @@ The iterative `pow` uses a single context changing `i` and `result` in the proce **Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.** -...But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. +...But sometimes the rewrite is non-trivial, especially when a function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts. Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used. diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md index c63fe70cd7..dbdfbd6c0d 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md @@ -23,7 +23,7 @@ function sum(a, b) { alert( sum(1, 2, 3, 4, 5) ); ``` -There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted. +There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted, so the result in the code above is `3`. The rest of the parameters can be included in the function definition by using three dots `...` followed by the name of the array that will contain them. The dots literally mean "gather the remaining parameters into an array". diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md index 1579afb621..28d7a76ec8 100644 --- a/1-js/06-advanced-functions/04-var/article.md +++ b/1-js/06-advanced-functions/04-var/article.md @@ -58,7 +58,7 @@ alert(test); // ReferenceError: test is not defined The same thing for loops: `var` cannot be block- or loop-local: -```js +```js run for (var i = 0; i < 10; i++) { var one = 1; // ... @@ -170,7 +170,7 @@ That's best demonstrated with an example: ```js run function sayHi() { - alert(phrase); + alert(phrase); *!* var phrase = "Hello"; diff --git a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md index fa1d98cb93..f969599884 100644 --- a/1-js/06-advanced-functions/08-settimeout-setinterval/article.md +++ b/1-js/06-advanced-functions/08-settimeout-setinterval/article.md @@ -27,7 +27,7 @@ Usually, that's a function. For historical reasons, a string of code can be pass : The delay before run, in milliseconds (1000 ms = 1 second), by default 0. `arg1`, `arg2`... -: Arguments for the function (not supported in IE9-) +: Arguments for the function For instance, this code calls `sayHi()` after one second: @@ -102,7 +102,7 @@ As we can see from `alert` output, in a browser the timer identifier is a number Again, there is no universal specification for these methods, so that's fine. -For browsers, timers are described in the [timers section](https://www.w3.org/TR/html5/webappapis.html#timers) of HTML5 standard. +For browsers, timers are described in the [timers section](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) of HTML Living Standard. ## setInterval @@ -256,7 +256,7 @@ The first line "puts the call into calendar after 0ms". But the scheduler will o There are also advanced browser-related use cases of zero-delay timeout, that we'll discuss in the chapter . ````smart header="Zero delay is in fact not zero (in a browser)" -In the browser, there's a limitation of how often nested timers can run. The [HTML5 standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". +In the browser, there's a limitation of how often nested timers can run. The [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.". Let's demonstrate what it means with the example below. The `setTimeout` call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the `times` array. What do the real delays look like? Let's see: diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index 9d705cdcdf..6d65e7dd10 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -202,7 +202,7 @@ for (let key in user) { } ``` -JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll) in lodash. +JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) in lodash. ```` ## Partial functions diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index bdc6934182..0a945b377b 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name' Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours. ```smart header="Errors appear only in strict mode" -In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. ``` Here's the same example, but the property is created from scratch: diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index 06001d900e..526b832efa 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -103,7 +103,7 @@ Here's the diagram (see the right part): That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above. -As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. +As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`. ## EventMixin diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index a928da2896..bf548373ad 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -632,7 +632,7 @@ For instance: The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers. -There are also web-services that provide error-logging for such cases, like or . +There are also web-services that provide error-logging for such cases, like or . They work like this: diff --git a/1-js/11-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md index fbb425d47e..207fb2c8c0 100644 --- a/1-js/11-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -36,7 +36,7 @@ So to summarize: the executor runs automatically and attempts to perform a job. The `promise` object returned by the `new Promise` constructor has these internal properties: - `state` — initially `"pending"`, then changes to either `"fulfilled"` when `resolve` is called or `"rejected"` when `reject` is called. -- `result` — initially `undefined`, then changes to `value` when `resolve(value)` called or `error` when `reject(error)` is called. +- `result` — initially `undefined`, then changes to `value` when `resolve(value)` is called or `error` when `reject(error)` is called. So the executor eventually moves `promise` to one of these states: @@ -281,7 +281,7 @@ To summarize: - If a `finally` handler returns something, it's ignored. - When `finally` throws an error, then the execution goes to the nearest error handler. -These features are helpful and make things work just the right way if we `finally` how it's supposed to be used: for generic cleanup procedures. +These features are helpful and make things work just the right way if we use `finally` how it's supposed to be used: for generic cleanup procedures. ````smart header="We can attach handlers to settled promises" If a promise is pending, `.then/catch/finally` handlers wait for its outcome. diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md index ebf869156d..aa60254011 100644 --- a/1-js/11-async/03-promise-chaining/article.md +++ b/1-js/11-async/03-promise-chaining/article.md @@ -224,7 +224,7 @@ This feature allows us to integrate custom objects with promise chains without h ## Bigger example: fetch -In frontend programming promises are often used for network requests. So let's see an extended example of that. +In frontend programming, promises are often used for network requests. So let's see an extended example of that. We'll use the [fetch](info:fetch) method to load the information about the user from the remote server. It has a lot of optional parameters covered in [separate chapters](info:fetch), but the basic syntax is quite simple: diff --git a/1-js/11-async/06-promisify/article.md b/1-js/11-async/06-promisify/article.md index 1d81b31a62..855678e5be 100644 --- a/1-js/11-async/06-promisify/article.md +++ b/1-js/11-async/06-promisify/article.md @@ -25,7 +25,7 @@ function loadScript(src, callback) { The function loads a script with the given `src`, and then calls `callback(err)` in case of an error, or `callback(null, script)` in case of successful loading. That's a widespread agreement for using callbacks, we saw it before. -Let's promisify it. +Let's promisify it. We'll make a new function `loadScriptPromise(src)`, that does the same (loads the script), but returns a promise instead of using callbacks. @@ -124,7 +124,7 @@ For more exotic callback formats, like those without `err` at all: `callback(res There are also modules with a bit more flexible promisification functions, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). In Node.js, there's a built-in `util.promisify` function for that. ```smart -Promisification is a great approach, especially when you use `async/await` (see the next chapter), but not a total replacement for callbacks. +Promisification is a great approach, especially when you use `async/await` (covered later in the chapter ), but not a total replacement for callbacks. Remember, a promise may have only one result, but a callback may technically be called many times. diff --git a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md index af2ad0eed0..4355d0cfc0 100644 --- a/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md +++ b/1-js/12-generators-iterators/1-generators/01-pseudo-random-generator/solution.md @@ -3,7 +3,7 @@ function* pseudoRandom(seed) { let value = seed; while(true) { - value = value * 16807 % 2147483647 + value = value * 16807 % 2147483647; yield value; } diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md index e9b7272b9d..5ad70d1519 100644 --- a/1-js/13-modules/01-modules-intro/article.md +++ b/1-js/13-modules/01-modules-intro/article.md @@ -9,11 +9,11 @@ But eventually scripts became more and more complex, so the community invented a To name some (for historical reasons): -- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](http://requirejs.org/). -- [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.js server. +- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](https://requirejs.org/). +- [CommonJS](https://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.js server. - [UMD](https://github.com/umdjs/umd) -- one more module system, suggested as a universal one, compatible with AMD and CommonJS. -Now all these slowly become a part of history, but we still can find them in old scripts. +Now these all slowly became a part of history, but we still can find them in old scripts. The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js. So we'll study the modern JavaScript modules from now on. diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md index 8af1b3b109..ccbf18cf57 100644 --- a/1-js/13-modules/02-import-export/article.md +++ b/1-js/13-modules/02-import-export/article.md @@ -46,7 +46,7 @@ Also, we can put `export` separately. Here we first declare, and then export: -```js +```js // 📁 say.js function sayHi(user) { alert(`Hello, ${user}!`); @@ -93,25 +93,14 @@ At first sight, "import everything" seems such a cool thing, short to write, why Well, there are few reasons. -1. Modern build tools ([webpack](https://webpack.js.org/) and others) bundle modules together and optimize them to speedup loading and remove unused stuff. - - Let's say, we added a 3rd-party library `say.js` to our project with many functions: - ```js - // 📁 say.js - export function sayHi() { ... } - export function sayBye() { ... } - export function becomeSilent() { ... } - ``` +1. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. +2. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. - Now if we only use one of `say.js` functions in our project: - ```js - // 📁 main.js - import {sayHi} from './say.js'; - ``` - ...Then the optimizer will see that and remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking". +```smart header="Don't be afraid to import too much" +Modern build tools, such as [webpack](https://webpack.js.org/) and others, bundle modules together and optimize them to speedup loading. They also removed unused imports. -2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `say.sayHi()`. -3. Explicit list of imports gives better overview of the code structure: what is used and where. It makes code support and refactoring easier. +For instance, if you `import * as library` from a huge code library, and then use only few methods, then unused ones [will not be included](https://github.com/webpack/webpack/tree/main/examples/harmony-unused#examplejs) into the optimzed bundle. +``` ## Import "as" @@ -224,7 +213,7 @@ Without `default`, such an export would give an error: export class { // Error! (non-default export needs a name) constructor() {} } -``` +``` ### The "default" name @@ -326,7 +315,7 @@ Imagine, we're writing a "package": a folder with a lot of modules, with some of The file structure could be like this: ``` auth/ - index.js + index.js user.js helpers.js tests/ @@ -372,7 +361,7 @@ The syntax `export ... from ...` is just a shorter notation for such import-expo ```js // 📁 auth/index.js -// re-export login/logout +// re-export login/logout export {login, logout} from './helpers.js'; // re-export the default export as User @@ -380,7 +369,7 @@ export {default as User} from './user.js'; ... ``` -The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. +The notable difference of `export ... from` compared to `import/export` is that re-exported modules aren't available in the current file. So inside the above example of `auth/index.js` we can't use re-exported `login/logout` functions. ### Re-exporting the default export @@ -399,7 +388,7 @@ We can come across two problems with it: 1. `export User from './user.js'` won't work. That would lead to a syntax error. - To re-export the default export, we have to write `export {default as User}`, as in the example above. + To re-export the default export, we have to write `export {default as User}`, as in the example above. 2. `export * from './user.js'` re-exports only named exports, but ignores the default one. @@ -430,7 +419,7 @@ Import: - Importing named exports: - `import {x [as y], ...} from "module"` -- Importing the default export: +- Importing the default export: - `import x from "module"` - `import {default as x} from "module"` - Import all: diff --git a/1-js/99-js-misc/04-reference-type/article.md b/1-js/99-js-misc/04-reference-type/article.md index 6861837a92..894db8fc63 100644 --- a/1-js/99-js-misc/04-reference-type/article.md +++ b/1-js/99-js-misc/04-reference-type/article.md @@ -87,7 +87,7 @@ The result of a property access `user.hi` is not a function, but a value of Refe (user, "hi", true) ``` -When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`=user` in this case). +When parentheses `()` are called on the Reference Type, they receive the full information about the object and its method, and can set the right `this` (`user` in this case). Reference type is a special "intermediary" internal type, with the purpose to pass information from dot `.` to calling parentheses `()`. diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index e18335f38e..f7f2be91dc 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -212,7 +212,7 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu ## See it for yourself -To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index de47eac9fb..405129694f 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -55,7 +55,7 @@ Also, there's a global variable named by `id` that references the element: ``` ```warn header="Please don't use id-named global variables to access elements" -This behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), so it's a kind of standard. But it is supported mainly for compatibility. +This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from. diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index aed342b205..99dde5bcd0 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -28,7 +28,7 @@ The classes are: - [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole. - The `document` global object belongs exactly to this class. It servers as an entry point to the DOM. + The `document` global object belongs exactly to this class. It serves as an entry point to the DOM. - [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by: - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `

Hello

`. diff --git a/2-ui/1-document/06-dom-attributes-and-properties/article.md b/2-ui/1-document/06-dom-attributes-and-properties/article.md index e39a542564..b02f626dc8 100644 --- a/2-ui/1-document/06-dom-attributes-and-properties/article.md +++ b/2-ui/1-document/06-dom-attributes-and-properties/article.md @@ -162,7 +162,7 @@ In the example below `id` is modified as an attribute, and we can see the proper ``` -But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back: +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: ```html run diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index 644583c34f..46aaa3b00d 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -269,20 +269,6 @@ So nowadays `getComputedStyle` actually returns the resolved value of the proper We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed. For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. - -There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: - -```html run - - -``` ```` ```smart header="Styles applied to `:visited` links are hidden!" diff --git a/2-ui/2-events/01-introduction-browser-events/article.md b/2-ui/2-events/01-introduction-browser-events/article.md index f75f6c11f4..4eca222aa3 100644 --- a/2-ui/2-events/01-introduction-browser-events/article.md +++ b/2-ui/2-events/01-introduction-browser-events/article.md @@ -24,7 +24,7 @@ Here's a list of the most useful DOM events, just to take a look at: **CSS events:** - `transitionend` -- when a CSS-animation finishes. -There are many other events. We'll get into more details of particular events in next chapters. +There are many other events. We'll get into more details of particular events in upcoming chapters. ## Event handlers @@ -195,7 +195,7 @@ Assign a handler to `elem.onclick`, not `elem.ONCLICK`, because DOM properties a ## addEventListener -The fundamental problem of the aforementioned ways to assign handlers -- we can't assign multiple handlers to one event. +The fundamental problem of the aforementioned ways to assign handlers is that we *can't assign multiple handlers to one event*. Let's say, one part of our code wants to highlight a button on click, and another one wants to show a message on the same click. @@ -207,7 +207,7 @@ input.onclick = function() { alert(1); } input.onclick = function() { alert(2); } // replaces the previous handler ``` -Developers of web standards understood that long ago and suggested an alternative way of managing handlers using special methods `addEventListener` and `removeEventListener`. They are free of such a problem. +Developers of web standards understood that long ago and suggested an alternative way of managing handlers using the special methods `addEventListener` and `removeEventListener` which aren't bound by such constraint. The syntax to add a handler: @@ -261,7 +261,7 @@ input.removeEventListener("click", handler); Please note -- if we don't store the function in a variable, then we can't remove it. There's no way to "read back" handlers assigned by `addEventListener`. ```` -Multiple calls to `addEventListener` allow to add multiple handlers, like this: +Multiple calls to `addEventListener` allow it to add multiple handlers, like this: ```html run no-beautify @@ -288,7 +288,7 @@ 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. Only with `addEventListener`. -For instance, the `DOMContentLoaded` event, that triggers when the document is loaded and DOM is built. +For instance, the `DOMContentLoaded` event, that triggers when the document is loaded and the DOM has been built. ```js // will never run @@ -334,10 +334,10 @@ Some properties of `event` object: `event.currentTarget` : Element that handled the event. That's exactly the same as `this`, unless the handler is an arrow function, or its `this` is bound to something else, then we can get the element from `event.currentTarget`. -`event.clientX / event.clientY` +`event.clientX` / `event.clientY` : Window-relative coordinates of the cursor, for pointer events. -There are more properties. Many of them depend on the event type: keyboard events have one set of properties, pointer events - another one, we'll study them later when we come to different events in details. +There are more properties. Many of them depend on the event type: keyboard events have one set of properties, pointer events - another one, we'll study them later when as we move on to the details of different events. ````smart header="The event object is also available in HTML handlers" If we assign a handler in HTML, we can also use the `event` object, like this: @@ -373,7 +373,7 @@ For instance: As we can see, when `addEventListener` receives an object as the handler, it calls `obj.handleEvent(event)` in case of an event. -We could also use a class for that: +We could also use objects of a custom class, like this: ```html run @@ -395,6 +395,7 @@ We could also use a class for that: *!* let menu = new Menu(); + elem.addEventListener('mousedown', menu); elem.addEventListener('mouseup', menu); */!* diff --git a/2-ui/2-events/02-bubbling-and-capturing/article.md b/2-ui/2-events/02-bubbling-and-capturing/article.md index 5d854a59fd..2448cfa5b6 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -120,26 +120,27 @@ There's usually no real need to prevent the bubbling. A task that seemingly requ There's another phase of event processing called "capturing". It is rarely used in real code, but sometimes can be useful. -The standard [DOM Events](http://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation: +The standard [DOM Events](https://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation: 1. Capturing phase -- the event goes down to the element. 2. Target phase -- the event reached the target element. 3. Bubbling phase -- the event bubbles up from the element. -Here's the picture of a click on `` inside a table, taken from the specification: +Here's the picture, taken from the specification, of the capturing `(1)`, target `(2)` and bubbling `(3)` phases for a click event on a `` inside a table: ![](eventflow.svg) That is: for a click on `` the event first goes through the ancestors chain down to the element (capturing phase), then it reaches the target and triggers there (target phase), and then it goes up (bubbling phase), calling handlers on its way. -**Before we only talked about bubbling, because the capturing phase is rarely used. Normally it is invisible to us.** +Until now, we only talked about bubbling, because the capturing phase is rarely used. -Handlers added using `on`-property or using HTML attributes or using two-argument `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. +In fact, the capturing phase was invisible for us, because handlers added using `on`-property or using HTML attributes or using two-argument `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. To catch an event on the capturing phase, we need to set the handler `capture` option to `true`: ```js elem.addEventListener(..., {capture: true}) + // or, just "true" is an alias to {capture: true} elem.addEventListener(..., true) ``` @@ -180,9 +181,10 @@ The code sets click handlers on *every* element in the document to see which one If you click on `

`, then the sequence is: -1. `HTML` -> `BODY` -> `FORM` -> `DIV` (capturing phase, the first listener): -2. `P` (target phase, triggers two times, as we've set two listeners: capturing and bubbling) -3. `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). +1. `HTML` -> `BODY` -> `FORM` -> `DIV -> P` (capturing phase, the first listener): +2. `P` -> `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). + +Please note, the `P` shows up twice, because we've set two listeners: capturing and bubbling. The target triggers at the end of the first and at the beginning of the second phase. There's a property `event.eventPhase` that tells us the number of the phase on which the event was caught. But it's rarely used, because we usually know it in the handler. @@ -190,7 +192,7 @@ There's a property `event.eventPhase` that tells us the number of the phase on w If we `addEventListener(..., true)`, then we should mention the same phase in `removeEventListener(..., true)` to correctly remove the handler. ``` -````smart header="Listeners on same element and same phase run in their set order" +````smart header="Listeners on the same element and same phase run in their set order" If we have multiple event handlers on the same phase, assigned to the same element with `addEventListener`, they run in the same order as they are created: ```js @@ -199,6 +201,12 @@ elem.addEventListener("click", e => alert(2)); ``` ```` +```smart header="The `event.stopPropagation()` during the capturing also prevents the bubbling" +The `event.stopPropagation()` method and its sibling `event.stopImmediatePropagation()` can also be called on the capturing phase. Then not only the futher capturing is stopped, but the bubbling as well. + +In other words, normally the event goes first down ("capturing") and then up ("bubbling"). But if `event.stopPropagation()` is called during the capturing phase, then the event travel stops, no bubbling will occur. +``` + ## Summary @@ -216,7 +224,7 @@ Each handler can access `event` object properties: Any event handler can stop the event by calling `event.stopPropagation()`, but that's not recommended, because we can't really be sure we won't need it above, maybe for completely different things. -The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logic behind that. +The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logical explanation for that. In real world, when an accident happens, local authorities react first. They know best the area where it happened. Then higher-level authorities if needed. diff --git a/2-ui/2-events/05-dispatch-events/article.md b/2-ui/2-events/05-dispatch-events/article.md index 77a49490fa..047413fd3d 100644 --- a/2-ui/2-events/05-dispatch-events/article.md +++ b/2-ui/2-events/05-dispatch-events/article.md @@ -8,7 +8,7 @@ We can generate not only completely new events, that we invent for our own purpo ## Event constructor -Built-in event classes form a hierarchy, similar to DOM element classes. The root is the built-in [Event](http://www.w3.org/TR/dom/#event) class. +Built-in event classes form a hierarchy, similar to DOM element classes. The root is the built-in [Event](https://dom.spec.whatwg.org/#events) class. We can create `Event` objects like this: diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index e9a23ad63f..4c928eef1c 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -18,7 +18,7 @@ The basic Drag'n'Drop algorithm looks like this: 2. Then on `mousemove` move it by changing `left/top` with `position:absolute`. 3. On `mouseup` - perform all actions related to finishing the drag'n'drop. -These are the basics. Later we'll see how to other features, such as highlighting current underlying elements while we drag over them. +These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them. Here's the implementation of dragging a ball: diff --git a/2-ui/3-event-details/6-pointer-events/article.md b/2-ui/3-event-details/6-pointer-events/article.md index ef48dc3354..b8873e9d8a 100644 --- a/2-ui/3-event-details/6-pointer-events/article.md +++ b/2-ui/3-event-details/6-pointer-events/article.md @@ -111,7 +111,7 @@ Such causes are: We'll demonstrate `pointercancel` on a practical example to see how it affects us. -Let's say we're impelementing drag'n'drop for a ball, just as in the beginning of the article . +Let's say we're implementing drag'n'drop for a ball, just as in the beginning of the article . Here is the flow of user actions and the corresponding events: diff --git a/2-ui/5-loading/01-onload-ondomcontentloaded/article.md b/2-ui/5-loading/01-onload-ondomcontentloaded/article.md index 42d10fe723..07624a6587 100644 --- a/2-ui/5-loading/01-onload-ondomcontentloaded/article.md +++ b/2-ui/5-loading/01-onload-ondomcontentloaded/article.md @@ -265,7 +265,7 @@ Here's a document with ` - + diff --git a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html index a4685a7165..27df709393 100644 --- a/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html +++ b/2-ui/5-loading/01-onload-ondomcontentloaded/readystate.view/index.html @@ -9,8 +9,8 @@ [20] readyState:interactive [21] DOMContentLoaded [30] iframe onload - [40] readyState:complete [40] img onload + [40] readyState:complete [40] window onload --> diff --git a/2-ui/99-ui-misc/02-selection-range/article.md b/2-ui/99-ui-misc/02-selection-range/article.md index 9d6bb9c93a..819bcba293 100644 --- a/2-ui/99-ui-misc/02-selection-range/article.md +++ b/2-ui/99-ui-misc/02-selection-range/article.md @@ -408,7 +408,7 @@ From – To There are two approaches to copying the selected content: 1. We can use `document.getSelection().toString()` to get it as text. -2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangesAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. +2. Otherwise, to copy the full DOM, e.g. if we need to keep formatting, we can get the underlying ranges with `getRangeAt(...)`. A `Range` object, in turn, has `cloneContents()` method that clones its content and returns as `DocumentFragment` object, that we can insert elsewhere. Here's the demo of copying the selected content both as text and as DOM nodes: diff --git a/3-frames-and-windows/01-popup-windows/article.md b/3-frames-and-windows/01-popup-windows/article.md index 01da2b3045..f2c87d1e01 100644 --- a/3-frames-and-windows/01-popup-windows/article.md +++ b/3-frames-and-windows/01-popup-windows/article.md @@ -38,26 +38,6 @@ button.onclick = () => { This way users are somewhat protected from unwanted popups, but the functionality is not disabled totally. -What if the popup opens from `onclick`, but after `setTimeout`? That's a bit tricky. - -Try this code: - -```js run -// open after 3 seconds -setTimeout(() => window.open('http://google.com'), 3000); -``` - -The popup opens in Chrome, but gets blocked in Firefox. - -...If we decrease the delay, the popup works in Firefox too: - -```js run -// open after 1 seconds -setTimeout(() => window.open('http://google.com'), 1000); -``` - -The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it -- removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not. - ## window.open The syntax to open a popup is: `window.open(url, name, params)`: diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index d45ee391d6..4420f43c77 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -44,7 +44,7 @@ One way to communicate with another server was to submit a `

` there. Peopl */!* - + *!* */!* @@ -169,6 +169,7 @@ For cross-origin request, by default JavaScript may only access so-called "safe" - `Cache-Control` - `Content-Language` +- `Content-Length` - `Content-Type` - `Expires` - `Last-Modified` @@ -176,12 +177,6 @@ For cross-origin request, by default JavaScript may only access so-called "safe" Accessing any other response header causes an error. -```smart -There's no `Content-Length` header in the list! - -This header contains the full response length. So, if we're downloading something and would like to track the percentage of progress, then an additional permission is required to access that header (see below). -``` - To grant JavaScript access to any other response header, the server must send the `Access-Control-Expose-Headers` header. It contains a comma-separated list of unsafe header names that should be made accessible. For example: @@ -190,14 +185,15 @@ For example: 200 OK Content-Type:text/html; charset=UTF-8 Content-Length: 12345 +Content-Encoding: gzip API-Key: 2c9de507f2c54aa1 Access-Control-Allow-Origin: https://javascript.info *!* -Access-Control-Expose-Headers: Content-Length,API-Key +Access-Control-Expose-Headers: Content-Encoding,API-Key */!* ``` -With such an `Access-Control-Expose-Headers` header, the script is allowed to read the `Content-Length` and `API-Key` headers of the response. +With such an `Access-Control-Expose-Headers` header, the script is allowed to read the `Content-Encoding` and `API-Key` headers of the response. ## "Unsafe" requests diff --git a/5-network/06-fetch-api/article.md b/5-network/06-fetch-api/article.md index c3f75175fc..5f55c78efa 100644 --- a/5-network/06-fetch-api/article.md +++ b/5-network/06-fetch-api/article.md @@ -21,10 +21,10 @@ let promise = fetch(url, { // depending on the request body "Content-Type": "text/plain;charset=UTF-8" }, - body: undefined // string, FormData, Blob, BufferSource, or URLSearchParams + body: undefined, // string, FormData, Blob, BufferSource, or URLSearchParams referrer: "about:client", // or "" to send no Referer header, // or an url from the current origin - referrerPolicy: "no-referrer-when-downgrade", // no-referrer, origin, same-origin... + referrerPolicy: "strict-origin-when-cross-origin", // no-referrer-when-downgrade, no-referrer, origin, same-origin... mode: "cors", // same-origin, no-cors credentials: "same-origin", // omit, include cache: "default", // no-store, reload, no-cache, force-cache, or only-if-cached @@ -85,13 +85,13 @@ Unlike the `referrer` option that allows to set the exact `Referer` value, `refe Possible values are described in the [Referrer Policy specification](https://w3c.github.io/webappsec-referrer-policy/): -- **`"no-referrer-when-downgrade"`** -- the default value: full `Referer` is always sent, unless we send a request from HTTPS to HTTP (to the less secure protocol). +- **`"strict-origin-when-cross-origin"`** -- the default value: for same-origin send the full `Referer`, for cross-origin send only the origin, unless it's HTTPS→HTTP request, then send nothing. +- **`"no-referrer-when-downgrade"`** -- full `Referer` is always sent, unless we send a request from HTTPS to HTTP (to the less secure protocol). - **`"no-referrer"`** -- never send `Referer`. - **`"origin"`** -- only send the origin in `Referer`, not the full page URL, e.g. only `http://site.com` instead of `http://site.com/path`. - **`"origin-when-cross-origin"`** -- send the full `Referer` to the same origin, but only the origin part for cross-origin requests (as above). - **`"same-origin"`** -- send the full `Referer` to the same origin, but no `Referer` for cross-origin requests. - **`"strict-origin"`** -- send only the origin, not the `Referer` for HTTPS→HTTP requests. -- **`"strict-origin-when-cross-origin"`** -- for same-origin send the full `Referer`, for cross-origin send only the origin, unless it's HTTPS→HTTP request, then send nothing. - **`"unsafe-url"`** -- always send the full url in `Referer`, even for HTTPS→HTTP requests. Here's a table with all combinations: @@ -99,12 +99,12 @@ Here's a table with all combinations: | Value | To same origin | To another origin | HTTPS→HTTP | |-------|----------------|-------------------|------------| | `"no-referrer"` | - | - | - | -| `"no-referrer-when-downgrade"` or `""` (default) | full | full | - | +| `"no-referrer-when-downgrade"` | full | full | - | | `"origin"` | origin | origin | origin | | `"origin-when-cross-origin"` | full | origin | origin | | `"same-origin"` | full | - | - | | `"strict-origin"` | origin | origin | - | -| `"strict-origin-when-cross-origin"` | full | origin | - | +| `"strict-origin-when-cross-origin"` or `""` (default) | full | origin | - | | `"unsafe-url"` | full | full | full | Let's say we have an admin zone with a URL structure that shouldn't be known from outside of the site. diff --git a/5-network/09-resume-upload/article.md b/5-network/09-resume-upload/article.md index 7eedc3fbd5..b0aa447d61 100644 --- a/5-network/09-resume-upload/article.md +++ b/5-network/09-resume-upload/article.md @@ -48,7 +48,7 @@ To resume upload, we need to know *exactly* the number of bytes received by the 3. Then, we can use `Blob` method `slice` to send the file from `startByte`: ```js - xhr.open("POST", "upload", true); + xhr.open("POST", "upload"); // File id, so that the server knows which file we upload xhr.setRequestHeader('X-File-Id', fileId); diff --git a/5-network/10-long-polling/article.md b/5-network/10-long-polling/article.md index be367cee7b..9c95079c62 100644 --- a/5-network/10-long-polling/article.md +++ b/5-network/10-long-polling/article.md @@ -1,6 +1,6 @@ # Long polling -Long polling is the simplest way of having persistent connection with server, that doesn't use any specific protocol like WebSocket or Server Side Events. +Long polling is the simplest way of having persistent connection with server, that doesn't use any specific protocol like WebSocket or Server Sent Events. Being very easy to implement, it's also good enough in a lot of cases. diff --git a/5-network/11-websocket/article.md b/5-network/11-websocket/article.md index adebd6c74c..268b674f0f 100644 --- a/5-network/11-websocket/article.md +++ b/5-network/11-websocket/article.md @@ -56,7 +56,7 @@ socket.onclose = function(event) { }; socket.onerror = function(error) { - alert(`[error] ${error.message}`); + alert(`[error]`); }; ``` @@ -91,7 +91,7 @@ Sec-WebSocket-Version: 13 - `Origin` -- the origin of the client page, e.g. `https://javascript.info`. WebSocket objects are cross-origin by nature. There are no special headers or other limitations. Old servers are unable to handle WebSocket anyway, so there are no compatibility issues. But the `Origin` header is important, as it allows the server to decide whether or not to talk WebSocket with this website. - `Connection: Upgrade` -- signals that the client would like to change the protocol. - `Upgrade: websocket` -- the requested protocol is "websocket". -- `Sec-WebSocket-Key` -- a random browser-generated key for security. +- `Sec-WebSocket-Key` -- a random browser-generated key, used to ensure that the server supports WebSocket protocol. It's random to prevent proxies from caching any following communication. - `Sec-WebSocket-Version` -- WebSocket protocol version, 13 is the current one. ```smart header="WebSocket handshake can't be emulated" @@ -107,7 +107,7 @@ Connection: Upgrade Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g= ``` -Here `Sec-WebSocket-Accept` is `Sec-WebSocket-Key`, recoded using a special algorithm. The browser uses it to make sure that the response corresponds to the request. +Here `Sec-WebSocket-Accept` is `Sec-WebSocket-Key`, recoded using a special algorithm. Upon seeing it, the browser understands that the server really does support the WebSocket protocol. Afterwards, the data is transferred using the WebSocket protocol, we'll see its structure ("frames") soon. And that's not HTTP at all. diff --git a/6-data-storage/01-cookie/article.md b/6-data-storage/01-cookie/article.md index b888494575..01c0e1feea 100644 --- a/6-data-storage/01-cookie/article.md +++ b/6-data-storage/01-cookie/article.md @@ -156,7 +156,7 @@ If we set `expires` to a date in the past, the cookie is deleted. - **`max-age=3600`** -Is an alternative to `expires` and specifies the cookie's expiration in seconds from the current moment. +It's an alternative to `expires` and specifies the cookie's expiration in seconds from the current moment. If set to zero or a negative value, the cookie is deleted: diff --git a/6-data-storage/02-localstorage/article.md b/6-data-storage/02-localstorage/article.md index e4a4b35586..a99bcb6533 100644 --- a/6-data-storage/02-localstorage/article.md +++ b/6-data-storage/02-localstorage/article.md @@ -10,7 +10,7 @@ We already have cookies. Why additional objects? - Also unlike cookies, the server can't manipulate storage objects via HTTP headers. Everything's done in JavaScript. - The storage is bound to the origin (domain/protocol/port triplet). That is, different protocols or subdomains infer different storage objects, they can't access data from each other. -Both storage objects provide same methods and properties: +Both storage objects provide the same methods and properties: - `setItem(key, value)` -- store key/value pair. - `getItem(key)` -- get the value by key. @@ -124,7 +124,7 @@ The latter works, because `Object.keys` only returns the keys that belong to the Please note that both key and value must be strings. -If were any other type, like a number, or an object, it gets converted to string automatically: +If they were any other type, like a number, or an object, they would get converted to a string automatically: ```js run localStorage.user = {name: "John"}; @@ -219,7 +219,7 @@ Modern browsers also support [Broadcast channel API](mdn:/api/Broadcast_Channel_ ## Summary -Web storage objects `localStorage` and `sessionStorage` allow to store key/value in the browser. +Web storage objects `localStorage` and `sessionStorage` allow to store key/value pairs in the browser. - Both `key` and `value` must be strings. - The limit is 5mb+, depends on the browser. diff --git a/9-regular-expressions/03-regexp-unicode/article.md b/9-regular-expressions/03-regexp-unicode/article.md index 712b6ecd01..1be163aff8 100644 --- a/9-regular-expressions/03-regexp-unicode/article.md +++ b/9-regular-expressions/03-regexp-unicode/article.md @@ -142,7 +142,7 @@ Let's use it to look for prices in the format "currency, followed by a digit": ```js run let regexp = /\p{Sc}\d/gu; -let str = `Prices: $2, €1, ¥9`; +let str = `Prices: $2, €1, ¥9`; alert( str.match(regexp) ); // $2,€1,¥9 ``` diff --git a/9-regular-expressions/08-regexp-character-sets-and-ranges/article.md b/9-regular-expressions/08-regexp-character-sets-and-ranges/article.md index a1b8f896dd..b7b82d45f2 100644 --- a/9-regular-expressions/08-regexp-character-sets-and-ranges/article.md +++ b/9-regular-expressions/08-regexp-character-sets-and-ranges/article.md @@ -88,7 +88,7 @@ alert( str.match(regexp) ); // H,i,你,好,1,2 Of course, we can edit this pattern: add Unicode properties or remove them. Unicode properties are covered in more details in the article . ```warn header="Unicode properties aren't supported in IE" -Unicode properties `pattern:p{…}` are not implemented in IE. If we really need them, we can use library [XRegExp](http://xregexp.com/). +Unicode properties `pattern:p{…}` are not implemented in IE. If we really need them, we can use library [XRegExp](https://xregexp.com/). Or just use ranges of characters in a language that interests us, e.g. `pattern:[а-я]` for Cyrillic letters. ``` diff --git a/9-regular-expressions/10-regexp-greedy-and-lazy/article.md b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md index 2f656479da..e20175075f 100644 --- a/9-regular-expressions/10-regexp-greedy-and-lazy/article.md +++ b/9-regular-expressions/10-regexp-greedy-and-lazy/article.md @@ -8,7 +8,7 @@ Let's take the following task as an example. We have a text and need to replace all quotes `"..."` with guillemet marks: `«...»`. They are preferred for typography in many countries. -For instance: `"Hello, world"` should become `«Hello, world»`. There exist other quotes, such as `„Witam, świat!”` (Polish) or `「你好,世界」` (Chinese), but for our task let's choose `«...»`. +For instance: `"Hello, world"` should become `«Hello, world»`. There exist other quotes, such as `„Witaj, świecie!”` (Polish) or `「你好,世界」` (Chinese), but for our task let's choose `«...»`. The first thing to do is to locate quoted strings, and then we can replace them. diff --git a/9-regular-expressions/11-regexp-groups/04-parse-expression/solution.md b/9-regular-expressions/11-regexp-groups/04-parse-expression/solution.md index ac67519bb5..b238133952 100644 --- a/9-regular-expressions/11-regexp-groups/04-parse-expression/solution.md +++ b/9-regular-expressions/11-regexp-groups/04-parse-expression/solution.md @@ -10,8 +10,8 @@ The full regular expression: `pattern:-?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?`. It has 3 parts, with `pattern:\s*` between them: 1. `pattern:-?\d+(\.\d+)?` - the first number, -1. `pattern:[-+*/]` - the operator, -1. `pattern:-?\d+(\.\d+)?` - the second number. +2. `pattern:[-+*/]` - the operator, +3. `pattern:-?\d+(\.\d+)?` - the second number. To make each of these parts a separate element of the result array, let's enclose them in parentheses: `pattern:(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)`. @@ -54,3 +54,17 @@ function parse(expr) { alert( parse("-1.23 * 3.45") ); // -1.23, *, 3.45 ``` + +As an alternative to using the non-capturing `?:`, we could name the groups, like this: + +```js run +function parse(expr) { + let regexp = /(?-?\d+(?:\.\d+)?)\s*(?[-+*\/])\s*(?-?\d+(?:\.\d+)?)/; + + let result = expr.match(regexp); + + return [result.groups.a, result.groups.operator, result.groups.b]; +} + +alert( parse("-1.23 * 3.45") ); // -1.23, *, 3.45; +``` \ No newline at end of file diff --git a/9-regular-expressions/13-regexp-alternation/03-match-quoted-string/task.md b/9-regular-expressions/13-regexp-alternation/03-match-quoted-string/task.md index ad41d91b18..27b6bc5c80 100644 --- a/9-regular-expressions/13-regexp-alternation/03-match-quoted-string/task.md +++ b/9-regular-expressions/13-regexp-alternation/03-match-quoted-string/task.md @@ -2,7 +2,7 @@ Create a regexp to find strings in double quotes `subject:"..."`. -The strings should support escaping, the same way as JavaScript strings do. For instance, quotes can be inserted as `subject:\"` a newline as `subject:\n`, and the slash itself as `subject:\\`. +The strings should support escaping, the same way as JavaScript strings do. For instance, quotes can be inserted as `subject:\"` a newline as `subject:\n`, and the backslash itself as `subject:\\`. ```js let str = "Just like \"here\"."; @@ -18,11 +18,11 @@ Examples of strings to match: ```js .. *!*"test me"*/!* .. .. *!*"Say \"Hello\"!"*/!* ... (escaped quotes inside) -.. *!*"\\"*/!* .. (double slash inside) -.. *!*"\\ \""*/!* .. (double slash and an escaped quote inside) +.. *!*"\\"*/!* .. (double backslash inside) +.. *!*"\\ \""*/!* .. (double backslash and an escaped quote inside) ``` -In JavaScript we need to double the slashes to pass them right into the string, like this: +In JavaScript we need to double the backslashes to pass them right into the string, like this: ```js run let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. '; diff --git a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md index b6e1054fbe..c1d4040f76 100644 --- a/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md +++ b/9-regular-expressions/15-regexp-catastrophic-backtracking/article.md @@ -283,7 +283,7 @@ alert( "JavaScript".match(/(?=(\w+))\1Script/)); // null We can put a more complex regular expression into `pattern:(?=(\w+))\1` instead of `pattern:\w`, when we need to forbid backtracking for `pattern:+` after it. ```smart -There's more about the relation between possessive quantifiers and lookahead in articles [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](http://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) and [Mimicking Atomic Groups](http://blog.stevenlevithan.com/archives/mimic-atomic-groups). +There's more about the relation between possessive quantifiers and lookahead in articles [Regex: Emulate Atomic Grouping (and Possessive Quantifiers) with LookAhead](https://instanceof.me/post/52245507631/regex-emulate-atomic-grouping-with-lookahead) and [Mimicking Atomic Groups](https://blog.stevenlevithan.com/archives/mimic-atomic-groups). ``` Let's rewrite the first example using lookahead to prevent backtracking: diff --git a/9-regular-expressions/17-regexp-methods/article.md b/9-regular-expressions/17-regexp-methods/article.md index 0cafbbebbd..897d0ffb64 100644 --- a/9-regular-expressions/17-regexp-methods/article.md +++ b/9-regular-expressions/17-regexp-methods/article.md @@ -232,10 +232,10 @@ Using a function gives us the ultimate replacement power, because it gets all th This method is essentially the same as `str.replace`, with two major differences: -1. If the first argument is a string, it replaces *all occurences* of the string, while `replace` replaces only the *first occurence*. +1. If the first argument is a string, it replaces *all occurrences* of the string, while `replace` replaces only the *first occurrence*. 2. If the first argument is a regular expression without the `g` flag, there'll be an error. With `g` flag, it works the same as `replace`. -The main use case for `replaceAll` is replacing all occurences of a string. +The main use case for `replaceAll` is replacing all occurrences of a string. Like this: