diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..490051876
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: iliakan
diff --git a/.gitignore b/.gitignore
index 6f90fd190..1a71fb7c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ sftp-config.json
Thumbs.db
+/svgs
\ No newline at end of file
diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md
index f81e52822..2f4f518f3 100644
--- a/1-js/01-getting-started/1-intro/article.md
+++ b/1-js/01-getting-started/1-intro/article.md
@@ -24,18 +24,18 @@ The browser has an embedded engine sometimes called a "JavaScript virtual machin
Different engines have different "codenames". For example:
-- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome and Opera.
+- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome, Opera and Edge.
- [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- in Firefox.
- ...There are other codenames like "Chakra" for IE, "JavaScriptCore", "Nitro" and "SquirrelFish" for Safari, etc.
-The terms above are good to remember because they are used in developer articles on the internet. We'll use them too. For instance, if "a feature X is supported by V8", then it probably works in Chrome and Opera.
+The terms above are good to remember because they are used in developer articles on the internet. We'll use them too. For instance, if "a feature X is supported by V8", then it probably works in Chrome, Opera and Edge.
```smart header="How do engines work?"
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 the 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.

-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?
@@ -86,13 +86,13 @@ There are at least *three* great things about JavaScript:
```compare
+ Full integration with HTML/CSS.
+ Simple things are done simply.
-+ Support by all major browsers and enabled by default.
++ Supported by all major browsers and enabled by default.
```
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,23 +100,23 @@ 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](http://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.
-- [TypeScript](http://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](http://flow.org/) also adds data typing, but in a different way. Developed by Facebook.
+- [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
- JavaScript was initially created as a browser-only language, but it is now used in many other environments as well.
-- Today, JavaScript has a unique position as the most widely-adopted browser language with full integration in HTML/CSS.
+- Today, JavaScript has a unique position as the most widely-adopted browser language, fully integrated with HTML/CSS.
- There are many languages that get "transpiled" to JavaScript and provide certain features. It is recommended to take a look at them, at least briefly, after mastering JavaScript.
diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md
index 2824232bb..3fa243336 100644
--- a/1-js/01-getting-started/2-manuals-specifications/article.md
+++ b/1-js/01-getting-started/2-manuals-specifications/article.md
@@ -1,7 +1,7 @@
# Manuals and specifications
-This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other sources.
+This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other resources.
## Specification
@@ -9,7 +9,7 @@ This book is a *tutorial*. It aims to help you gradually learn the language. But
But being that formalized, it's difficult to understand at first. So if you need the most trustworthy source of information about the language details, the specification is the right place. But it's not for everyday use.
-A new specification version is released every year. In-between these releases, the latest specification draft is at .
+A new specification version is released every year. Between these releases, the latest specification draft is at .
To read about new bleeding-edge features, including those that are "almost standard" (so-called "stage 3"), see proposals at .
@@ -19,9 +19,9 @@ Also, if you're developing for the browser, then there are other specifications
- **MDN (Mozilla) JavaScript Reference** is the main manual with examples and other information. It's great to get in-depth information about individual language functions, methods etc.
- One can find it at .
+ You can find it at .
-Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for `parseInt` function.
+Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for the `parseInt` function.
## Compatibility tables
@@ -29,9 +29,9 @@ JavaScript is a developing language, new features get added regularly.
To see their support among browser-based and other engines, see:
-- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: .
+- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: .
- - a table with language features and engines that support those or don't support.
-All these resources are useful in real-life development, as they contain valuable information about language details, their support etc.
+All these resources are useful in real-life development, as they contain valuable information about language details, their support, etc.
Please remember them (or this page) for the cases when you need in-depth information about a particular feature.
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 d03f03def..ca6194741 100644
--- a/1-js/01-getting-started/3-code-editors/article.md
+++ b/1-js/01-getting-started/3-code-editors/article.md
@@ -13,7 +13,7 @@ An IDE loads the project (which can be many files), allows navigation between fi
If you haven't selected an IDE yet, consider the following options:
- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free).
-- [WebStorm](http://www.jetbrains.com/webstorm/) (cross-platform, paid).
+- [WebStorm](https://www.jetbrains.com/webstorm/) (cross-platform, paid).
For Windows, there's also "Visual Studio", not to be confused with "Visual Studio Code". "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. It's also good at JavaScript. There's also a free version [Visual Studio Community](https://www.visualstudio.com/vs/community/).
@@ -29,13 +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:
-- [Atom](https://atom.io/) (cross-platform, free).
-- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free).
-- [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware).
+- [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
@@ -44,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/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md
index 50926d4f7..bbe8af920 100644
--- a/1-js/01-getting-started/4-devtools/article.md
+++ b/1-js/01-getting-started/4-devtools/article.md
@@ -22,7 +22,7 @@ The developer tools will open on the Console tab by default.
It looks somewhat like this:
-
+
The exact look of developer tools depends on your version of Chrome. It changes from time to time but should be similar.
@@ -49,7 +49,7 @@ The look & feel of them is quite similar. Once you know how to use one of these
Safari (Mac browser, not supported by Windows/Linux) is a little bit special here. We need to enable the "Develop menu" first.
-Open Preferences and go to the "Advanced" pane. There's a checkbox at the bottom:
+Open Settings and go to the "Advanced" pane. There's a checkbox at the bottom:

diff --git a/1-js/01-getting-started/4-devtools/chrome.png b/1-js/01-getting-started/4-devtools/chrome.png
deleted file mode 100644
index 4cb3ea2f4..000000000
Binary files a/1-js/01-getting-started/4-devtools/chrome.png and /dev/null differ
diff --git a/1-js/01-getting-started/4-devtools/chrome.webp b/1-js/01-getting-started/4-devtools/chrome.webp
new file mode 100644
index 000000000..bdf067079
Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome.webp differ
diff --git a/1-js/01-getting-started/4-devtools/chrome@2.webp b/1-js/01-getting-started/4-devtools/chrome@2.webp
new file mode 100644
index 000000000..2aeca5898
Binary files /dev/null and b/1-js/01-getting-started/4-devtools/chrome@2.webp differ
diff --git a/1-js/01-getting-started/4-devtools/chrome@2x.png b/1-js/01-getting-started/4-devtools/chrome@2x.png
deleted file mode 100644
index b87404a8f..000000000
Binary files a/1-js/01-getting-started/4-devtools/chrome@2x.png and /dev/null differ
diff --git a/1-js/01-getting-started/4-devtools/safari.png b/1-js/01-getting-started/4-devtools/safari.png
index 64c7a3f6c..4538827eb 100644
Binary files a/1-js/01-getting-started/4-devtools/safari.png and b/1-js/01-getting-started/4-devtools/safari.png differ
diff --git a/1-js/01-getting-started/4-devtools/safari@2x.png b/1-js/01-getting-started/4-devtools/safari@2x.png
index 27def4d09..1561b2bd9 100644
Binary files a/1-js/01-getting-started/4-devtools/safari@2x.png and b/1-js/01-getting-started/4-devtools/safari@2x.png differ
diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md
index fa935f341..35f82bf5d 100644
--- a/1-js/02-first-steps/01-hello-world/article.md
+++ b/1-js/02-first-steps/01-hello-world/article.md
@@ -73,7 +73,7 @@ Script files are attached to HTML with the `src` attribute:
```
-Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="script.js"` would mean a file `"script.js"` in the current folder.
+Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="script.js"`, just like `src="./script.js"`, would mean a file `"script.js"` in the current folder.
We can give a full URL as well. For instance:
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 5fd18f90a..f3c208a74 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 03eeaa6d0..e91d0ea7e 100644
--- a/1-js/02-first-steps/04-variables/article.md
+++ b/1-js/02-first-steps/04-variables/article.md
@@ -24,7 +24,7 @@ Now, we can put some data into it by using the assignment operator `=`:
let message;
*!*
-message = 'Hello'; // store the string
+message = 'Hello'; // store the string 'Hello' in the variable named message
*/!*
```
@@ -64,6 +64,7 @@ let message = 'Hello';
```
Some people also define multiple variables in this multiline style:
+
```js no-beautify
let user = 'John',
age = 25,
@@ -87,22 +88,23 @@ In older scripts, you may also find another keyword: `var` instead of `let`:
*!*var*/!* message = 'Hello';
```
-The `var` keyword is *almost* the same as `let`. It also declares a variable, but in a slightly different, "old-school" way.
+The `var` keyword is *almost* the same as `let`. It also declares a variable but in a slightly different, "old-school" way.
-There are subtle differences between `let` and `var`, but they do not matter for us yet. We'll cover them in detail in the chapter .
+There are subtle differences between `let` and `var`, but they do not matter to us yet. We'll cover them in detail in the chapter .
````
## A real-life analogy
We can easily grasp the concept of a "variable" if we imagine it as a "box" for data, with a uniquely-named sticker on it.
-For instance, the variable `message` can be imagined as a box labeled `"message"` with the value `"Hello!"` in it:
+For instance, the variable `message` can be imagined as a box labelled `"message"` with the value `"Hello!"` in it:

We can put any value in the box.
We can also change it as many times as we want:
+
```js run
let message;
@@ -149,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]
@@ -192,18 +194,18 @@ let my-name; // hyphens '-' aren't allowed in the name
```
```smart header="Case matters"
-Variables named `apple` and `AppLE` are two different variables.
+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 имя = '...';
let 我 = '...';
```
-Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it some time.
+Technically, there is no error here. Such names are allowed, but there is an international convention to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it sometime.
````
````warn header="Reserved names"
@@ -258,12 +260,11 @@ const myBirthday = '18.04.1982';
myBirthday = '01.01.2001'; // error, can't reassign the constant!
```
-When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and clearly communicate that fact to everyone.
-
+When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and communicate that fact to everyone.
### Uppercase constants
-There is a widespread practice to use constants as aliases for difficult-to-remember values that are known prior to execution.
+There is a widespread practice to use constants as aliases for difficult-to-remember values that are known before execution.
Such constants are named using capital letters and underscores.
@@ -288,16 +289,17 @@ Benefits:
When should we use capitals for a constant and when should we name it normally? Let's make that clear.
-Being a "constant" just means that a variable's value never changes. But there are constants that are known prior to execution (like a hexadecimal value for red) and there are constants that are *calculated* in run-time, during the execution, but do not change after their initial assignment.
+Being a "constant" just means that a variable's value never changes. But some constants are known before execution (like a hexadecimal value for red) and some constants are *calculated* in run-time, during the execution, but do not change after their initial assignment.
For instance:
+
```js
const pageLoadTime = /* time taken by a webpage to load */;
```
-The value of `pageLoadTime` is not known prior to the page load, so it's named normally. But it's still a constant because it doesn't change after assignment.
+The value of `pageLoadTime` is not known before the page load, so it's named normally. But it's still a constant because it doesn't change after the assignment.
-In other words, capital-named constants are only used as aliases for "hard-coded" values.
+In other words, capital-named constants are only used as aliases for "hard-coded" values.
## Name things right
@@ -305,18 +307,18 @@ Talking about variables, there's one more extremely important thing.
A variable name should have a clean, obvious meaning, describing the data that it stores.
-Variable naming is one of the most important and complex skills in programming. A quick glance at variable names can reveal which code was written by a beginner versus an experienced developer.
+Variable naming is one of the most important and complex skills in programming. A glance at variable names can reveal which code was written by a beginner versus an experienced developer.
-In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labeled. Or, in other words, when the variables have good names.
+In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labelled. Or, in other words, when the variables have good names.
Please spend time thinking about the right name for a variable before declaring it. Doing so will repay you handsomely.
Some good-to-follow rules are:
- Use human-readable names like `userName` or `shoppingCart`.
-- Stay away from abbreviations or short names like `a`, `b`, `c`, unless you really know what you're doing.
+- Stay away from abbreviations or short names like `a`, `b`, and `c`, unless you know what you're doing.
- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing.
-- Agree on terms within your team and in your own mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`.
+- Agree on terms within your team and in your mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`.
Sounds simple? Indeed it is, but creating descriptive and concise variable names in practice is not. Go for it.
diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md
index 9fba1d8ad..04e8b2450 100644
--- a/1-js/02-first-steps/05-types/article.md
+++ b/1-js/02-first-steps/05-types/article.md
@@ -46,13 +46,15 @@ Besides regular numbers, there are so-called "special numeric values" which also
alert( "not a number" / 2 ); // NaN, such division is erroneous
```
- `NaN` is sticky. Any further operation on `NaN` returns `NaN`:
+ `NaN` is sticky. Any further mathematical operation on `NaN` returns `NaN`:
```js run
- alert( "not a number" / 2 + 5 ); // NaN
+ alert( NaN + 1 ); // NaN
+ alert( 3 * NaN ); // NaN
+ alert( "not a number" / 2 - 1 ); // NaN
```
- So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result.
+ So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result (there's only one exception to that: `NaN ** 0` is `1`).
```smart header="Mathematical operations are safe"
Doing maths is "safe" in JavaScript. We can do anything: divide by zero, treat non-numeric strings as numbers, etc.
@@ -66,9 +68,20 @@ We'll see more about working with numbers in the chapter .
## BigInt [#bigint-type]
-In JavaScript, the "number" type cannot represent integer values larger than (253-1)
(that's `9007199254740991`), or less than -(253-1)
for negatives. It's a technical limitation caused by their internal representation.
+In JavaScript, the "number" type cannot safely represent integer values larger than (253-1)
(that's `9007199254740991`), or less than -(253-1)
for negatives.
-For most purposes that's quite enough, but sometimes we need really big numbers, e.g. for cryptography or microsecond-precision timestamps.
+To be really precise, the "number" type can store larger integers (up to 1.7976931348623157 * 10308
), but outside of the safe integer range ±(253-1)
there'll be a precision error, because not all digits fit into the fixed 64-bit storage. So an "approximate" value may be stored.
+
+For example, these two numbers (right above the safe range) are the same:
+
+```js
+console.log(9007199254740991 + 1); // 9007199254740992
+console.log(9007199254740991 + 2); // 9007199254740992
+```
+
+So to say, all odd integers greater than (253-1)
can't be stored at all in the "number" type.
+
+For most purposes ±(253-1)
range is quite enough, but sometimes we need the entire range of really big integers, e.g. for cryptography or microsecond-precision timestamps.
`BigInt` type was recently added to the language to represent integers of arbitrary length.
@@ -81,13 +94,6 @@ const bigInt = 1234567890123456789012345678901234567890n;
As `BigInt` numbers are rarely needed, we don't cover them here, but devoted them a separate chapter . Read it when you need such big numbers.
-
-```smart header="Compatibility issues"
-Right now, `BigInt` is supported in Firefox/Chrome/Edge/Safari, but not in IE.
-```
-
-You can check [*MDN* BigInt compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) to know which versions of a browser are supported.
-
## String
A string in JavaScript must be surrounded by quotes.
@@ -211,16 +217,9 @@ 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.
-
-It supports two forms of syntax:
-
-1. As an operator: `typeof x`.
-2. As a function: `typeof(x)`.
+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.
-In other words, it works with parentheses or without them. The result is the same.
-
-The call to `typeof x` returns a string with the type name:
+A call to `typeof x` returns a string with the type name:
```js
typeof undefined // "undefined"
@@ -251,25 +250,37 @@ typeof alert // "function" (3)
The last three lines may need additional explanation:
1. `Math` is a built-in object that provides mathematical operations. We will learn it in the chapter . Here, it serves just as an example of an object.
-2. The result of `typeof null` is `"object"`. That's an officially recognized error in `typeof` behavior, coming from the early days of JavaScript and kept for compatibility. Definitely, `null` is not an object. It is a special value with a separate type of its own.
+2. The result of `typeof null` is `"object"`. That's an officially recognized error in `typeof`, coming from very early days of JavaScript and kept for compatibility. Definitely, `null` is not an object. It is a special value with a separate type of its own. The behavior of `typeof` is wrong here.
3. The result of `typeof alert` is `"function"`, because `alert` is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently, returning `"function"`. That also comes from the early days of JavaScript. Technically, such behavior isn't correct, but can be convenient in practice.
+```smart header="The `typeof(x)` syntax"
+You may also come across another syntax: `typeof(x)`. It's the same as `typeof x`.
+
+To put it clear: `typeof` is an operator, not a function. The parentheses here aren't a part of `typeof`. It's the kind of parentheses used for mathematical grouping.
+
+Usually, such parentheses contain a mathematical expression, such as `(2 + 2)`, but here they contain only one argument `(x)`. Syntactically, they allow to avoid a space between the `typeof` operator and its argument, and some people like it.
+
+Some people prefer `typeof(x)`, although the `typeof x` syntax is much more common.
+```
+
## Summary
There are 8 basic data types in JavaScript.
-- `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253-1)
.
-- `bigint` is for integer numbers of arbitrary length.
-- `string` for strings. A string may have zero or more characters, there's no separate single-character type.
-- `boolean` for `true`/`false`.
-- `null` for unknown values -- a standalone type that has a single value `null`.
-- `undefined` for unassigned values -- a standalone type that has a single value `undefined`.
-- `object` for more complex data structures.
-- `symbol` for unique identifiers.
+- Seven primitive data types:
+ - `number` for numbers of any kind: integer or floating-point, integers are limited by ±(253-1)
.
+ - `bigint` for integer numbers of arbitrary length.
+ - `string` for strings. A string may have zero or more characters, there's no separate single-character type.
+ - `boolean` for `true`/`false`.
+ - `null` for unknown values -- a standalone type that has a single value `null`.
+ - `undefined` for unassigned values -- a standalone type that has a single value `undefined`.
+ - `symbol` for unique identifiers.
+- And one non-primitive data type:
+ - `object` for more complex data structures.
The `typeof` operator allows us to see which type is stored in a variable.
-- Two forms: `typeof x` or `typeof(x)`.
+- Usually used as `typeof x`, but `typeof(x)` is also possible.
- Returns a string with the name of the type, like `"string"`.
- For `null` returns `"object"` -- this is an error in the language, it's not actually an object.
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 cf97b3307..329556141 100644
--- a/1-js/02-first-steps/07-type-conversions/article.md
+++ b/1-js/02-first-steps/07-type-conversions/article.md
@@ -7,7 +7,7 @@ For example, `alert` automatically converts any value to a string to show it. Ma
There are also cases when we need to explicitly convert a value to the expected type.
```smart header="Not talking about objects yet"
-In this chapter, we won't cover objects. For now we'll just be talking about primitives.
+In this chapter, we won't cover objects. For now, we'll just be talking about primitives.
Later, after we learn about objects, in the chapter we'll see how objects fit in.
```
@@ -34,7 +34,7 @@ String conversion is mostly obvious. A `false` becomes `"false"`, `null` becomes
## Numeric Conversion
-Numeric conversion happens in mathematical functions and expressions automatically.
+Numeric conversion in mathematical functions and expressions happens automatically.
For example, when division `/` is applied to non-numbers:
@@ -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 dfd061cb6..7370b66af 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 5f611827b..efa230d14 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.
@@ -199,22 +200,22 @@ Here's an extract from the [precedence table](https://developer.mozilla.org/en-U
| Precedence | Name | Sign |
|------------|------|------|
| ... | ... | ... |
-| 17 | unary plus | `+` |
-| 17 | unary negation | `-` |
-| 16 | exponentiation | `**` |
-| 15 | multiplication | `*` |
-| 15 | division | `/` |
-| 13 | addition | `+` |
-| 13 | subtraction | `-` |
+| 14 | unary plus | `+` |
+| 14 | unary negation | `-` |
+| 13 | exponentiation | `**` |
+| 12 | multiplication | `*` |
+| 12 | division | `/` |
+| 11 | addition | `+` |
+| 11 | subtraction | `-` |
| ... | ... | ... |
-| 3 | assignment | `=` |
+| 2 | assignment | `=` |
| ... | ... | ... |
-As we can see, the "unary plus" has a priority of `17` which is higher than the `13` 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
-Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `3`.
+Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `2`.
That's why, when we assign a variable, like `x = 2 * 2 + 1`, the calculations are done first and then the `=` is evaluated, storing the result in `x`.
@@ -308,9 +309,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
@@ -442,7 +443,7 @@ The list of operators:
- RIGHT SHIFT ( `>>` )
- ZERO-FILL RIGHT SHIFT ( `>>>` )
-These operators are used very rarely, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise) chapter on MDN when a need arises.
+These operators are used very rarely, when we need to fiddle with numbers on the very lowest (bitwise) level. We won't need these operators any time soon, as web development has little use of them, but in some special areas, such as cryptography, they are useful. You can read the [Bitwise Operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) chapter on MDN when a need arises.
## Comma
diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md
index ead7922ff..a69317fee 100644
--- a/1-js/02-first-steps/09-comparison/article.md
+++ b/1-js/02-first-steps/09-comparison/article.md
@@ -7,7 +7,7 @@ In JavaScript they are written like this:
- Greater/less than: a > b
, a < b
.
- Greater/less than or equals: a >= b
, a <= b
.
- Equals: `a == b`, please note the double equality sign `==` means the equality test, while a single one `a = b` means an assignment.
-- Not equals. In maths the notation is ≠
, but in JavaScript it's written as a != b
.
+- Not equals: In maths the notation is ≠
, but in JavaScript it's written as a != b
.
In this article we'll learn more about different types of comparisons, how JavaScript makes them, including important peculiarities.
diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md
index 51514062f..82e8800b9 100644
--- a/1-js/02-first-steps/10-ifelse/article.md
+++ b/1-js/02-first-steps/10-ifelse/article.md
@@ -68,7 +68,7 @@ if (cond) {
## The "else" clause
-The `if` statement may contain an optional "else" block. It executes when the condition is 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/11-logical-operators/3-alert-1-null-2/solution.md b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
index 5c2455ef4..368b59409 100644
--- a/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
+++ b/1-js/02-first-steps/11-logical-operators/3-alert-1-null-2/solution.md
@@ -1,6 +1,6 @@
The answer: `null`, because it's the first falsy value from the list.
```js run
-alert( 1 && null && 2 );
+alert(1 && null && 2);
```
diff --git a/1-js/02-first-steps/11-logical-operators/article.md b/1-js/02-first-steps/11-logical-operators/article.md
index eb491d9f4..24cf31420 100644
--- a/1-js/02-first-steps/11-logical-operators/article.md
+++ b/1-js/02-first-steps/11-logical-operators/article.md
@@ -123,7 +123,7 @@ This leads to some interesting usage compared to a "pure, classical, boolean-onl
It means that `||` processes its arguments until the first truthy value is reached, and then the value is returned immediately, without even touching the other argument.
- That importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call.
+ The importance of this feature becomes obvious if an operand isn't just a value, but an expression with a side effect, such as a variable assignment or a function call.
In the example below, only the second message is printed:
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 b84dff892..0b2f092ab 100644
--- a/1-js/02-first-steps/12-nullish-coalescing-operator/article.md
+++ b/1-js/02-first-steps/12-nullish-coalescing-operator/article.md
@@ -4,7 +4,7 @@
The nullish coalescing operator is written as two question marks `??`.
-As it treats `null` and `undefined` similarly, we'll use a special term here, in this article. We'll say that an expression is "defined" when it's neither `null` nor `undefined`.
+As it treats `null` and `undefined` similarly, we'll use a special term here, in this article. For brevity, we'll say that a value is "defined" when it's neither `null` nor `undefined`.
The result of `a ?? b` is:
- if `a` is defined, then `a`,
@@ -22,14 +22,14 @@ result = (a !== null && a !== undefined) ? a : b;
Now it should be absolutely clear what `??` does. Let's see where it helps.
-The common use case for `??` is to provide a default value for a potentially undefined variable.
+The common use case for `??` is to provide a default value.
-For example, here we show `user` if defined, otherwise `Anonymous`:
+For example, here we show `user` if its value isn't `null/undefined`, otherwise `Anonymous`:
```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,14 +37,14 @@ 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`.
-Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be not defined, if the user decided not to enter a value.
+Let's say we have a user's data in variables `firstName`, `lastName` or `nickName`. All of them may be not defined, if the user decided not to fill in the corresponding values.
-We'd like to display the user name using one of these variables, or show "Anonymous" if all of them aren't defined.
+We'd like to display the user name using one of these variables, or show "Anonymous" if all of them are `null/undefined`.
Let's use the `??` operator for that:
@@ -76,7 +76,7 @@ alert(firstName || lastName || nickName || "Anonymous"); // Supercoder
*/!*
```
-Historically, the OR `||` operator was there first. It exists since the beginning of JavaScript, so developers were using it for such purposes for a long time.
+Historically, the OR `||` operator was there first. It's been there since the beginning of JavaScript, so developers were using it for such purposes for a long time.
On the other hand, the nullish coalescing operator `??` was added to JavaScript only recently, and the reason for that was that people weren't quite happy with `||`.
@@ -106,11 +106,11 @@ In practice, the zero height is often a valid value, that shouldn't be replaced
## Precedence
-The precedence of the `??` operator is about the same as `||`, just a bit lower. It equals `5` in the [MDN table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table), while `||` is `6`.
+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 `+`, `*`.
-So if we'd like to choose a value with `??` in an expression with other operators, consider adding parentheses:
+So we may need to add parentheses in expressions like this:
```js run
let height = null;
@@ -128,7 +128,7 @@ Otherwise, if we omit parentheses, then as `*` has the higher precedence than `?
// without parentheses
let area = height ?? 100 * width ?? 50;
-// ...works the same as this (probably not what we want):
+// ...works this way (not what we want):
let area = height ?? (100 * width) ?? 50;
```
diff --git a/1-js/02-first-steps/13-while-for/article.md b/1-js/02-first-steps/13-while-for/article.md
index 2579b647d..d1b749888 100644
--- a/1-js/02-first-steps/13-while-for/article.md
+++ b/1-js/02-first-steps/13-while-for/article.md
@@ -6,6 +6,19 @@ For example, outputting goods from a list one after another or just running the
*Loops* are a way to repeat the same code multiple times.
+```smart header="The for..of and for..in loops"
+A small announcement for advanced readers.
+
+This article covers only basic loops: `while`, `do..while` and `for(..;..;..)`.
+
+If you came to this article searching for other types of loops, here are the pointers:
+
+- See [for..in](info:object#forin) to loop over object properties.
+- See [for..of](info:array#loops) and [iterables](info:iterable) for looping over arrays and iterable objects.
+
+Otherwise, please read on.
+```
+
## The "while" loop
The `while` loop has the following syntax:
@@ -106,7 +119,7 @@ Let's examine the `for` statement part-by-part:
| part | | |
|-------|----------|----------------------------------------------------------------------------|
-| begin | `i = 0` | Executes once upon entering the loop. |
+| begin | `let i = 0` | Executes once upon entering the loop. |
| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. |
| body | `alert(i)`| Runs again and again while the condition is truthy. |
| step| `i++` | Executes after the body on each iteration. |
@@ -162,10 +175,8 @@ for (i = 0; i < 3; i++) { // use an existing variable
alert(i); // 3, visible, because declared outside of the loop
```
-
````
-
### Skipping parts
Any part of `for` can be skipped.
@@ -268,7 +279,7 @@ for (let i = 0; i < 10; i++) {
From a technical point of view, this is identical to the example above. Surely, we can just wrap the code in an `if` block instead of using `continue`.
-But as a side-effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability.
+But as a side effect, this created one more level of nesting (the `alert` call inside the curly braces). If the code inside of `if` is longer than a few lines, that may decrease the overall readability.
````
````warn header="No `break/continue` to the right side of '?'"
@@ -286,7 +297,6 @@ if (i > 5) {
...and rewrite it using a question mark:
-
```js no-beautify
(i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here
```
@@ -321,6 +331,7 @@ We need a way to stop the process if the user cancels the input.
The ordinary `break` after `input` would only break the inner loop. That's not sufficient -- labels, come to the rescue!
A *label* is an identifier with a colon before a loop:
+
```js
labelName: for (...) {
...
@@ -342,6 +353,7 @@ The `break ` statement in the loop below breaks out to the label:
// do something with the value...
}
}
+
alert('Done!');
```
@@ -362,6 +374,7 @@ The `continue` directive can also be used with a label. In this case, code execu
Labels do not allow us to jump into an arbitrary place in the code.
For example, it is impossible to do this:
+
```js
break label; // jump to the label below (doesn't work)
@@ -369,6 +382,7 @@ label: for (...)
```
A `break` directive must be inside a code block. Technically, any labelled code block will do, e.g.:
+
```js
label: {
// ...
@@ -377,7 +391,7 @@ label: {
}
```
-...Although, 99.9% of the time `break` used is inside loops, as we've seen in the examples above.
+...Although, 99.9% of the time `break` is used inside loops, as we've seen in the examples above.
A `continue` is only possible from inside a loop.
````
diff --git a/1-js/02-first-steps/14-switch/article.md b/1-js/02-first-steps/14-switch/article.md
index effdafcf9..d86babcec 100644
--- a/1-js/02-first-steps/14-switch/article.md
+++ b/1-js/02-first-steps/14-switch/article.md
@@ -139,7 +139,7 @@ switch (a) {
Now both `3` and `5` show the same message.
-The ability to "group" cases is a side-effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`.
+The ability to "group" cases is a side effect of how `switch/case` works without `break`. Here the execution of `case 3` starts from the line `(*)` and goes through `case 5`, because there's no `break`.
## Type matters
diff --git a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md
index e41c80418..e3a0df77c 100644
--- a/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md
+++ b/1-js/02-first-steps/15-function-basics/1-if-else-required/solution.md
@@ -1 +1,3 @@
-No difference.
\ No newline at end of file
+No difference!
+
+In both cases, `return confirm('Did parents allow you?')` executes exactly when the `if` condition is falsy.
\ No newline at end of file
diff --git a/1-js/02-first-steps/15-function-basics/article.md b/1-js/02-first-steps/15-function-basics/article.md
index ac6f1f47c..415fed3e0 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
}
```
@@ -176,12 +176,12 @@ When a value is passed as a function parameter, it's also called an *argument*.
In other words, to put these terms straight:
-- A parameter is the variable listed inside the parentheses in the function declaration (it's a declaration time term)
+- A parameter is the variable listed inside the parentheses in the function declaration (it's a declaration time term).
- An argument is the value that is passed to the function when it is called (it's a call time term).
We declare functions listing their parameters, then call them passing arguments.
-In the example above, one might say: "the function `sayMessage` is declared with two parameters, then called with two arguments: `from` and `"Hello"`".
+In the example above, one might say: "the function `showMessage` is declared with two parameters, then called with two arguments: `from` and `"Hello"`".
## Default values
@@ -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:
@@ -225,9 +231,41 @@ In the example above, `anotherFunction()` isn't called at all, if the `text` par
On the other hand, it's independently called every time when `text` is missing.
```
+````smart header="Default parameters in old JavaScript code"
+Several years ago, JavaScript didn't support the syntax for default parameters. So people used other ways to specify them.
+
+Nowadays, we can come across them in old scripts.
+
+For example, an explicit check for `undefined`:
+
+```js
+function showMessage(from, text) {
+*!*
+ if (text === undefined) {
+ text = 'no text given';
+ }
+*/!*
+
+ alert( from + ": " + text );
+}
+```
+
+...Or using the `||` operator:
+
+```js
+function showMessage(from, text) {
+ // If the value of text is falsy, assign the default value
+ // this assumes that text == "" is the same as no text at all
+ text = text || 'no text given';
+ ...
+}
+```
+````
+
+
### Alternative default parameters
-Sometimes it makes sense to 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`:
@@ -247,7 +285,7 @@ function showMessage(text) {
showMessage(); // empty message
```
-...Or we could use the `??` operator:
+...Or we could use the `||` operator:
```js
function showMessage(text) {
@@ -422,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.
```
@@ -490,7 +528,7 @@ function name(parameters, delimited, by, comma) {
To make the code clean and easy to understand, it's recommended to use mainly local variables and parameters in the function, not outer variables.
-It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side-effect.
+It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side effect.
Function naming:
diff --git a/1-js/02-first-steps/16-function-expressions/article.md b/1-js/02-first-steps/16-function-expressions/article.md
index a8ccd6c6c..c6dd891bd 100644
--- a/1-js/02-first-steps/16-function-expressions/article.md
+++ b/1-js/02-first-steps/16-function-expressions/article.md
@@ -12,7 +12,9 @@ function sayHi() {
There is another syntax for creating a function that is called a *Function Expression*.
-It looks like this:
+It allows us to create a new function in the middle of any expression.
+
+For example:
```js
let sayHi = function() {
@@ -20,9 +22,19 @@ let sayHi = function() {
};
```
-Here, the function is created and assigned to the variable explicitly, like any other value. No matter how the function is defined, it's just a value stored in the variable `sayHi`.
+Here we can see a variable `sayHi` getting a value, the new function, created as `function() { alert("Hello"); }`.
+
+As the function creation happens in the context of the assignment expression (to the right side of `=`), this is a *Function Expression*.
+
+Please note, there's no name after the `function` keyword. Omitting a name is allowed for Function Expressions.
+
+Here we immediately assign it to the variable, so the meaning of these code samples is the same: "create a function and put it into the variable `sayHi`".
+
+In more advanced situations, that we'll come across later, a function may be created and immediately called or scheduled for a later execution, not stored anywhere, thus remaining anonymous.
-The meaning of these code samples is the same: "create a function and put it into the variable `sayHi`".
+## Function is a value
+
+Let's reiterate: no matter how the function is created, a function is a value. Both examples above store a function in the `sayHi` variable.
We can even print out that value using `alert`:
@@ -63,14 +75,14 @@ Here's what happens above in detail:
2. Line `(2)` copies it into the variable `func`. Please note again: there are no parentheses after `sayHi`. If there were, then `func = sayHi()` would write *the result of the call* `sayHi()` into `func`, not *the function* `sayHi` itself.
3. Now the function can be called as both `sayHi()` and `func()`.
-Note that we could also have used a Function Expression to declare `sayHi`, in the first line:
+We could also have used a Function Expression to declare `sayHi`, in the first line:
```js
-let sayHi = function() {
+let sayHi = function() { // (1) create
alert( "Hello" );
};
-let func = sayHi;
+let func = sayHi; //(2)
// ...
```
@@ -78,7 +90,7 @@ Everything would work the same.
````smart header="Why is there a semicolon at the end?"
-You might wonder, why does Function Expression have a semicolon `;` at the end, but Function Declaration does not:
+You might wonder, why do Function Expressions have a semicolon `;` at the end, but Function Declarations do not:
```js
function sayHi() {
@@ -90,9 +102,9 @@ let sayHi = function() {
}*!*;*/!*
```
-The answer is simple:
-- There's no need for `;` at the end of code blocks and syntax structures that use them like `if { ... }`, `for { }`, `function f { }` etc.
-- A Function Expression is used inside the statement: `let sayHi = ...;`, as a value. It's not a code block, but rather an assignment. The semicolon `;` is recommended at the end of statements, no matter what the value is. So the semicolon here is not related to the Function Expression itself, it just terminates the statement.
+The answer is simple: a Function Expression is created here as `function(…) {…}` inside the assignment statement: `let sayHi = …;`. The semicolon `;` is recommended at the end of the statement, it's not a part of the function syntax.
+
+The semicolon would be there for a simpler assignment, such as `let sayHi = 5;`, and it's also there for a function assignment.
````
## Callback functions
@@ -132,13 +144,13 @@ function showCancel() {
ask("Do you agree?", showOk, showCancel);
```
-In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such function usually draws a nice-looking question window. But that's another story.
+In practice, such functions are quite useful. The major difference between a real-life `ask` and the example above is that real-life functions use more complex ways to interact with the user than a simple `confirm`. In the browser, such functions usually draw a nice-looking question window. But that's another story.
**The arguments `showOk` and `showCancel` of `ask` are called *callback functions* or just *callbacks*.**
The idea is that we pass a function and expect it to be "called back" later if necessary. In our case, `showOk` becomes the callback for "yes" answer, and `showCancel` for "no" answer.
-We can use Function Expressions to write the same function much shorter:
+We can use Function Expressions to write an equivalent, shorter function:
```js run no-beautify
function ask(question, yes, no) {
@@ -174,7 +186,7 @@ Let's formulate the key differences between Function Declarations and Expression
First, the syntax: how to differentiate between them in the code.
-- *Function Declaration:* a function, declared as a separate statement, in the main code flow.
+- *Function Declaration:* a function, declared as a separate statement, in the main code flow:
```js
// Function Declaration
@@ -182,7 +194,7 @@ First, the syntax: how to differentiate between them in the code.
return a + b;
}
```
-- *Function Expression:* a function, created inside an expression or inside another syntax construct. Here, the function is created at the right side of the "assignment expression" `=`:
+- *Function Expression:* a function, created inside an expression or inside another syntax construct. Here, the function is created on the right side of the "assignment expression" `=`:
```js
// Function Expression
@@ -279,7 +291,7 @@ if (age < 18) {
welcome(); // \ (runs)
*/!*
// |
- function welcome() { // |
+ function welcome() { // |
alert("Hello!"); // | Function Declaration is available
} // | everywhere in the block where it's declared
// |
@@ -289,7 +301,7 @@ if (age < 18) {
} else {
- function welcome() {
+ function welcome() {
alert("Greetings!");
}
}
@@ -348,7 +360,7 @@ welcome(); // ok now
```smart header="When to choose Function Declaration versus Function Expression?"
-As a rule of thumb, when we need to declare a function, the first to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared.
+As a rule of thumb, when we need to declare a function, the first thing to consider is Function Declaration syntax. It gives more freedom in how to organize our code, because we can call such functions before they are declared.
That's also better for readability, as it's easier to look up `function f(…) {…}` in the code than `let f = function(…) {…};`. Function Declarations are more "eye-catching".
diff --git a/1-js/02-first-steps/17-arrow-functions-basics/article.md b/1-js/02-first-steps/17-arrow-functions-basics/article.md
index 1b6da9bfe..50c0d475d 100644
--- a/1-js/02-first-steps/17-arrow-functions-basics/article.md
+++ b/1-js/02-first-steps/17-arrow-functions-basics/article.md
@@ -5,10 +5,10 @@ There's another very simple and concise syntax for creating functions, that's of
It's called "arrow functions", because it looks like this:
```js
-let func = (arg1, arg2, ..., argN) => expression
+let func = (arg1, arg2, ..., argN) => expression;
```
-...This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result.
+This creates a function `func` that accepts arguments `arg1..argN`, then evaluates the `expression` on the right side with their use and returns its result.
In other words, it's the shorter version of:
@@ -33,7 +33,7 @@ let sum = function(a, b) {
alert( sum(1, 2) ); // 3
```
-As you can, see `(a, b) => a + b` means a function that accepts two arguments named `a` and `b`. Upon the execution, it evaluates the expression `a + b` and returns the result.
+As you can see, `(a, b) => a + b` means a function that accepts two arguments named `a` and `b`. Upon the execution, it evaluates the expression `a + b` and returns the result.
- If we have only one argument, then parentheses around parameters can be omitted, making that even shorter.
@@ -48,7 +48,7 @@ As you can, see `(a, b) => a + b` means a function that accepts two arguments na
alert( double(3) ); // 6
```
-- If there are no arguments, parentheses will be empty (but they should be present):
+- If there are no arguments, parentheses are empty, but they must be present:
```js run
let sayHi = () => alert("Hello!");
@@ -64,7 +64,7 @@ For instance, to dynamically create a function:
let age = prompt("What is your age?", 18);
let welcome = (age < 18) ?
- () => alert('Hello') :
+ () => alert('Hello!') :
() => alert("Greetings!");
welcome();
@@ -76,9 +76,9 @@ They are very convenient for simple one-line actions, when we're just too lazy t
## Multiline arrow functions
-The examples above took arguments from the left of `=>` and evaluated the right-side expression with them.
+The arrow functions that we've seen so far were very simple. They took arguments from the left of `=>`, evaluated and returned the right-side expression with them.
-Sometimes we need something a little bit more complex, like multiple expressions or statements. It is also possible, but we should enclose them in curly braces. Then use a normal `return` within them.
+Sometimes we need a more complex function, with multiple expressions and statements. In that case, we can enclose them in curly braces. The major difference is that curly braces require a `return` within them to return a value (just like a regular function does).
Like this:
@@ -86,7 +86,7 @@ Like this:
let sum = (a, b) => { // the curly brace opens a multiline function
let result = a + b;
*!*
- return result; // if we use curly braces, then we need an explicit "return"
+ return result; // if we use curly braces, then we need an explicit "return"
*/!*
};
@@ -105,7 +105,7 @@ For now, we can already use arrow functions for one-line actions and callbacks.
## Summary
-Arrow functions are handy for one-liners. They come in two flavors:
+Arrow functions are handy for simple actions, especially for one-liners. They come in two flavors:
-1. Without curly braces: `(...args) => expression` -- the right side is an expression: the function evaluates it and returns the result.
+1. Without curly braces: `(...args) => expression` -- the right side is an expression: the function evaluates it and returns the result. Parentheses can be omitted, if there's only a single argument, e.g. `n => n*2`.
2. With curly braces: `(...args) => { body }` -- brackets allow us to write multiple statements inside the function, but we need an explicit `return` to return something.
diff --git a/1-js/02-first-steps/18-javascript-specials/article.md b/1-js/02-first-steps/18-javascript-specials/article.md
index d0ed0ef08..e7ddacac4 100644
--- a/1-js/02-first-steps/18-javascript-specials/article.md
+++ b/1-js/02-first-steps/18-javascript-specials/article.md
@@ -55,7 +55,7 @@ To fully enable all features of modern JavaScript, we should start scripts with
The directive must be at the top of a script or at the beginning of a function body.
-Without `"use strict"`, everything still works, but some features behave in the old-fashion, "compatible" way. We'd generally prefer the modern behavior.
+Without `"use strict"`, everything still works, but some features behave in the old-fashioned, "compatible" way. We'd generally prefer the modern behavior.
Some modern features of the language (like classes that we'll study in the future) enable strict mode implicitly.
@@ -103,13 +103,13 @@ More in: and .
We're using a browser as a working environment, so basic UI functions will be:
-[`prompt(question, [default])`](mdn:api/Window/prompt)
+[`prompt(question, [default])`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt)
: Ask a `question`, and return either what the visitor entered or `null` if they clicked "cancel".
-[`confirm(question)`](mdn:api/Window/confirm)
+[`confirm(question)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)
: Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`.
-[`alert(message)`](mdn:api/Window/alert)
+[`alert(message)`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert)
: Output a `message`.
All these functions are *modal*, they pause the code execution and prevent the visitor from interacting with the page until they answer.
@@ -144,7 +144,7 @@ Assignments
: There is a simple assignment: `a = b` and combined ones like `a *= 2`.
Bitwise
-: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](mdn:/JavaScript/Guide/Expressions_and_Operators#Bitwise) when they are needed.
+: Bitwise operators work with 32-bit integers at the lowest, bit-level: see the [docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#bitwise_operators) when they are needed.
Conditional
: The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`.
@@ -256,7 +256,7 @@ We covered three ways to create a function in JavaScript:
3. Arrow functions:
```js
- // expression at the right side
+ // expression on the right side
let sum = (a, b) => a + b;
// or multi-line syntax with { ... }, need return here:
diff --git a/1-js/03-code-quality/01-debugging-chrome/article.md b/1-js/03-code-quality/01-debugging-chrome/article.md
index cf90f5492..4f50fb428 100644
--- a/1-js/03-code-quality/01-debugging-chrome/article.md
+++ b/1-js/03-code-quality/01-debugging-chrome/article.md
@@ -38,7 +38,7 @@ If we press `key:Esc`, then a console opens below. We can type commands there an
After a statement is executed, its result is shown below.
-For example, here `1+2` results in `3`, and `hello("debugger")` returns nothing, so the result is `undefined`:
+For example, here `1+2` results in `3`, while the function call `hello("debugger")` returns nothing, so the result is `undefined`:

@@ -63,12 +63,12 @@ We can always find a list of breakpoints in the right panel. That's useful when
- ...And so on.
```smart header="Conditional breakpoints"
-*Right click* on the line number allows to create a *conditional* breakpoint. It only triggers when the given expression is truthy.
+*Right click* on the line number allows to create a *conditional* breakpoint. It only triggers when the given expression, that you should provide when you create it, is truthy.
That's handy when we need to stop only for a certain variable value or for certain function parameters.
```
-## Debugger command
+## The command "debugger"
We can also pause the code by using the `debugger` command in it, like this:
@@ -84,8 +84,7 @@ function hello(name) {
}
```
-That's very convenient when we are in a code editor and don't want to switch to the browser and look up the script in developer tools to set the breakpoint.
-
+Such command works only when the development tools are open, otherwise the browser ignores it.
## Pause and look around
@@ -99,7 +98,7 @@ Please open the informational dropdowns to the right (labeled with arrows). They
1. **`Watch` -- shows current values for any expressions.**
- You can click the plus `+` and input an expression. The debugger will show its value at any moment, automatically recalculating it in the process of execution.
+ You can click the plus `+` and input an expression. The debugger will show its value, automatically recalculating it in the process of execution.
2. **`Call Stack` -- shows the nested calls chain.**
@@ -135,11 +134,11 @@ There are buttons for it at the top of the right panel. Let's engage them.
Clicking this again and again will step through all script statements one by one.
-- "Step over": run the next command, but *don't go into a function*, hotkey `key:F10`.
-: Similar to the previous "Step" command, but behaves differently if the next statement is a function call. That is: not a built-in, like `alert`, but a function of our own.
+: Similar to the previous "Step" command, but behaves differently if the next statement is a function call (not a built-in, like `alert`, but a function of our own).
- The "Step" command goes into it and pauses the execution at its first line, while "Step over" executes the nested function call invisibly, skipping the function internals.
+ If we compare them, the "Step" command goes into a nested function call and pauses the execution at its first line, while "Step over" executes the nested function call invisibly to us, skipping the function internals.
- The execution is then paused immediately after that function.
+ The execution is then paused immediately after that function call.
That's good if we're not interested to see what happens inside the function call.
@@ -155,7 +154,7 @@ There are buttons for it at the top of the right panel. Let's engage them.
: That button does not move the execution. Just a mass on/off for breakpoints.
-- enable/disable automatic pause in case of an error.
-: When enabled, and the developer tools is open, a script error automatically pauses the execution. Then we can analyze variables to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment.
+: When enabled, if the developer tools is open, an error during the script execution automatically pauses it. Then we can analyze variables in the debugger to see what went wrong. So if our script dies with an error, we can open debugger, enable this option and reload the page to see where it dies and what's the context at that moment.
```smart header="Continue to here"
Right click on a line of code opens the context menu with a great option called "Continue to here".
@@ -187,7 +186,7 @@ As we can see, there are three main ways to pause a script:
2. The `debugger` statements.
3. An error (if dev tools are open and the button is "on").
-When paused, we can debug - examine variables and trace the code to see where the execution goes wrong.
+When paused, we can debug: examine variables and trace the code to see where the execution goes wrong.
There are many more options in developer tools than covered here. The full manual is at .
diff --git a/1-js/03-code-quality/02-coding-style/article.md b/1-js/03-code-quality/02-coding-style/article.md
index 982f41c4d..904f0a939 100644
--- a/1-js/03-code-quality/02-coding-style/article.md
+++ b/1-js/03-code-quality/02-coding-style/article.md
@@ -301,11 +301,11 @@ The great thing about them is that style-checking can also find some bugs, like
Here are some well-known linting tools:
-- [JSLint](http://www.jslint.com/) -- one of the first linters.
-- [JSHint](http://www.jshint.com/) -- more settings than JSLint.
-- [ESLint](http://eslint.org/) -- probably the newest one.
+- [JSLint](https://www.jslint.com/) -- one of the first linters.
+- [JSHint](https://jshint.com/) -- more settings than JSLint.
+- [ESLint](https://eslint.org/) -- probably the newest one.
-All of them can do the job. The author uses [ESLint](http://eslint.org/).
+All of them can do the job. The author uses [ESLint](https://eslint.org/).
Most linters are integrated with many popular editors: just enable the plugin in the editor and configure the style.
@@ -335,7 +335,7 @@ Here's an example of an `.eslintrc` file:
Here the directive `"extends"` denotes that the configuration is based on the "eslint:recommended" set of settings. After that, we specify our own.
-It is also possible to download style rule sets from the web and extend them instead. See for more details about installation.
+It is also possible to download style rule sets from the web and extend them instead. See for more details about installation.
Also certain IDEs have built-in linting, which is convenient but not as customizable as ESLint.
diff --git a/1-js/03-code-quality/03-comments/article.md b/1-js/03-code-quality/03-comments/article.md
index 0d11c6c52..af3a06c80 100644
--- a/1-js/03-code-quality/03-comments/article.md
+++ b/1-js/03-code-quality/03-comments/article.md
@@ -143,7 +143,7 @@ Such comments allow us to understand the purpose of the function and use it the
By the way, many editors like [WebStorm](https://www.jetbrains.com/webstorm/) can understand them as well and use them to provide autocomplete and some automatic code-checking.
-Also, there are tools like [JSDoc 3](https://github.com/jsdoc3/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at .
+Also, there are tools like [JSDoc 3](https://github.com/jsdoc/jsdoc) that can generate HTML-documentation from the comments. You can read more information about JSDoc at .
Why is the task solved this way?
: What's written is important. But what's *not* written may be even more important to understand what's going on. Why is the task solved exactly this way? The code gives no answer.
diff --git a/1-js/03-code-quality/05-testing-mocha/article.md b/1-js/03-code-quality/05-testing-mocha/article.md
index 9037b484d..4c2b1aa5e 100644
--- a/1-js/03-code-quality/05-testing-mocha/article.md
+++ b/1-js/03-code-quality/05-testing-mocha/article.md
@@ -51,7 +51,7 @@ describe("pow", function() {
A spec has three main building blocks that you can see above:
`describe("title", function() { ... })`
-: What functionality we're describing. In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks.
+: What functionality we're describing? In our case we're describing the function `pow`. Used to group "workers" -- the `it` blocks.
`it("use case description", function() { ... })`
: In the title of `it` we *in a human-readable way* describe the particular use case, and the second argument is a function that tests it.
@@ -69,7 +69,7 @@ The flow of development usually looks like this:
1. An initial spec is written, with tests for the most basic functionality.
2. An initial implementation is created.
-3. To check whether it works, we run the testing framework [Mocha](http://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works.
+3. To check whether it works, we run the testing framework [Mocha](https://mochajs.org/) (more details soon) that runs the spec. While the functionality is not complete, errors are displayed. We make corrections until everything works.
4. Now we have a working initial implementation with tests.
5. We add more use cases to the spec, probably not yet supported by the implementations. Tests start to fail.
6. Go to 3, update the implementation till tests give no errors.
@@ -79,15 +79,15 @@ So, the development is *iterative*. We write the spec, implement it, make sure t
Let's see this development flow in our practical case.
-The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use few JavaScript libraries to run the tests, just to see that they are working (they will all fail).
+The first step is already complete: we have an initial spec for `pow`. Now, before making the implementation, let's use a few JavaScript libraries to run the tests, just to see that they are working (they will all fail).
## The spec in action
Here in the tutorial we'll be using the following JavaScript libraries for tests:
-- [Mocha](http://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests.
-- [Chai](http://chaijs.com) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`.
-- [Sinon](http://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later.
+- [Mocha](https://mochajs.org/) -- the core framework: it provides common testing functions including `describe` and `it` and the main function that runs tests.
+- [Chai](https://www.chaijs.com/) -- the library with many assertions. It allows to use a lot of different assertions, for now we need only `assert.equal`.
+- [Sinon](https://sinonjs.org/) -- a library to spy over functions, emulate built-in functions and more, we'll need it much later.
These libraries are suitable for both in-browser and server-side testing. Here we'll consider the browser variant.
@@ -338,14 +338,14 @@ The newly added tests fail, because our implementation does not support them. Th
```smart header="Other assertions"
Please note the assertion `assert.isNaN`: it checks for `NaN`.
-There are other assertions in [Chai](http://chaijs.com) as well, for instance:
+There are other assertions in [Chai](https://www.chaijs.com/) as well, for instance:
- `assert.equal(value1, value2)` -- checks the equality `value1 == value2`.
- `assert.strictEqual(value1, value2)` -- checks the strict equality `value1 === value2`.
- `assert.notEqual`, `assert.notStrictEqual` -- inverse checks to the ones above.
- `assert.isTrue(value)` -- checks that `value === true`
- `assert.isFalse(value)` -- checks that `value === false`
-- ...the full list is in the [docs](http://chaijs.com/api/assert/)
+- ...the full list is in the [docs](https://www.chaijs.com/api/assert/)
```
So we should add a couple of lines to `pow`:
diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md
index 7efaf8677..5ca123908 100644
--- a/1-js/03-code-quality/06-polyfills/article.md
+++ b/1-js/03-code-quality/06-polyfills/article.md
@@ -1,13 +1,13 @@
# Polyfills and transpilers
-The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](http://www.ecma-international.org/publications/standards/Ecma-262.htm).
+The JavaScript language steadily evolves. New proposals to the language appear regularly, they are analyzed and, if considered worthy, are appended to the list at and then progress to the [specification](https://www.ecma-international.org/publications-and-standards/standards/ecma-262/).
Teams behind JavaScript engines have their own ideas about what to implement first. They may decide to implement proposals that are in draft and postpone things that are already in the spec, because they are less interesting or just harder to do.
-So it's quite common for an engine to implement only the part of the standard.
+So it's quite common for an engine to implement only part of the standard.
-A good page to see the current state of support for language features is (it's big, we have a lot to study yet).
+A good page to see the current state of support for language features is (it's big, we have a lot to study yet).
As programmers, we'd like to use most recent features. The more good stuff - the better!
@@ -22,7 +22,7 @@ Here, in this chapter, our purpose is to get the gist of how they work, and thei
## Transpilers
-A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that can parse ("read and understand") modern code, and rewrite it using older syntax constructs, so that the result would be the same.
+A [transpiler](https://en.wikipedia.org/wiki/Source-to-source_compiler) is a special piece of software that translates source code to another source code. It can parse ("read and understand") modern code and rewrite it using older syntax constructs, so that it'll also work in outdated engines.
E.g. JavaScript before year 2020 didn't have the "nullish coalescing operator" `??`. So, if a visitor uses an outdated browser, it may fail to understand the code like `height = height ?? 100`.
@@ -40,9 +40,9 @@ Now the rewritten code is suitable for older JavaScript engines.
Usually, a developer runs the transpiler on their own computer, and then deploys the transpiled code to the server.
-Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there.
+Speaking of names, [Babel](https://babeljs.io) is one of the most prominent transpilers out there.
-Modern project build systems, such as [webpack](http://webpack.github.io/), provide means to run transpiler automatically on every code change, so it's very easy to integrate into development process.
+Modern project build systems, such as [webpack](https://webpack.js.org/), provide a means to run a transpiler automatically on every code change, so it's very easy to integrate into the development process.
## Polyfills
@@ -69,23 +69,20 @@ if (!Math.trunc) { // if no such function
}
```
-JavaScript is a highly dynamic language, scripts may add/modify any functions, even including built-in ones.
-
-Two interesting libraries of polyfills are:
-- [core js](https://github.com/zloirock/core-js) that supports a lot, allows to include only needed features.
-- [polyfill.io](http://polyfill.io) service that provides a script with polyfills, depending on the features and user's browser.
+JavaScript is a highly dynamic language. Scripts may add/modify any function, even built-in ones.
+One interesting polyfill library is [core-js](https://github.com/zloirock/core-js), which supports a wide range of features and allows you to include only the ones you need.
## Summary
In this chapter we'd like to motivate you to study modern and even "bleeding-edge" language features, even if they aren't yet well-supported by JavaScript engines.
-Just don't forget to use transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). And they'll ensure that the code works.
+Just don't forget to use a transpiler (if using modern syntax or operators) and polyfills (to add functions that may be missing). They'll ensure that the code works.
-For example, later when you're familiar with JavaScript, you can setup a code build system based on [webpack](http://webpack.github.io/) with [babel-loader](https://github.com/babel/babel-loader) plugin.
+For example, later when you're familiar with JavaScript, you can setup a code build system based on [webpack](https://webpack.js.org/) with the [babel-loader](https://github.com/babel/babel-loader) plugin.
Good resources that show the current state of support for various features:
-- - for pure JavaScript.
+- - for pure JavaScript.
- - for browser-related functions.
P.S. Google Chrome is usually the most up-to-date with language features, try it if a tutorial demo fails. Most tutorial demos work with any modern browser though.
diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md
index ed8a3f4d7..0fe5979fa 100644
--- a/1-js/04-object-basics/01-object/article.md
+++ b/1-js/04-object-basics/01-object/article.md
@@ -44,7 +44,7 @@ The resulting `user` object can be imagined as a cabinet with two signed files l

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

-To remove a property, we can use `delete` operator:
+To remove a property, we can use the `delete` operator:
```js
delete user.age;
@@ -201,13 +201,13 @@ let bag = {
};
```
-Square brackets are much more powerful than the dot notation. They allow any property names and variables. But they are also more cumbersome to write.
+Square brackets are much more powerful than dot notation. They allow any property names and variables. But they are also more cumbersome to write.
So most of the time, when property names are known and simple, the dot is used. And if we need something more complex, then we switch to square brackets.
## Property value shorthand
-In real code we often use existing variables as values for property names.
+In real code, we often use existing variables as values for property names.
For instance:
@@ -252,7 +252,7 @@ let user = {
## Property names limitations
-As we already know, a variable cannot have a name equal to one of language-reserved words like "for", "let", "return" etc.
+As we already know, a variable cannot have a name equal to one of the language-reserved words like "for", "let", "return" etc.
But for an object property, there's no such restriction:
@@ -325,7 +325,7 @@ alert( "blabla" in user ); // false, user.blabla doesn't exist
Please note that on the left side of `in` there must be a *property name*. That's usually a quoted string.
-If we omit quotes, that means a variable, it should contain the actual name to be tested. For instance:
+If we omit quotes, that means a variable should contain the actual name to be tested. For instance:
```js run
let user = { age: 30 };
@@ -355,7 +355,7 @@ In the code above, the property `obj.test` technically exists. So the `in` opera
Situations like this happen very rarely, because `undefined` should not be explicitly assigned. We mostly use `null` for "unknown" or "empty" values. So the `in` operator is an exotic guest in the code.
-## The "for..in" loop
+## The "for..in" loop [#forin]
To walk over all keys of an object, there exists a special form of the loop: `for..in`. This is a completely different thing from the `for(;;)` construct that we studied before.
@@ -412,7 +412,7 @@ for (let code in codes) {
*/!*
```
-The object may be used to suggest a list of options to the user. If we're making a site mainly for German audience then we probably want `49` to be the first.
+The object may be used to suggest a list of options to the user. If we're making a site mainly for a German audience then we probably want `49` to be the first.
But if we run the code, we see a totally different picture:
@@ -424,9 +424,10 @@ The phone codes go in the ascending sorted order, because they are integers. So
````smart header="Integer properties? What's that?"
The "integer property" term here means a string that can be converted to-and-from an integer without a change.
-So, "49" is an integer property name, because when it's transformed to an integer number and back, it's still the same. But "+49" and "1.2" are not:
+So, `"49"` is an integer property name, because when it's transformed to an integer number and back, it's still the same. But `"+49"` and `"1.2"` are not:
```js run
+// Number(...) explicitly converts to a number
// Math.trunc is a built-in function that removes the decimal part
alert( String(Math.trunc(Number("49"))) ); // "49", same, integer property
alert( String(Math.trunc(Number("+49"))) ); // "49", not same "+49" ⇒ not integer property
@@ -481,7 +482,7 @@ They store properties (key-value pairs), where:
To access a property, we can use:
- The dot notation: `obj.property`.
-- Square brackets notation `obj["property"]`. Square brackets allow to take the key from a variable, like `obj[varWithKey]`.
+- Square brackets notation `obj["property"]`. Square brackets allow taking the key from a variable, like `obj[varWithKey]`.
Additional operators:
- To delete a property: `delete obj.prop`.
diff --git a/1-js/04-object-basics/02-object-copy/article.md b/1-js/04-object-basics/02-object-copy/article.md
index cafb71cac..e80f748ab 100644
--- a/1-js/04-object-basics/02-object-copy/article.md
+++ b/1-js/04-object-basics/02-object-copy/article.md
@@ -37,7 +37,7 @@ And here's how it's actually stored in memory:
The object is stored somewhere in memory (at the right of the picture), while the `user` variable (at the left) has a "reference" to it.
-We may think of an object variable, such as `user`, as like a sheet of paper with the address of the object on it.
+We may think of an object variable, such as `user`, like a sheet of paper with the address of the object on it.
When we perform actions with the object, e.g. take a property `user.name`, the JavaScript engine looks at what's at that address and performs the operation on the actual object.
@@ -100,15 +100,37 @@ alert( a == b ); // false
For comparisons like `obj1 > obj2` or for a comparison against a primitive `obj == 5`, objects are converted to primitives. We'll study how object conversions work very soon, but to tell the truth, such comparisons are needed very rarely -- usually they appear as a result of a programming mistake.
+````smart header="Const objects can be modified"
+An important side effect of storing objects as references is that an object declared as `const` *can* be modified.
+
+For instance:
+
+```js run
+const user = {
+ name: "John"
+};
+
+*!*
+user.name = "Pete"; // (*)
+*/!*
+
+alert(user.name); // Pete
+```
+
+It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change.
+
+In other words, the `const user` gives an error only if we try to set `user=...` as a whole.
+
+That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter .
+````
+
## Cloning and merging, Object.assign [#cloning-and-merging-object-assign]
So, copying an object variable creates one more reference to the same object.
-But what if we need to duplicate an object? Create an independent copy, a clone?
-
-That's also doable, but a little bit more difficult, because there's no built-in method for that in JavaScript. But there is rarely a need -- copying by reference is good most of the time.
+But what if we need to duplicate an object?
-But if we really want that, then we need to create a new object and replicate the structure of the existing one by iterating over its properties and copying them on the primitive level.
+We can create a new object and replicate the structure of the existing one, by iterating over its properties and copying them on the primitive level.
Like this:
@@ -133,21 +155,22 @@ clone.name = "Pete"; // changed the data in it
alert( user.name ); // still John in the original object
```
-Also we can use the method [Object.assign](mdn:js/Object/assign) for that.
+We can also use the method [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign).
The syntax is:
```js
-Object.assign(dest, [src1, src2, src3...])
+Object.assign(dest, ...sources)
```
- The first argument `dest` is a target object.
-- Further arguments `src1, ..., srcN` (can be as many as needed) are source objects.
-- It copies the properties of all source objects `src1, ..., srcN` into the target `dest`. In other words, properties of all arguments starting from the second are copied into the first object.
-- The call returns `dest`.
+- Further arguments is a list of source objects.
-For instance, we can use it to merge several objects into one:
-```js
+It copies the properties of all source objects into the target `dest`, and then returns it as the result.
+
+For example, we have `user` object, let's add a couple of permissions to it:
+
+```js run
let user = { name: "John" };
let permissions1 = { canView: true };
@@ -159,6 +182,9 @@ Object.assign(user, permissions1, permissions2);
*/!*
// now user = { name: "John", canView: true, canEdit: true }
+alert(user.name); // John
+alert(user.canView); // true
+alert(user.canEdit); // true
```
If the copied property name already exists, it gets overwritten:
@@ -171,9 +197,9 @@ Object.assign(user, { name: "Pete" });
alert(user.name); // now user = { name: "Pete" }
```
-We also can use `Object.assign` to replace `for..in` loop for simple cloning:
+We also can use `Object.assign` to perform a simple object cloning:
-```js
+```js run
let user = {
name: "John",
age: 30
@@ -182,15 +208,18 @@ let user = {
*!*
let clone = Object.assign({}, user);
*/!*
+
+alert(clone.name); // John
+alert(clone.age); // 30
```
-It copies all properties of `user` into the empty object and returns it.
+Here it copies all properties of `user` into the empty object and returns it.
There are also other methods of cloning an object, e.g. using the [spread syntax](info:rest-parameters-spread) `clone = {...user}`, covered later in the tutorial.
## Nested cloning
-Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects. What to do with them?
+Until now we assumed that all properties of `user` are primitive. But properties can be references to other objects.
Like this:
```js run
@@ -205,9 +234,7 @@ let user = {
alert( user.sizes.height ); // 182
```
-Now it's not enough to copy `clone.sizes = user.sizes`, because the `user.sizes` is an object, it will be copied by reference. So `clone` and `user` will share the same sizes:
-
-Like this:
+Now it's not enough to copy `clone.sizes = user.sizes`, because `user.sizes` is an object, and will be copied by reference, so `clone` and `user` will share the same sizes:
```js run
let user = {
@@ -223,37 +250,71 @@ let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); // true, same object
// user and clone share sizes
-user.sizes.width++; // change a property from one place
-alert(clone.sizes.width); // 51, see the result from the other one
+user.sizes.width = 60; // change a property from one place
+alert(clone.sizes.width); // 60, get the result from the other one
```
-To fix that, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning".
+To fix that and make `user` and `clone` truly separate objects, we should use a cloning loop that examines each value of `user[key]` and, if it's an object, then replicate its structure as well. That is called a "deep cloning" or "structured cloning". There's [structuredClone](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) method that implements deep cloning.
-We can use recursion to implement it. Or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
-````smart header="Const objects can be modified"
-An important side effect of storing objects as references is that an object declared as `const` *can* be modified.
+### structuredClone
-For instance:
+The call `structuredClone(object)` clones the `object` with all nested properties.
+
+Here's how we can use it in our example:
```js run
-const user = {
- name: "John"
+let user = {
+ name: "John",
+ sizes: {
+ height: 182,
+ width: 50
+ }
};
*!*
-user.name = "Pete"; // (*)
+let clone = structuredClone(user);
*/!*
-alert(user.name); // Pete
+alert( user.sizes === clone.sizes ); // false, different objects
+
+// user and clone are totally unrelated now
+user.sizes.width = 60; // change a property from one place
+alert(clone.sizes.width); // 50, not related
```
-It might seem that the line `(*)` would cause an error, but it does not. The value of `user` is constant, it must always reference the same object, but properties of that object are free to change.
+The `structuredClone` method can clone most data types, such as objects, arrays, primitive values.
-In other words, the `const user` gives an error only if we try to set `user=...` as a whole.
+It also supports circular references, when an object property references the object itself (directly or via a chain or references).
-That said, if we really need to make constant object properties, it's also possible, but using totally different methods. We'll mention that in the chapter .
-````
+For instance:
+
+```js run
+let user = {};
+// let's create a circular reference:
+// user.me references the user itself
+user.me = user;
+
+let clone = structuredClone(user);
+alert(clone.me === clone); // true
+```
+
+As you can see, `clone.me` references the `clone`, not the `user`! So the circular reference was cloned correctly as well.
+
+Although, there are cases when `structuredClone` fails.
+
+For instance, when an object has a function property:
+
+```js run
+// error
+structuredClone({
+ f: function() {}
+});
+```
+
+Function properties aren't supported.
+
+To handle such complex cases we may need to use a combination of cloning methods, write custom code or, to not reinvent the wheel, take an existing implementation, for instance [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep) from the JavaScript library [lodash](https://lodash.com).
## Summary
@@ -261,4 +322,4 @@ Objects are assigned and copied by reference. In other words, a variable stores
All operations via copied references (like adding/removing properties) are performed on the same single object.
-To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
+To make a "real copy" (a clone) we can use `Object.assign` for the so-called "shallow copy" (nested objects are copied by reference) or a "deep cloning" function `structuredClone` or use a custom cloning implementation, such as [_.cloneDeep(obj)](https://lodash.com/docs#cloneDeep).
diff --git a/1-js/04-object-basics/03-garbage-collection/article.md b/1-js/04-object-basics/03-garbage-collection/article.md
index 72e30469c..1b576d629 100644
--- a/1-js/04-object-basics/03-garbage-collection/article.md
+++ b/1-js/04-object-basics/03-garbage-collection/article.md
@@ -74,7 +74,7 @@ Now if we do the same:
user = null;
```
-...Then the object is still reachable via `admin` global variable, so it's in memory. If we overwrite `admin` too, then it can be removed.
+...Then the object is still reachable via `admin` global variable, so it must stay in memory. If we overwrite `admin` too, then it can be removed.
## Interlinked objects
@@ -169,11 +169,11 @@ The first step marks the roots:

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

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

@@ -183,12 +183,12 @@ Now the objects that could not be visited in the process are considered unreacha
We can also imagine the process as spilling a huge bucket of paint from the roots, that flows through all references and marks all reachable objects. The unmarked ones are then removed.
-That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not affect the execution.
+That's the concept of how garbage collection works. JavaScript engines apply many optimizations to make it run faster and not introduce any delays into the code execution.
Some of the optimizations:
-- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". Many objects appear, do their job and die fast, they can be cleaned up aggressively. Those that survive for long enough, become "old" and are examined less often.
-- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine tries to split the garbage collection into pieces. Then the pieces are executed one by one, separately. That requires some extra bookkeeping between them to track changes, but we have many tiny delays instead of a big one.
+- **Generational collection** -- objects are split into two sets: "new ones" and "old ones". In typical code, many objects have a short life span: they appear, do their job and die fast, so it makes sense to track new objects and clear the memory from them if that's the case. Those that survive for long enough, become "old" and are examined less often.
+- **Incremental collection** -- if there are many objects, and we try to walk and mark the whole object set at once, it may take some time and introduce visible delays in the execution. So the engine splits the whole set of existing objects into multiple parts. And then clear these parts one after another. There are many small garbage collections instead of a total one. That requires some extra bookkeeping between them to track changes, but we get many tiny delays instead of a big one.
- **Idle-time collection** -- the garbage collector tries to run only while the CPU is idle, to reduce the possible effect on the execution.
There exist other optimizations and flavours of garbage collection algorithms. As much as I'd like to describe them here, I have to hold off, because different engines implement different tweaks and techniques. And, what's even more important, things change as engines develop, so studying deeper "in advance", without a real need is probably not worth that. Unless, of course, it is a matter of pure interest, then there will be some links for you below.
@@ -199,14 +199,14 @@ The main things to know:
- Garbage collection is performed automatically. We cannot force or prevent it.
- Objects are retained in memory while they are reachable.
-- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole.
+- Being referenced is not the same as being reachable (from a root): a pack of interlinked objects can become unreachable as a whole, as we've seen in the example above.
Modern engines implement advanced algorithms of garbage collection.
A general book "The Garbage Collection Handbook: The Art of Automatic Memory Management" (R. Jones et al) covers some of them.
-If you are familiar with low-level programming, the more detailed information about V8 garbage collector is in the article [A tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection).
+If you are familiar with low-level programming, more detailed information about V8's garbage collector is in the article [A tour of V8: Garbage Collection](https://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection).
-[V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn the garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](http://mrale.ph) who worked as one of V8 engineers. I'm saying: "V8", because it is best covered with articles in the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects.
+The [V8 blog](https://v8.dev/) also publishes articles about changes in memory management from time to time. Naturally, to learn more about garbage collection, you'd better prepare by learning about V8 internals in general and read the blog of [Vyacheslav Egorov](https://mrale.ph) who worked as one of the V8 engineers. I'm saying: "V8", because it is best covered by articles on the internet. For other engines, many approaches are similar, but garbage collection differs in many aspects.
-In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language.
+In-depth knowledge of engines is good when you need low-level optimizations. It would be wise to plan that as the next step after you're familiar with the language.
diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/task.md b/1-js/04-object-basics/04-object-methods/7-calculator/task.md
index aa22608ec..82d0da030 100644
--- a/1-js/04-object-basics/04-object-methods/7-calculator/task.md
+++ b/1-js/04-object-basics/04-object-methods/7-calculator/task.md
@@ -6,7 +6,7 @@ importance: 5
Create an object `calculator` with three methods:
-- `read()` prompts for two values and saves them as object properties.
+- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively.
- `sum()` returns the sum of saved values.
- `mul()` multiplies saved values and returns the result.
@@ -21,4 +21,3 @@ alert( calculator.mul() );
```
[demo]
-
diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js
index e98fe6410..a35c009cc 100644
--- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js
+++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/solution.js
@@ -11,5 +11,6 @@ let ladder = {
},
showStep: function() {
alert(this.step);
+ return this;
}
};
\ No newline at end of file
diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js
index a2b17fcc4..b4f2459b7 100644
--- a/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js
+++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/_js.view/test.js
@@ -32,6 +32,14 @@ describe('Ladder', function() {
it('down().up().up().up() ', function() {
assert.equal(ladder.down().up().up().up().step, 2);
});
+
+ it('showStep() should return this', function() {
+ assert.equal(ladder.showStep(), ladder);
+ });
+
+ it('up().up().down().showStep().down().showStep()', function () {
+ assert.equal(ladder.up().up().down().showStep().down().showStep().step, 0)
+ });
after(function() {
ladder.step = 0;
diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md
index ab4e37340..f215461dd 100644
--- a/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md
+++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/solution.md
@@ -23,7 +23,7 @@ let ladder = {
}
};
-ladder.up().up().down().up().down().showStep(); // 1
+ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0
```
We also can write a single call per line. For long chains it's more readable:
@@ -33,7 +33,7 @@ ladder
.up()
.up()
.down()
- .up()
+ .showStep() // 1
.down()
- .showStep(); // 1
+ .showStep(); // 0
```
diff --git a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md
index eca9f4e92..7d2ef8c15 100644
--- a/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md
+++ b/1-js/04-object-basics/04-object-methods/8-chain-calls/task.md
@@ -4,7 +4,7 @@ importance: 2
# Chaining
-There's a `ladder` object that allows to go up and down:
+There's a `ladder` object that allows you to go up and down:
```js
let ladder = {
@@ -21,19 +21,21 @@ let ladder = {
};
```
-Now, if we need to make several calls in sequence, can do it like this:
+Now, if we need to make several calls in sequence, we can do it like this:
```js
ladder.up();
ladder.up();
ladder.down();
ladder.showStep(); // 1
+ladder.down();
+ladder.showStep(); // 0
```
-Modify the code of `up`, `down` and `showStep` to make the calls chainable, like this:
+Modify the code of `up`, `down`, and `showStep` to make the calls chainable, like this:
```js
-ladder.up().up().down().showStep(); // 1
+ladder.up().up().down().showStep().down().showStep(); // shows 1 then 0
```
-Such approach is widely used across JavaScript libraries.
+Such an approach is widely used across JavaScript libraries.
diff --git a/1-js/04-object-basics/04-object-methods/article.md b/1-js/04-object-basics/04-object-methods/article.md
index a36b9ca07..cea2b6a70 100644
--- a/1-js/04-object-basics/04-object-methods/article.md
+++ b/1-js/04-object-basics/04-object-methods/article.md
@@ -51,7 +51,7 @@ let user = {
// first, declare
function sayHi() {
alert("Hello!");
-};
+}
// then add as a method
user.sayHi = sayHi;
@@ -90,7 +90,7 @@ user = {
As demonstrated, we can omit `"function"` and just write `sayHi()`.
-To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases the shorter syntax is preferred.
+To tell the truth, the notations are not fully identical. There are subtle differences related to object inheritance (to be covered later), but for now they do not matter. In almost all cases, the shorter syntax is preferred.
## "this" in methods
diff --git a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md
index 8c1fea8eb..e932a201a 100644
--- a/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md
+++ b/1-js/04-object-basics/06-constructor-new/1-two-functions-one-object/task.md
@@ -4,14 +4,14 @@ importance: 2
# Two functions – one object
-Is it possible to create functions `A` and `B` such as `new A()==new B()`?
+Is it possible to create functions `A` and `B` so that `new A() == new B()`?
```js no-beautify
function A() { ... }
function B() { ... }
-let a = new A;
-let b = new B;
+let a = new A();
+let b = new B();
alert( a == b ); // true
```
diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md
index 60e7c373e..c862bec40 100644
--- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md
+++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/task.md
@@ -6,7 +6,7 @@ importance: 5
Create a constructor function `Calculator` that creates objects with 3 methods:
-- `read()` asks for two values using `prompt` and remembers them in object properties.
+- `read()` prompts for two values and saves them as object properties with names `a` and `b` respectively.
- `sum()` returns the sum of these properties.
- `mul()` returns the multiplication product of these properties.
diff --git a/1-js/04-object-basics/06-constructor-new/article.md b/1-js/04-object-basics/06-constructor-new/article.md
index 184b30f86..a335464f1 100644
--- a/1-js/04-object-basics/06-constructor-new/article.md
+++ b/1-js/04-object-basics/06-constructor-new/article.md
@@ -1,6 +1,6 @@
# Constructor, operator "new"
-The regular `{...}` syntax allows to create one object. But often we need to create many similar objects, like multiple users or menu items and so on.
+The regular `{...}` syntax allows us to create one object. But often we need to create many similar objects, like multiple users or menu items and so on.
That can be done using constructor functions and the `"new"` operator.
@@ -171,7 +171,7 @@ alert( new SmallUser().name ); // John
Usually constructors don't have a `return` statement. Here we mention the special behavior with returning objects mainly for the sake of completeness.
````smart header="Omitting parentheses"
-By the way, we can omit parentheses after `new`, if it has no arguments:
+By the way, we can omit parentheses after `new`:
```js
let user = new User; // <-- no parentheses
diff --git a/1-js/04-object-basics/07-optional-chaining/article.md b/1-js/04-object-basics/07-optional-chaining/article.md
index 9591dcd6c..4c6029423 100644
--- a/1-js/04-object-basics/07-optional-chaining/article.md
+++ b/1-js/04-object-basics/07-optional-chaining/article.md
@@ -25,14 +25,14 @@ That's the expected result. JavaScript works like this. As `user.address` is `un
In many practical cases we'd prefer to get `undefined` instead of an error here (meaning "no street").
-...And another example. In the web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element.
+...and another example. In Web development, we can get an object that corresponds to a web page element using a special method call, such as `document.querySelector('.elem')`, and it returns `null` when there's no such element.
```js run
// document.querySelector('.elem') is null if there's no element
let html = document.querySelector('.elem').innerHTML; // error if it's null
```
-Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result.
+Once again, if the element doesn't exist, we'll get an error accessing `.innerHTML` property of `null`. And in some cases, when the absence of the element is normal, we'd like to avoid the error and just accept `html = null` as the result.
How can we do this?
@@ -44,11 +44,19 @@ let user = {};
alert(user.address ? user.address.street : undefined);
```
-It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code. For more deeply nested properties, that becomes a problem as more repetitions are required.
+It works, there's no error... But it's quite inelegant. As you can see, the `"user.address"` appears twice in the code.
-E.g. let's try getting `user.address.street.name`.
+Here's how the same would look for `document.querySelector`:
-We need to check both `user.address` and `user.address.street`:
+```js run
+let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null;
+```
+
+We can see that the element search `document.querySelector('.elem')` is actually called twice here. Not good.
+
+For more deeply nested properties, it becomes even uglier, as more repetitions are required.
+
+E.g. let's get `user.address.street.name` in a similar fashion.
```js
let user = {}; // user has no address
@@ -58,7 +66,7 @@ alert(user.address ? user.address.street ? user.address.street.name : null : nul
That's just awful, one may even have problems understanding such code.
-Don't even care to, as there's a better way to write it, using the `&&` operator:
+There's a little better way to write it, using the `&&` operator:
```js run
let user = {}; // user has no address
@@ -92,6 +100,12 @@ alert( user?.address?.street ); // undefined (no error)
The code is short and clean, there's no duplication at all.
+Here's an example with `document.querySelector`:
+
+```js run
+let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element
+```
+
Reading the address with `user?.address` works even if `user` object doesn't exist:
```js run
@@ -108,9 +122,9 @@ E.g. in `user?.address.street.name` the `?.` allows `user` to safely be `null/un
```warn header="Don't overuse the optional chaining"
We should use `?.` only where it's ok that something doesn't exist.
-For example, if according to our coding logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`.
+For example, if according to our code logic `user` object must exist, but `address` is optional, then we should write `user.address?.street`, but not `user?.address?.street`.
-So, if `user` happens to be undefined due to a mistake, we'll see a programming error about it and fix it. Otherwise, coding errors can be silenced where not appropriate, and become more difficult to debug.
+Then, if `user` happens to be undefined, we'll see a programming error about it and fix it. Otherwise, if we overuse `?.`, coding errors can be silenced where not appropriate, and become more difficult to debug.
```
````warn header="The variable before `?.` must be declared"
@@ -127,7 +141,7 @@ The variable must be declared (e.g. `let/const/var user` or as a function parame
As it was said before, the `?.` immediately stops ("short-circuits") the evaluation if the left part doesn't exist.
-So, if there are any further function calls or side effects, they don't occur.
+So, if there are any further function calls or operations to the right of `?.`, they won't be made.
For instance:
@@ -135,7 +149,7 @@ For instance:
let user = null;
let x = 0;
-user?.sayHi(x++); // no "sayHi", so the execution doesn't reach x++
+user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++
alert(x); // 0, value not incremented
```
@@ -162,13 +176,13 @@ userAdmin.admin?.(); // I am admin
*/!*
*!*
-userGuest.admin?.(); // nothing (no such method)
+userGuest.admin?.(); // nothing happens (no such method)
*/!*
```
-Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the user object exists, so it's safe read from it.
+Here, in both lines we first use the dot (`userAdmin.admin`) to get `admin` property, because we assume that the `user` object exists, so it's safe read from it.
-Then `?.()` checks the left part: if the admin function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors.
+Then `?.()` checks the left part: if the `admin` function exists, then it runs (that's so for `userAdmin`). Otherwise (for `userGuest`) the evaluation stops without errors.
The `?.[]` syntax also works, if we'd like to use brackets `[]` to access properties instead of dot `.`. Similar to previous cases, it allows to safely read a property from an object that may not exist.
@@ -179,7 +193,7 @@ let user1 = {
firstName: "John"
};
-let user2 = null;
+let user2 = null;
alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined
@@ -192,17 +206,16 @@ delete user?.name; // delete user.name if user exists
```
````warn header="We can use `?.` for safe reading and deleting, but not writing"
-The optional chaining `?.` has no use at the left side of an assignment.
+The optional chaining `?.` has no use on the left side of an assignment.
For example:
```js run
let user = null;
user?.name = "John"; // Error, doesn't work
-// because it evaluates to undefined = "John"
+// because it evaluates to: undefined = "John"
```
-It's just not that smart.
````
## Summary
@@ -217,4 +230,4 @@ As we can see, all of them are straightforward and simple to use. The `?.` check
A chain of `?.` allows to safely access nested properties.
-Still, we should apply `?.` carefully, only where it's acceptable that the left part doesn't exist. So that it won't hide programming errors from us, if they occur.
+Still, we should apply `?.` carefully, only where it's acceptable, according to our code logic, that the left part doesn't exist. So that it won't hide programming errors from us, if they occur.
diff --git a/1-js/04-object-basics/08-symbol/article.md b/1-js/04-object-basics/08-symbol/article.md
index 626eedb53..10a98af0a 100644
--- a/1-js/04-object-basics/08-symbol/article.md
+++ b/1-js/04-object-basics/08-symbol/article.md
@@ -1,9 +1,16 @@
# Symbol type
-By specification, object property keys may be either of string type, or of symbol type. Not numbers, not booleans, only strings or symbols, these two types.
+By specification, only two primitive types may serve as object property keys:
-Till now we've been using only strings. Now let's see the benefits that symbols can give us.
+- string type, or
+- symbol type.
+
+Otherwise, if one uses another type, such as number, it's autoconverted to string. So that `obj[1]` is the same as `obj["1"]`, and `obj[true]` is the same as `obj["true"]`.
+
+Until now we've been using only strings.
+
+Now let's explore symbols, see what they can do for us.
## Symbols
@@ -12,18 +19,17 @@ A "symbol" represents a unique identifier.
A value of this type can be created using `Symbol()`:
```js
-// id is a new symbol
let id = Symbol();
```
-Upon creation, we can give symbol a description (also called a symbol name), mostly useful for debugging purposes:
+Upon creation, we can give symbols a description (also called a symbol name), mostly useful for debugging purposes:
```js
// id is a symbol with the description "id"
let id = Symbol("id");
```
-Symbols are guaranteed to be unique. Even if we create many symbols with the same description, they are different values. The description is just a label that doesn't affect anything.
+Symbols are guaranteed to be unique. Even if we create many symbols with exactly the same description, they are different values. The description is just a label that doesn't affect anything.
For instance, here are two symbols with the same description -- they are not equal:
@@ -38,6 +44,8 @@ alert(id1 == id2); // false
If you are familiar with Ruby or another language that also has some sort of "symbols" -- please don't be misguided. JavaScript symbols are different.
+So, to summarize, a symbol is a "primitive unique value" with an optional description. Let's see where we can use them.
+
````warn header="Symbols don't auto-convert to a string"
Most values in JavaScript support implicit conversion to a string. For instance, we can `alert` almost any value, and it will work. Symbols are special. They don't auto-convert.
@@ -53,6 +61,7 @@ alert(id); // TypeError: Cannot convert a Symbol value to a string
That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not accidentally convert one into another.
If we really want to show a symbol, we need to explicitly call `.toString()` on it, like here:
+
```js run
let id = Symbol("id");
*!*
@@ -61,6 +70,7 @@ alert(id.toString()); // Symbol(id), now it works
```
Or get `symbol.description` property to show the description only:
+
```js run
let id = Symbol("id");
*!*
@@ -72,6 +82,7 @@ alert(id.description); // id
## "Hidden" properties
+
Symbols allow us to create "hidden" properties of an object, that no other part of code can accidentally access or overwrite.
For instance, if we're working with `user` objects, that belong to a third-party code. We'd like to add identifiers to them.
@@ -92,9 +103,9 @@ alert( user[id] ); // we can access the data using the symbol as the key
What's the benefit of using `Symbol("id")` over a string `"id"`?
-As `user` objects belongs to another code, and that code also works with them, we shouldn't just add any fields to it. That's unsafe. But a symbol cannot be accessed accidentally, the third-party code probably won't even see it, so it's probably all right to do.
+As `user` objects belong to another codebase, it's unsafe to add fields to them, since we might affect pre-defined behavior in that other codebase. However, symbols cannot be accessed accidentally. The third-party code won't be aware of newly defined symbols, so it's safe to add symbols to the `user` objects.
-Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes. That may be another JavaScript library, so that the scripts are completely unaware of each other.
+Also, imagine that another script wants to have its own identifier inside `user`, for its own purposes.
Then that script can create its own `Symbol("id")`, like this:
@@ -158,10 +169,10 @@ for (let key in user) alert(key); // name, age (no symbols)
*/!*
// the direct access by the symbol works
-alert( "Direct: " + user[id] );
+alert( "Direct: " + user[id] ); // Direct: 123
```
-`Object.keys(user)` also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property.
+[Object.keys(user)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys) also ignores them. That's a part of the general "hiding symbolic properties" principle. If another script or a library loops over our object, it won't unexpectedly access a symbolic property.
In contrast, [Object.assign](mdn:js/Object/assign) copies both string and symbol properties:
@@ -206,12 +217,12 @@ Symbols inside the registry are called *global symbols*. If we want an applicati
```smart header="That sounds like Ruby"
In some programming languages, like Ruby, there's a single symbol per name.
-In JavaScript, as we can see, that's right for global symbols.
+In JavaScript, as we can see, that's true for global symbols.
```
### Symbol.keyFor
-For global symbols, not only `Symbol.for(key)` returns a symbol by name, but there's a reverse call: `Symbol.keyFor(sym)`, that does the reverse: returns a name by a global symbol.
+We have seen that for global symbols, `Symbol.for(key)` returns a symbol by name. To do the opposite -- return a name by global symbol -- we can use: `Symbol.keyFor(sym)`:
For instance:
@@ -227,7 +238,7 @@ alert( Symbol.keyFor(sym2) ); // id
The `Symbol.keyFor` internally uses the global symbol registry to look up the key for the symbol. So it doesn't work for non-global symbols. If the symbol is not global, it won't be able to find it and returns `undefined`.
-That said, any symbols have `description` property.
+That said, all symbols have the `description` property.
For instance:
@@ -268,10 +279,11 @@ Symbols are always different values, even if they have the same name. If we want
Symbols have two main use cases:
1. "Hidden" object properties.
+
If we want to add a property into an object that "belongs" to another script or a library, we can create a symbol and use it as a property key. A symbolic property does not appear in `for..in`, so it won't be accidentally processed together with other properties. Also it won't be accessed directly, because another script does not have our symbol. So the property will be protected from accidental use or overwrite.
So we can "covertly" hide something into objects that we need, but others should not see, using symbolic properties.
2. There are many system symbols used by JavaScript which are accessible as `Symbol.*`. We can use them to alter some built-in behaviors. For instance, later in the tutorial we'll use `Symbol.iterator` for [iterables](info:iterable), `Symbol.toPrimitive` to setup [object-to-primitive conversion](info:object-toprimitive) and so on.
-Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. So they are not really hidden. But most libraries, built-in functions and syntax constructs don't use these methods.
+Technically, symbols are not 100% hidden. There is a built-in method [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) that allows us to get all symbols. Also there is a method named [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) that returns *all* keys of an object including symbolic ones. But most libraries, built-in functions and syntax constructs don't use these methods.
diff --git a/1-js/04-object-basics/09-object-toprimitive/article.md b/1-js/04-object-basics/09-object-toprimitive/article.md
index b0605d44f..fa68da583 100644
--- a/1-js/04-object-basics/09-object-toprimitive/article.md
+++ b/1-js/04-object-basics/09-object-toprimitive/article.md
@@ -3,15 +3,15 @@
What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`?
-JavaScript doesn't exactly allow to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle an addition (or other operators).
+JavaScript doesn't allow you to customize how operators work on objects. Unlike some other programming languages, such as Ruby or C++, we can't implement a special object method to handle addition (or other operators).
In case of such operations, objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value.
-That's an important limitation, as the result of `obj1 + obj2` can't be another object!
+That's an important limitation: the result of `obj1 + obj2` (or another math operation) can't be another object!
-E.g. we can't make objects representing vectors or matrices (or archievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board".
+E.g. we can't make objects representing vectors or matrices (or achievements or whatever), add them and expect a "summed" object as the result. Such architectural feats are automatically "off the board".
-So, because we can't do much here, there's no maths with objects in real projects. When it happens, it's usually because of a coding mistake.
+So, because we can't technically do much here, there's no maths with objects in real projects. When it happens, with rare exceptions, it's because of a coding mistake.
In this chapter we'll cover how an object converts to primitive and how to customize it.
@@ -24,15 +24,19 @@ We have two purposes:
In the chapter we've seen the rules for numeric, string and boolean conversions of primitives. But we left a gap for objects. Now, as we know about methods and symbols it becomes possible to fill it.
-1. All objects are `true` in a boolean context. There are only numeric and string conversions.
+1. There's no conversion to boolean. All objects are `true` in a boolean context, as simple as that. There exist only numeric and string conversions.
2. The numeric conversion happens when we subtract objects or apply mathematical functions. For instance, `Date` objects (to be covered in the chapter ) can be subtracted, and the result of `date1 - date2` is the time difference between two dates.
-3. As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts.
+3. As for the string conversion -- it usually happens when we output an object with `alert(obj)` and in similar contexts.
-We can fine-tune string and numeric conversion, using special object methods.
+We can implement string and numeric conversion by ourselves, using special object methods.
-There are three variants of type conversion, that happen in various situations.
+Now let's get into technical details, because it's the only way to cover the topic in-depth.
-They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive):
+## Hints
+
+How does JavaScript decide which conversion to apply?
+
+There are three variants of type conversion, that happen in various situations. They're called "hints", as described in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive):
`"string"`
: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`:
@@ -60,10 +64,12 @@ They're called "hints", as described in the [specification](https://tc39.github.
let greater = user1 > user2;
```
+ Most built-in mathematical functions also include such conversion.
+
`"default"`
: Occurs in rare cases when the operator is "not sure" what type to expect.
- For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it.
+ For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them). So if a binary plus gets an object as an argument, it uses the `"default"` hint to convert it.
Also, if an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done, so the `"default"` hint is used.
@@ -77,21 +83,19 @@ They're called "hints", as described in the [specification](https://tc39.github.
The greater and less comparison operators, such as `<` `>`, can work with both strings and numbers too. Still, they use the `"number"` hint, not `"default"`. That's for historical reasons.
- In practice though, we don't need to remember these peculiar details, because all built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we can do the same.
+In practice though, things are a bit simpler.
-```smart header="No `\"boolean\"` hint"
-Please note -- there are only three hints. It's that simple.
+All built-in objects except for one case (`Date` object, we'll learn it later) implement `"default"` conversion the same way as `"number"`. And we probably should do the same.
-There is no "boolean" hint (all objects are `true` in boolean context) or anything else. And if we treat `"default"` and `"number"` the same, like most built-ins do, then there are only two conversions.
-```
+Still, it's important to know about all 3 hints, soon we'll see why.
**To do the conversion, JavaScript tries to find and call three object methods:**
1. Call `obj[Symbol.toPrimitive](hint)` - the method with the symbolic key `Symbol.toPrimitive` (system symbol), if such method exists,
2. Otherwise if hint is `"string"`
- - try `obj.toString()` and `obj.valueOf()`, whatever exists.
+ - try calling `obj.toString()` or `obj.valueOf()`, whatever exists.
3. Otherwise if hint is `"number"` or `"default"`
- - try `obj.valueOf()` and `obj.toString()`, whatever exists.
+ - try calling `obj.valueOf()` or `obj.toString()`, whatever exists.
## Symbol.toPrimitive
@@ -126,15 +130,14 @@ alert(+user); // hint: number -> 1000
alert(user + 500); // hint: default -> 1500
```
-As we can see from the code, `user` becomes a self-descriptive string or a money amount depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases.
-
+As we can see from the code, `user` becomes a self-descriptive string or a money amount, depending on the conversion. The single method `user[Symbol.toPrimitive]` handles all conversion cases.
## toString/valueOf
If there's no `Symbol.toPrimitive` then JavaScript tries to find methods `toString` and `valueOf`:
-- For the "string" hint: `toString`, and if it doesn't exist, then `valueOf` (so `toString` has the priority for stirng 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.
@@ -207,23 +210,23 @@ In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all
The important thing to know about all primitive-conversion methods is that they do not necessarily return the "hinted" primitive.
-There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for a hint `"number"`.
+There is no control whether `toString` returns exactly a string, or whether `Symbol.toPrimitive` method returns a number for the hint `"number"`.
The only mandatory thing: these methods must return a primitive, not an object.
```smart header="Historical notes"
For historical reasons, if `toString` or `valueOf` returns an object, there's no error, but such value is ignored (like if the method didn't exist). That's because in ancient times there was no good "error" concept in JavaScript.
-In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise there will be an error.
+In contrast, `Symbol.toPrimitive` is stricter, it *must* return a primitive, otherwise there will be an error.
```
## Further conversions
As we know already, many operators and functions perform type conversions, e.g. multiplication `*` converts operands to numbers.
-If we pass an object as an argument, then there are two stages:
+If we pass an object as an argument, then there are two stages of calculations:
1. The object is converted to a primitive (using the rules described above).
-2. If the resulting primitive isn't of the right type, it's converted.
+2. If necessary for further calculations, the resulting primitive is also converted.
For instance:
@@ -250,7 +253,7 @@ let obj = {
}
};
-alert(obj + 2); // 22 ("2" + 2), conversion to primitive returned a string => concatenation
+alert(obj + 2); // "22" ("2" + 2), conversion to primitive returned a string => concatenation
```
## Summary
@@ -260,18 +263,18 @@ The object-to-primitive conversion is called automatically by many built-in func
There are 3 types (hints) of it:
- `"string"` (for `alert` and other operations that need a string)
- `"number"` (for maths)
-- `"default"` (few operators)
+- `"default"` (few operators, usually objects implement it the same way as `"number"`)
-The specification describes explicitly which operator uses which hint. There are very few operators that "don't know what to expect" and use the `"default"` hint. Usually for built-in objects `"default"` hint is handled the same way as `"number"`, so in practice the last two are often merged together.
+The specification describes explicitly which operator uses which hint.
The conversion algorithm is:
1. Call `obj[Symbol.toPrimitive](hint)` if the method exists,
2. Otherwise if hint is `"string"`
- - try `obj.toString()` and `obj.valueOf()`, whatever exists.
+ - try calling `obj.toString()` or `obj.valueOf()`, whatever exists.
3. Otherwise if hint is `"number"` or `"default"`
- - try `obj.valueOf()` and `obj.toString()`, whatever exists.
+ - try calling `obj.valueOf()` or `obj.toString()`, whatever exists.
-In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes.
+All these methods must return a primitive to work (if defined).
-As for math operations, JavaScript doesn't provide a way to "override" them using methods, so real life projects rarely use them on objects.
\ No newline at end of file
+In practice, it's often enough to implement only `obj.toString()` as a "catch-all" method for string conversions that should return a "human-readable" representation of an object, for logging or debugging purposes.
diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md
index 50c781ea5..208f84cc7 100644
--- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md
+++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/task.md
@@ -15,4 +15,4 @@ str.test = 5;
alert(str.test);
```
-How do you think, will it work? What will be shown?
+What do you think, will it work? What will be shown?
diff --git a/1-js/05-data-types/01-primitives-methods/article.md b/1-js/05-data-types/01-primitives-methods/article.md
index 552c3b3b6..69e7196e9 100644
--- a/1-js/05-data-types/01-primitives-methods/article.md
+++ b/1-js/05-data-types/01-primitives-methods/article.md
@@ -39,7 +39,7 @@ Objects are "heavier" than primitives. They require additional resources to supp
Here's the paradox faced by the creator of JavaScript:
-- There are many things one would want to do with a primitive like a string or a number. It would be great to access them using methods.
+- There are many things one would want to do with a primitive, like a string or a number. It would be great to access them using methods.
- Primitives must be as fast and lightweight as possible.
The solution looks a little bit awkward, but here it is:
@@ -48,7 +48,7 @@ The solution looks a little bit awkward, but here it is:
2. The language allows access to methods and properties of strings, numbers, booleans and symbols.
3. In order for that to work, a special "object wrapper" that provides the extra functionality is created, and then is destroyed.
-The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean` and `Symbol`. Thus, they provide different sets of methods.
+The "object wrappers" are different for each primitive type and are called: `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. Thus, they provide different sets of methods.
For instance, there exists a string method [str.toUpperCase()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase) that returns a capitalized `str`.
@@ -104,9 +104,10 @@ if (zero) { // zero is true, because it's an object
}
```
-On the other hand, using the same functions `String/Number/Boolean` without `new` is a totally sane and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive).
+On the other hand, using the same functions `String/Number/Boolean` without `new` is totally fine and useful thing. They convert a value to the corresponding type: to a string, a number, or a boolean (primitive).
For example, this is entirely valid:
+
```js
let num = Number("123"); // convert a string to number
```
diff --git a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md
index a17a4671a..4bcd74512 100644
--- a/1-js/05-data-types/02-number/2-why-rounded-down/solution.md
+++ b/1-js/05-data-types/02-number/2-why-rounded-down/solution.md
@@ -28,6 +28,6 @@ Note that `63.5` has no precision loss at all. That's because the decimal part `
```js run
-alert( Math.round(6.35 * 10) / 10); // 6.35 -> 63.5 -> 64(rounded) -> 6.4
+alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(rounded) -> 6.4
```
diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md
index 6fd513f43..8e41f673d 100644
--- a/1-js/05-data-types/02-number/article.md
+++ b/1-js/05-data-types/02-number/article.md
@@ -2,9 +2,9 @@
In modern JavaScript, there are two types of numbers:
-1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter.
+1. Regular numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), also known as "double precision floating point numbers". These are numbers that we're using most of the time, and we'll talk about them in this chapter.
-2. BigInt numbers, to represent integers of arbitrary length. They are sometimes needed, because a regular number can't exceed 253
or be less than -253
. As bigints are used in few special areas, we devote them a special chapter .
+2. BigInt numbers represent integers of arbitrary length. They are sometimes needed because a regular integer number can't safely exceed (253-1)
or be less than -(253-1)
, as we mentioned earlier in the chapter . As bigints are used in a few special areas, we devote them to a special chapter .
So here we'll talk about regular numbers. Let's expand our knowledge of them.
@@ -22,7 +22,7 @@ We also can use underscore `_` as the separator:
let billion = 1_000_000_000;
```
-Here the underscore `_` plays the role of the "syntactic sugar", it makes the number more readable. The JavaScript engine simply ignores `_` between digits, so it's exactly the same one billion as above.
+Here the underscore `_` plays the role of the "[syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)", it makes the number more readable. The JavaScript engine simply ignores `_` between digits, so it's exactly the same one billion as above.
In real life though, we try to avoid writing long sequences of zeroes. We're too lazy for that. We'll try to write something like `"1bn"` for a billion or `"7.3bn"` for 7 billion 300 million. The same is true for most large numbers.
@@ -37,32 +37,35 @@ alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)
In other words, `e` multiplies the number by `1` with the given zeroes count.
```js
-1e3 = 1 * 1000 // e3 means *1000
-1.23e6 = 1.23 * 1000000 // e6 means *1000000
+1e3 === 1 * 1000; // e3 means *1000
+1.23e6 === 1.23 * 1000000; // e6 means *1000000
```
-Now let's write something very small. Say, 1 microsecond (one millionth of a second):
+Now let's write something very small. Say, 1 microsecond (one-millionth of a second):
```js
-let ms = 0.000001;
+let mсs = 0.000001;
```
-Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say the same as:
+Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could write the same as:
```js
-let ms = 1e-6; // six zeroes to the left from 1
+let mcs = 1e-6; // five zeroes to the left from 1
```
-If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`.
+If we count the zeroes in `0.000001`, there are 6 of them. So naturally it's `1e-6`.
In other words, a negative number after `"e"` means a division by 1 with the given number of zeroes:
```js
// -3 divides by 1 with 3 zeroes
-1e-3 = 1 / 1000 (=0.001)
+1e-3 === 1 / 1000; // 0.001
// -6 divides by 1 with 6 zeroes
-1.23e-6 = 1.23 / 1000000 (=0.00000123)
+1.23e-6 === 1.23 / 1000000; // 0.00000123
+
+// an example with a bigger number
+1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times
```
### Hex, binary and octal numbers
@@ -100,13 +103,13 @@ alert( num.toString(16) ); // ff
alert( num.toString(2) ); // 11111111
```
-The `base` can vary from `2` to `36`. By default it's `10`.
+The `base` can vary from `2` to `36`. By default, it's `10`.
Common use cases for this are:
- **base=16** is used for hex colors, character encodings etc, digits can be `0..9` or `A..F`.
- **base=2** is mostly for debugging bitwise operations, digits can be `0` or `1`.
-- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example to make a short url. Can simply represent it in the numeral system with base `36`:
+- **base=36** is the maximum, digits can be `0..9` or `A..Z`. The whole Latin alphabet is used to represent a number. A funny, but useful case for `36` is when we need to turn a long numeric identifier into something shorter, for example, to make a short url. Can simply represent it in the numeral system with base `36`:
```js run
alert( 123456..toString(36) ); // 2n9c
@@ -115,9 +118,10 @@ Common use cases for this are:
```warn header="Two dots to call a method"
Please note that two dots in `123456..toString(36)` is not a typo. If we want to call a method directly on a number, like `toString` in the example above, then we need to place two dots `..` after it.
-If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now goes the method.
+If we placed a single dot: `123456.toString(36)`, then there would be an error, because JavaScript syntax implies the decimal part after the first dot. And if we place one more dot, then JavaScript knows that the decimal part is empty and now uses the method.
Also could write `(123456).toString(36)`.
+
```
## Rounding
@@ -133,7 +137,7 @@ There are several built-in functions for rounding:
: Rounds up: `3.1` becomes `4`, and `-1.1` becomes `-1`.
`Math.round`
-: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`, the middle case: `3.5` rounds up to `4` too.
+: Rounds to the nearest integer: `3.1` becomes `3`, `3.6` becomes `4`. In the middle cases `3.5` rounds up to `4`, and `-3.5` rounds up to `-3`.
`Math.trunc` (not supported by Internet Explorer)
: Removes anything after the decimal point without rounding: `3.1` becomes `3`, `-1.1` becomes `-1`.
@@ -143,8 +147,10 @@ Here's the table to summarize the differences between them:
| | `Math.floor` | `Math.ceil` | `Math.round` | `Math.trunc` |
|---|---------|--------|---------|---------|
|`3.1`| `3` | `4` | `3` | `3` |
+|`3.5`| `3` | `4` | `4` | `3` |
|`3.6`| `3` | `4` | `4` | `3` |
|`-1.1`| `-2` | `-1` | `-1` | `-1` |
+|`-1.5`| `-2` | `-1` | `-1` | `-1` |
|`-1.6`| `-2` | `-1` | `-2` | `-1` |
@@ -156,7 +162,7 @@ There are two ways to do so:
1. Multiply-and-divide.
- For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100` (or a bigger power of 10), call the rounding function and then divide it back.
+ For example, to round the number to the 2nd digit after the decimal, we can multiply the number by `100`, call the rounding function and then divide it back.
```js run
let num = 1.23456;
@@ -177,20 +183,20 @@ There are two ways to do so:
alert( num.toFixed(1) ); // "12.4"
```
- Please note that result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end:
+ Please note that the result of `toFixed` is a string. If the decimal part is shorter than required, zeroes are appended to the end:
```js run
let num = 12.34;
alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits
```
- We can convert it to a number using the unary plus or a `Number()` call: `+num.toFixed(5)`.
+ We can convert it to a number using the unary plus or a `Number()` call, e.g. write `+num.toFixed(5)`.
## Imprecise calculations
-Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point (they are zero for integer numbers), and 1 bit is for the sign.
+Internally, a number is represented in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754), so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point, and 1 bit is for the sign.
-If a number is too big, it would overflow the 64-bit storage, potentially giving an infinity:
+If a number is really huge, it may overflow the 64-bit storage and become a special numeric value `Infinity`:
```js run
alert( 1e500 ); // Infinity
@@ -198,7 +204,7 @@ alert( 1e500 ); // Infinity
What may be a little less obvious, but happens quite often, is the loss of precision.
-Consider this (falsy!) test:
+Consider this (falsy!) equality test:
```js run
alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!*
@@ -212,13 +218,19 @@ Strange! What is it then if not `0.3`?
alert( 0.1 + 0.2 ); // 0.30000000000000004
```
-Ouch! There are more consequences than an incorrect comparison here. Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone.
+Ouch! Imagine you're making an e-shopping site and the visitor puts `$0.10` and `$0.20` goods into their cart. The order total will be `$0.30000000000000004`. That would surprise anyone.
But why does this happen?
A number is stored in memory in its binary form, a sequence of bits - ones and zeroes. But fractions like `0.1`, `0.2` that look simple in the decimal numeric system are actually unending fractions in their binary form.
-In other words, what is `0.1`? It is one divided by ten `1/10`, one-tenth. In decimal numeral system such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`.
+```js run
+alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101
+alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101
+alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101
+```
+
+What is `0.1`? It is one divided by ten `1/10`, one-tenth. In the decimal numeral system, such numbers are easily representable. Compare it to one-third: `1/3`. It becomes an endless fraction `0.33333(3)`.
So, division by powers `10` is guaranteed to work well in the decimal system, but division by `3` is not. For the same reason, in the binary numeral system, the division by powers of `2` is guaranteed to work, but `1/10` becomes an endless binary fraction.
@@ -238,14 +250,14 @@ That's why `0.1 + 0.2` is not exactly `0.3`.
```smart header="Not only JavaScript"
The same issue exists in many other programming languages.
-PHP, Java, C, Perl, Ruby give exactly the same result, because they are based on the same numeric format.
+PHP, Java, C, Perl, and Ruby give exactly the same result, because they are based on the same numeric format.
```
Can we work around the problem? Sure, the most reliable method is to round the result with the help of a method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed):
```js run
let sum = 0.1 + 0.2;
-alert( sum.toFixed(2) ); // 0.30
+alert( sum.toFixed(2) ); // "0.30"
```
Please note that `toFixed` always returns a string. It ensures that it has 2 digits after the decimal point. That's actually convenient if we have an e-shopping and need to show `$0.30`. For other cases, we can use the unary plus to coerce it into a number:
@@ -262,7 +274,7 @@ alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001
```
-So, multiply/divide approach reduces the error, but doesn't remove it totally.
+So, the multiply/divide approach reduces the error, but doesn't remove it totally.
Sometimes we could try to evade fractions at all. Like if we're dealing with a shop, then we can store prices in cents instead of dollars. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely possible. Just round them to cut "tails" when needed.
@@ -284,7 +296,7 @@ Another funny consequence of the internal representation of numbers is the exist
That's because a sign is represented by a single bit, so it can be set or not set for any number including a zero.
-In most cases the distinction is unnoticeable, because operators are suited to treat them as the same.
+In most cases, the distinction is unnoticeable, because operators are suited to treat them as the same.
```
## Tests: isFinite and isNaN
@@ -304,7 +316,7 @@ They belong to the type `number`, but are not "normal" numbers, so there are spe
alert( isNaN("str") ); // true
```
- But do we need this function? Can't we just use the comparison `=== NaN`? Sorry, but the answer is no. The value `NaN` is unique in that it does not equal anything, including itself:
+ But do we need this function? Can't we just use the comparison `=== NaN`? Unfortunately not. The value `NaN` is unique in that it does not equal anything, including itself:
```js run
alert( NaN === NaN ); // false
@@ -328,18 +340,46 @@ let num = +prompt("Enter a number", '');
alert( isFinite(num) );
```
-Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`.
+Please note that an empty or a space-only string is treated as `0` in all numeric functions including `isFinite`.
-```smart header="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
+ ```
-There is a special built-in method [`Object.is`](mdn:js/Object/is) that compares values like `===`, but is more reliable for two edge cases:
+- `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)).
```
@@ -353,7 +393,7 @@ alert( +"100px" ); // NaN
The sole exception is spaces at the beginning or at the end of the string, as they are ignored.
-But in real life we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that.
+But in real life, we often have values in units, like `"100px"` or `"12pt"` in CSS. Also in many countries, the currency symbol goes after the amount, so we have `"19€"` and would like to extract a numeric value out of that.
That's what `parseInt` and `parseFloat` are for.
@@ -399,8 +439,8 @@ A few examples:
alert( Math.random() ); // ... (any random numbers)
```
-`Math.max(a, b, c...)` / `Math.min(a, b, c...)`
-: Returns the greatest/smallest from the arbitrary number of arguments.
+`Math.max(a, b, c...)` and `Math.min(a, b, c...)`
+: Returns the greatest and smallest from the arbitrary number of arguments.
```js run
alert( Math.max(3, 5, -10, 0, 1) ); // 5
@@ -429,6 +469,13 @@ For different numeral systems:
- `parseInt(str, base)` parses the string `str` into an integer in numeral system with given `base`, `2 ≤ base ≤ 36`.
- `num.toString(base)` converts a number to a string in the numeral system with the given `base`.
+For regular number tests:
+
+- `isNaN(value)` converts its argument to a number and then tests it for being `NaN`
+- `Number.isNaN(value)` checks whether its argument belongs to the `number` type, and if so, tests it for being `NaN`
+- `isFinite(value)` converts its argument to a number and then tests it for not being `NaN/Infinity/-Infinity`
+- `Number.isFinite(value)` checks whether its argument belongs to the `number` type, and if so, tests it for not being `NaN/Infinity/-Infinity`
+
For converting values like `12pt` and `100px` to a number:
- Use `parseInt/parseFloat` for the "soft" conversion, which reads a number from a string and then returns the value they could read before the error.
@@ -440,4 +487,4 @@ For fractions:
More mathematical functions:
-- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small, but can cover basic needs.
+- See the [Math](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Math) object when you need them. The library is very small but can cover basic needs.
diff --git a/1-js/05-data-types/03-string/1-ucfirst/solution.md b/1-js/05-data-types/03-string/1-ucfirst/solution.md
index f7a332d0d..be5dd2aaf 100644
--- a/1-js/05-data-types/03-string/1-ucfirst/solution.md
+++ b/1-js/05-data-types/03-string/1-ucfirst/solution.md
@@ -8,12 +8,7 @@ let newStr = str[0].toUpperCase() + str.slice(1);
There's a small problem though. If `str` is empty, then `str[0]` is `undefined`, and as `undefined` doesn't have the `toUpperCase()` method, we'll get an error.
-There are two variants here:
-
-1. Use `str.charAt(0)`, as it always returns a string (maybe empty).
-2. Add a test for an empty string.
-
-Here's the 2nd variant:
+The easiest way out is to add a test for an empty string, like this:
```js run demo
function ucFirst(str) {
@@ -24,4 +19,3 @@ function ucFirst(str) {
alert( ucFirst("john") ); // John
```
-
diff --git a/1-js/05-data-types/03-string/3-truncate/task.md b/1-js/05-data-types/03-string/3-truncate/task.md
index 6382029f4..c99a5f15a 100644
--- a/1-js/05-data-types/03-string/3-truncate/task.md
+++ b/1-js/05-data-types/03-string/3-truncate/task.md
@@ -11,7 +11,7 @@ The result of the function should be the truncated (if needed) string.
For instance:
```js
-truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…"
+truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…"
-truncate("Hi everyone!", 20) = "Hi everyone!"
+truncate("Hi everyone!", 20) == "Hi everyone!"
```
diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md
index 3b07eccad..60ce2b6f0 100644
--- a/1-js/05-data-types/03-string/article.md
+++ b/1-js/05-data-types/03-string/article.md
@@ -48,9 +48,9 @@ let guestList = "Guests: // Error: Unexpected token ILLEGAL
* John";
```
-Single and double quotes come from ancient times of language creation when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile.
+Single and double quotes come from ancient times of language creation, when the need for multiline strings was not taken into account. Backticks appeared much later and thus are more versatile.
-Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`
. The function `func` is called automatically, receives the string and embedded expressions and can process them. This is called "tagged templates". This feature makes it easier to implement custom templating, but is rarely used in practice. You can read more about it in the [manual](mdn:/JavaScript/Reference/Template_literals#Tagged_templates).
+Backticks also allow us to specify a "template function" before the first backtick. The syntax is: func`string`
. The function `func` is called automatically, receives the string and embedded expressions and can process them. This feature is called "tagged templates", it's rarely seen, but you can read about it in the MDN: [Template literals](mdn:/JavaScript/Reference/Template_literals#Tagged_templates).
## Special characters
@@ -59,10 +59,10 @@ It is still possible to create multiline strings with single and double quotes b
```js run
let guestList = "Guests:\n * John\n * Pete\n * Mary";
-alert(guestList); // a multiline list of guests
+alert(guestList); // a multiline list of guests, same as above
```
-For example, these two lines are equal, just written differently:
+As a simpler example, these two lines are equal, just written differently:
```js run
let str1 = "Hello\nWorld"; // two lines using a "newline symbol"
@@ -74,33 +74,26 @@ World`;
alert(str1 == str2); // true
```
-There are other, less common "special" characters.
-
-Here's the full list:
+There are other, less common special characters:
| Character | Description |
|-----------|-------------|
|`\n`|New line|
-|`\r`|Carriage return: not used alone. Windows text files use a combination of two characters `\r\n` to represent a line break. |
-|`\'`, `\"`|Quotes|
+|`\r`|In Windows text files a combination of two characters `\r\n` represents a new break, while on non-Windows OS it's just `\n`. That's for historical reasons, most Windows software also understands `\n`. |
+|`\'`, `\"`, \\`
|Quotes|
|`\\`|Backslash|
|`\t`|Tab|
-|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- kept for compatibility, not used nowadays. |
-|`\xXX`|Unicode character with the given hexadecimal Unicode `XX`, e.g. `'\x7A'` is the same as `'z'`.|
-|`\uXXXX`|A Unicode symbol with the hex code `XXXX` in UTF-16 encoding, for instance `\u00A9` -- is a Unicode for the copyright symbol `©`. It must be exactly 4 hex digits. |
-|`\u{X…XXXXXX}` (1 to 6 hex characters)|A Unicode symbol with the given UTF-32 encoding. Some rare characters are encoded with two Unicode symbols, taking 4 bytes. This way we can insert long codes. |
+|`\b`, `\f`, `\v`| Backspace, Form Feed, Vertical Tab -- mentioned for completeness, coming from old times, not used nowadays (you can forget them right now). |
+
+As you can see, all special characters start with a backslash character `\`. It is also called an "escape character".
-Examples with Unicode:
+Because it's so special, if we need to show an actual backslash `\` within the string, we need to double it:
```js run
-alert( "\u00A9" ); // ©
-alert( "\u{20331}" ); // 佫, a rare Chinese hieroglyph (long Unicode)
-alert( "\u{1F60D}" ); // 😍, a smiling face symbol (another long Unicode)
+alert( `The backslash: \\` ); // The backslash: \
```
-All special characters start with a backslash character `\`. It is also called an "escape character".
-
-We might also use it if we wanted to insert a quote into the string.
+So-called "escaped" quotes `\'`, `\"`, \\`
are used to insert a quote into the same-quoted string.
For instance:
@@ -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`:
@@ -214,7 +202,7 @@ alert( 'Interface'.toLowerCase() ); // interface
Or, if we want a single character lowercased:
-```js
+```js run
alert( 'Interface'[0].toLowerCase() ); // 'i'
```
@@ -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.
@@ -371,8 +320,8 @@ alert( "Widget".includes("id", 3) ); // false, from position 3 there is no "id"
The methods [str.startsWith](mdn:js/String/startsWith) and [str.endsWith](mdn:js/String/endsWith) do exactly what they say:
```js run
-alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
-alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
+alert( "*!*Wid*/!*get".startsWith("Wid") ); // true, "Widget" starts with "Wid"
+alert( "Wid*!*get*/!*".endsWith("get") ); // true, "Widget" ends with "get"
```
## Getting a substring
@@ -407,9 +356,9 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
```
`str.substring(start [, end])`
-: Returns the part of the string *between* `start` and `end`.
+: Returns the part of the string *between* `start` and `end` (not including `end`).
- This is almost the same as `slice`, but it allows `start` to be greater than `end`.
+ This is almost the same as `slice`, but it allows `start` to be greater than `end` (in this case it simply swaps `start` and `end` values).
For instance:
@@ -445,18 +394,22 @@ There are 3 methods in JavaScript to get a substring: `substring`, `substr` and
alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters
```
+ This method resides in the [Annex B](https://tc39.es/ecma262/#sec-string.prototype.substr) of the language specification. It means that only browser-hosted Javascript engines should support it, and it's not recommended to use it. In practice, it's supported everywhere.
+
Let's recap these methods to avoid any confusion:
| method | selects... | negatives |
|--------|-----------|-----------|
| `slice(start, end)` | from `start` to `end` (not including `end`) | allows negatives |
-| `substring(start, end)` | between `start` and `end` | negative values mean `0` |
+| `substring(start, end)` | between `start` and `end` (not including `end`)| negative values mean `0` |
| `substr(start, length)` | from `start` get `length` characters | allows negative `start` |
```smart header="Which one to choose?"
All of them can do the job. Formally, `substr` has a minor drawback: it is described not in the core JavaScript specification, but in Annex B, which covers browser-only features that exist mainly for historical reasons. So, non-browser environments may fail to support it. But in practice it works everywhere.
-Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write. So, it's enough to remember solely `slice` of these three methods.
+Of the other two variants, `slice` is a little bit more flexible, it allows negative arguments and shorter to write.
+
+So, for practical use it's enough to remember only `slice`.
```
## Comparing strings
@@ -479,17 +432,18 @@ Although, there are some oddities.
This may lead to strange results if we sort these country names. Usually people would expect `Zealand` to come after `Österreich` in the list.
-To understand what happens, let's review the internal representation of strings in JavaScript.
+To understand what happens, we should be aware that strings in Javascript are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code.
-All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). That is: each character has a corresponding numeric code. There are special methods that allow to get the character for the code and back.
+There are special methods that allow to get the character for the code and back:
`str.codePointAt(pos)`
-: Returns the code for the character at position `pos`:
+: Returns a decimal number representing the code for the character at position `pos`:
```js run
// different case letters have different codes
- alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90
+ alert( "z".codePointAt(0) ); // 122
+ alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value)
```
`String.fromCodePoint(code)`
@@ -497,13 +451,7 @@ All strings are encoded using [UTF-16](https://en.wikipedia.org/wiki/UTF-16). Th
```js run
alert( String.fromCodePoint(90) ); // Z
- ```
-
- We can also add Unicode characters by their codes using `\u` followed by the hex code:
-
- ```js run
- // 90 is 5a in hexadecimal system
- alert( '\u005a' ); // Z
+ alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument)
```
Now let's see the characters with codes `65..220` (the latin alphabet and a little bit extra) by making a string of them:
@@ -515,6 +463,7 @@ for (let i = 65; i <= 220; i++) {
str += String.fromCodePoint(i);
}
alert( str );
+// Output:
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ
```
@@ -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 befd80296..7e1ca3bde 100644
--- a/1-js/05-data-types/04-array/10-maximal-subarray/solution.md
+++ b/1-js/05-data-types/04-array/10-maximal-subarray/solution.md
@@ -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 16d14071f..d4551c79c 100644
--- a/1-js/05-data-types/04-array/2-create-array/task.md
+++ b/1-js/05-data-types/04-array/2-create-array/task.md
@@ -8,7 +8,7 @@ Let's try 5 array operations.
1. Create an array `styles` with items "Jazz" and "Blues".
2. Append "Rock-n-Roll" to the end.
-3. Replace the value in the middle by "Classics". Your code for finding the middle value should work for any arrays with odd length.
+3. Replace the value in the middle with "Classics". Your code for finding the middle value should work for any arrays with odd length.
4. Strip off the first value of the array and show it.
5. Prepend `Rap` and `Reggae` to the array.
diff --git a/1-js/05-data-types/04-array/3-call-array-this/task.md b/1-js/05-data-types/04-array/3-call-array-this/task.md
index 340c5feef..f1e13499c 100644
--- a/1-js/05-data-types/04-array/3-call-array-this/task.md
+++ b/1-js/05-data-types/04-array/3-call-array-this/task.md
@@ -11,7 +11,7 @@ let arr = ["a", "b"];
arr.push(function() {
alert( this );
-})
+});
arr[2](); // ?
```
diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md
index a86dead64..e71e86a5b 100644
--- a/1-js/05-data-types/04-array/article.md
+++ b/1-js/05-data-types/04-array/article.md
@@ -92,6 +92,38 @@ let fruits = [
The "trailing comma" style makes it easier to insert/remove items, because all lines become alike.
````
+## Get last elements with "at"
+
+[recent browser="new"]
+
+Let's say we want the last element of the array.
+
+Some programming languages allow the use of negative indexes for the same purpose, like `fruits[-1]`.
+
+Although, in JavaScript it won't work. The result will be `undefined`, because the index in square brackets is treated literally.
+
+We can explicitly calculate the last element index and then access it: `fruits[fruits.length - 1]`.
+
+```js run
+let fruits = ["Apple", "Orange", "Plum"];
+
+alert( fruits[fruits.length-1] ); // Plum
+```
+
+A bit cumbersome, isn't it? We need to write the variable name twice.
+
+Luckily, there's a shorter syntax: `fruits.at(-1)`:
+
+```js run
+let fruits = ["Apple", "Orange", "Plum"];
+
+// same as fruits[fruits.length-1]
+alert( fruits.at(-1) ); // Plum
+```
+
+In other words, `arr.at(i)`:
+- is exactly the same as `arr[i]`, if `i >= 0`.
+- for negative values of `i`, it steps back from the end of the array.
## Methods pop/push, shift/unshift
@@ -121,9 +153,9 @@ A stack is usually illustrated as a pack of cards: new cards are added to the to
For stacks, the latest pushed item is received first, that's also called LIFO (Last-In-First-Out) principle. For queues, we have FIFO (First-In-First-Out).
-Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements both to/from the beginning or the end.
+Arrays in JavaScript can work both as a queue and as a stack. They allow you to add/remove elements, both to/from the beginning or the end.
-In computer science the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
+In computer science, the data structure that allows this, is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue).
**Methods that work with the end of the array:**
@@ -138,6 +170,8 @@ In computer science the data structure that allows this, is called [deque](https
alert( fruits ); // Apple, Orange
```
+ Both `fruits.pop()` and `fruits.at(-1)` return the last element of the array, but `fruits.pop()` also modifies the array by removing it.
+
`push`
: Append the element to the end of the array:
@@ -247,7 +281,7 @@ Why is it faster to work with the end of an array than with its beginning? Let's
fruits.shift(); // take 1 element from the start
```
-It's not enough to take and remove the element with the number `0`. Other elements need to be renumbered as well.
+It's not enough to take and remove the element with the index `0`. Other elements need to be renumbered as well.
The `shift` operation must do 3 things:
@@ -365,11 +399,11 @@ There is one more syntax to create an array:
let arr = *!*new Array*/!*("Apple", "Pear", "etc");
```
-It's rarely used, because square brackets `[]` are shorter. Also there's a tricky feature with it.
+It's rarely used, because square brackets `[]` are shorter. Also, there's a tricky feature with it.
If `new Array` is called with a single argument which is a number, then it creates an array *without items, but with the given length*.
-Let's see how one can shoot themself in the foot:
+Let's see how one can shoot themselves in the foot:
```js run
let arr = new Array(2); // will it create an array of [2] ?
@@ -392,7 +426,7 @@ let matrix = [
[7, 8, 9]
];
-alert( matrix[1][1] ); // 5, the central element
+alert( matrix[0][1] ); // 2, the second value of the first inner array
```
## toString
@@ -439,7 +473,7 @@ Let's recall the rules:
- If one of the arguments of `==` is an object, and the other one is a primitive, then the object gets converted to primitive, as explained in the chapter .
- ...With an exception of `null` and `undefined` that equal `==` each other and nothing else.
-The strict comparison `===` is even simpler, as it doesn't convert types.
+The strict comparison `===` is even simpler, as it doesn't convert types.
So, if we compare arrays with `==`, they are never the same, unless we compare two variables that reference exactly the same array.
@@ -459,7 +493,7 @@ alert( 0 == [] ); // true
alert('0' == [] ); // false
```
-Here, in both cases, we compare a primitive with an array object. So the array `[]` gets converted to primitive for the purpose of comparison and becomes an empty string `''`.
+Here, in both cases, we compare a primitive with an array object. So the array `[]` gets converted to primitive for the purpose of comparison and becomes an empty string `''`.
Then the comparison process goes on with the primitives, as described in the chapter :
@@ -478,21 +512,26 @@ That's simple: don't use the `==` operator. Instead, compare them item-by-item i
Array is a special kind of object, suited to storing and managing ordered data items.
-- The declaration:
+The declaration:
- ```js
- // square brackets (usual)
- let arr = [item1, item2...];
+```js
+// square brackets (usual)
+let arr = [item1, item2...];
- // new Array (exceptionally rare)
- let arr = new Array(item1, item2...);
- ```
+// new Array (exceptionally rare)
+let arr = new Array(item1, item2...);
+```
- The call to `new Array(number)` creates an array with the given length, but without elements.
+The call to `new Array(number)` creates an array with the given length, but without elements.
- The `length` property is the array length or, to be precise, its last numeric index plus one. It is auto-adjusted by array methods.
- If we shorten `length` manually, the array is truncated.
+Getting the elements:
+
+- we can get element by its index, like `arr[0]`
+- also we can use `at(i)` method that allows negative indexes. For negative values of `i`, it steps back from the end of the array. If `i >= 0`, it works same as `arr[i]`.
+
We can use an array as a deque with the following operations:
- `push(...items)` adds `items` to the end.
diff --git a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md
index d3c8f8eb1..7f0082357 100644
--- a/1-js/05-data-types/05-array-methods/12-reduce-object/task.md
+++ b/1-js/05-data-types/05-array-methods/12-reduce-object/task.md
@@ -4,7 +4,7 @@ importance: 4
# Create keyed object from array
-Let's say we received an array of users in the form `{id:..., name:..., age... }`.
+Let's say we received an array of users in the form `{id:..., name:..., age:... }`.
Create a function `groupById(arr)` that creates an object from it, with `id` as the key, and array items as values.
diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js
index db32d9a11..241b74c6e 100644
--- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js
+++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/test.js
@@ -4,13 +4,13 @@ describe("filterRangeInPlace", function() {
let arr = [5, 3, 8, 1];
- filterRangeInPlace(arr, 1, 4);
+ filterRangeInPlace(arr, 2, 5);
- assert.deepEqual(arr, [3, 1]);
+ assert.deepEqual(arr, [5, 3]);
});
it("doesn't return anything", function() {
assert.isUndefined(filterRangeInPlace([1,2,3], 1, 4));
});
-});
\ No newline at end of file
+});
diff --git a/1-js/05-data-types/05-array-methods/article.md b/1-js/05-data-types/05-array-methods/article.md
index a2e3c1ca5..1d24ebbed 100644
--- a/1-js/05-data-types/05-array-methods/article.md
+++ b/1-js/05-data-types/05-array-methods/article.md
@@ -1,6 +1,6 @@
# Array methods
-Arrays provide a lot of methods. To make things easier, in this chapter they are split into groups.
+Arrays provide a lot of methods. To make things easier, in this chapter, they are split into groups.
## Add/remove items
@@ -32,11 +32,11 @@ alert( arr.length ); // 3
The element was removed, but the array still has 3 elements, we can see that `arr.length == 3`.
-That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of elements to shift and occupy the freed place. We expect to have a shorter array now.
+That's natural, because `delete obj.key` removes a value by the `key`. It's all it does. Fine for objects. But for arrays we usually want the rest of the elements to shift and occupy the freed place. We expect to have a shorter array now.
So, special methods should be used.
-The [arr.splice](mdn:js/Array/splice) method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements.
+The [arr.splice](mdn:js/Array/splice) method is a Swiss army knife for arrays. It can do everything: insert, remove and replace elements.
The syntax is:
@@ -62,7 +62,7 @@ alert( arr ); // ["I", "JavaScript"]
Easy, right? Starting from the index `1` it removed `1` element.
-In the next example we remove 3 elements and replace them with the other two:
+In the next example, we remove 3 elements and replace them with the other two:
```js run
let arr = [*!*"I", "study", "JavaScript",*/!* "right", "now"];
@@ -84,7 +84,7 @@ let removed = arr.splice(0, 2);
alert( removed ); // "I", "study" <-- array of removed elements
```
-The `splice` method is also able to insert the elements without any removals. For that we need to set `deleteCount` to `0`:
+The `splice` method is also able to insert the elements without any removals. For that, we need to set `deleteCount` to `0`:
```js run
let arr = ["I", "study", "JavaScript"];
@@ -114,7 +114,7 @@ alert( arr ); // 1,2,3,4,5
### slice
-The method [arr.slice](mdn:js/Array/slice) is much simpler than similar-looking `arr.splice`.
+The method [arr.slice](mdn:js/Array/slice) is much simpler than the similar-looking `arr.splice`.
The syntax is:
@@ -124,7 +124,7 @@ arr.slice([start], [end])
It returns a new array copying to it all items from index `start` to `end` (not including `end`). Both `start` and `end` can be negative, in that case position from array end is assumed.
-It's similar to a string method `str.slice`, but instead of substrings it makes subarrays.
+It's similar to a string method `str.slice`, but instead of substrings, it makes subarrays.
For instance:
@@ -206,7 +206,7 @@ The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for ever
The syntax:
```js
arr.forEach(function(item, index, array) {
- // ... do something with item
+ // ... do something with an item
});
```
@@ -234,12 +234,13 @@ Now let's cover methods that search in an array.
### indexOf/lastIndexOf and includes
-The methods [arr.indexOf](mdn:js/Array/indexOf), [arr.lastIndexOf](mdn:js/Array/lastIndexOf) and [arr.includes](mdn:js/Array/includes) have the same syntax and do essentially the same as their string counterparts, but operate on items instead of characters:
+The methods [arr.indexOf](mdn:js/Array/indexOf) and [arr.includes](mdn:js/Array/includes) have the similar syntax and do essentially the same as their string counterparts, but operate on items instead of characters:
- `arr.indexOf(item, from)` -- looks for `item` starting from index `from`, and returns the index where it was found, otherwise `-1`.
-- `arr.lastIndexOf(item, from)` -- same, but looks for from right to left.
- `arr.includes(item, from)` -- looks for `item` starting from index `from`, returns `true` if found.
+Usually, these methods are used with only one argument: the `item` to search. By default, the search is from the beginning.
+
For instance:
```js run
@@ -252,21 +253,33 @@ alert( arr.indexOf(null) ); // -1
alert( arr.includes(1) ); // true
```
-Note that the methods use `===` comparison. So, if we look for `false`, it finds exactly `false` and not the zero.
+Please note that `indexOf` uses the strict equality `===` for comparison. So, if we look for `false`, it finds exactly `false` and not the zero.
+
+If we want to check 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.
-If we want to check for inclusion, and don't want to know the exact index, then `arr.includes` is preferred.
+```js run
+let fruits = ['Apple', 'Orange', 'Apple']
+
+alert( fruits.indexOf('Apple') ); // 0 (first Apple)
+alert( fruits.lastIndexOf('Apple') ); // 2 (last Apple)
+```
-Also, a very minor difference of `includes` is that it correctly handles `NaN`, unlike `indexOf/lastIndexOf`:
+````smart header="The `includes` method handles `NaN` correctly"
+A minor, but noteworthy feature of `includes` is that it correctly handles `NaN`, unlike `indexOf`:
```js run
const arr = [NaN];
-alert( arr.indexOf(NaN) ); // -1 (should be 0, but === equality doesn't work for NaN)
+alert( arr.indexOf(NaN) ); // -1 (wrong, should be 0)
alert( arr.includes(NaN) );// true (correct)
```
+That's because `includes` was added to JavaScript much later and uses the more up-to-date comparison algorithm internally.
+````
-### find and findIndex
+### find and findIndex/findLastIndex
-Imagine we have an array of objects. How do we find an object with the specific condition?
+Imagine we have an array of objects. How do we find an object with a specific condition?
Here the [arr.find(fn)](mdn:js/Array/find) method comes in handy.
@@ -284,7 +297,7 @@ The function is called for elements of the array, one after another:
- `index` is its index.
- `array` is the array itself.
-If it returns `true`, the search is stopped, the `item` is returned. If nothing found, `undefined` is returned.
+If it returns `true`, the search is stopped, the `item` is returned. If nothing is found, `undefined` is returned.
For example, we have an array of users, each with the fields `id` and `name`. Let's find the one with `id == 1`:
@@ -300,11 +313,30 @@ let user = users.find(item => item.id == 1);
alert(user.name); // John
```
-In real life arrays of objects is a common thing, so the `find` method is very useful.
+In real life, arrays of objects are a common thing, so the `find` method is very useful.
Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. That's typical, other arguments of this function are rarely used.
-The [arr.findIndex](mdn:js/Array/findIndex) method is essentially the same, but it returns the index where the element was found instead of the element itself and `-1` is returned when nothing is found.
+The [arr.findIndex](mdn:js/Array/findIndex) method has the same syntax but returns the index where the element was found instead of the element itself. The value of `-1` is returned if nothing is found.
+
+The [arr.findLastIndex](mdn:js/Array/findLastIndex) method is like `findIndex`, but searches from right to left, similar to `lastIndexOf`.
+
+Here's an example:
+
+```js run
+let users = [
+ {id: 1, name: "John"},
+ {id: 2, name: "Pete"},
+ {id: 3, name: "Mary"},
+ {id: 4, name: "John"}
+];
+
+// Find the index of the first John
+alert(users.findIndex(user => user.name == 'John')); // 0
+
+// Find the index of the last John
+alert(users.findLastIndex(user => user.name == 'John')); // 3
+```
### filter
@@ -389,6 +421,7 @@ Literally, all elements are converted to strings for comparisons. For strings, l
To use our own sorting order, we need to supply a function as the argument of `arr.sort()`.
The function should compare two arbitrary values and return:
+
```js
function compare(a, b) {
if (a > b) return 1; // if the first value is greater than the second
@@ -417,11 +450,11 @@ alert(arr); // *!*1, 2, 15*/!*
Now it works as intended.
-Let's step aside and think what's happening. The `arr` can be array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order.
+Let's step aside and think about what's happening. The `arr` can be an array of anything, right? It may contain numbers or strings or objects or whatever. We have a set of *some items*. To sort it, we need an *ordering function* that knows how to compare its elements. The default is a string order.
The `arr.sort(fn)` method implements a generic sorting algorithm. We don't need to care how it internally works (an optimized [quicksort](https://en.wikipedia.org/wiki/Quicksort) or [Timsort](https://en.wikipedia.org/wiki/Timsort) most of the time). It will walk the array, compare its elements using the provided function and reorder them, all we need is to provide the `fn` which does the comparison.
-By the way, if we ever want to know which elements are compared -- nothing prevents from alerting them:
+By the way, if we ever want to know which elements are compared -- nothing prevents us from alerting them:
```js run
[1, -2, 15, 2, 0, 8].sort(function(a, b) {
@@ -493,7 +526,7 @@ Here's the situation from real life. We are writing a messaging app, and the per
The [str.split(delim)](mdn:js/String/split) method does exactly that. It splits the string into an array by the given delimiter `delim`.
-In the example below, we split by a comma followed by space:
+In the example below, we split by a comma followed by a space:
```js run
let names = 'Bilbo, Gandalf, Nazgul';
@@ -560,9 +593,9 @@ Arguments:
- `index` -- is its position.
- `array` -- is the array.
-As function is applied, the result of the previous function call is passed to the next one as the first argument.
+As the function is applied, the result of the previous function call is passed to the next one as the first argument.
-So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end it becomes the result of `reduce`.
+So, the first argument is essentially the accumulator that stores the combined result of all previous executions. And at the end, it becomes the result of `reduce`.
Sounds complicated?
@@ -631,8 +664,7 @@ arr.reduce((sum, current) => sum + current);
So it's advised to always specify the initial value.
-The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same, but goes from right to left.
-
+The method [arr.reduceRight](mdn:js/Array/reduceRight) does the same but goes from right to left.
## Array.isArray
@@ -642,7 +674,7 @@ So `typeof` does not help to distinguish a plain object from an array:
```js run
alert(typeof {}); // object
-alert(typeof []); // same
+alert(typeof []); // object (same)
```
...But arrays are used so often that there's a special method for that: [Array.isArray(value)](mdn:js/Array/isArray). It returns `true` if the `value` is an array, and `false` otherwise.
@@ -657,7 +689,7 @@ alert(Array.isArray([])); // true
Almost all array methods that call functions -- like `find`, `filter`, `map`, with a notable exception of `sort`, accept an optional additional parameter `thisArg`.
-That parameter is not explained in the sections above, because it's rarely used. But for completeness we have to cover it.
+That parameter is not explained in the sections above, because it's rarely used. But for completeness, we have to cover it.
Here's the full syntax of these methods:
@@ -717,7 +749,7 @@ A cheat sheet of array methods:
- `concat(...items)` -- returns a new array: copies all members of the current one and adds `items` to it. If any of `items` is an array, then its elements are taken.
- To search among elements:
- - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, return the index or `-1` if not found.
+ - `indexOf/lastIndexOf(item, pos)` -- look for `item` starting from position `pos`, and return the index or `-1` if not found.
- `includes(value)` -- returns `true` if the array has `value`, otherwise `false`.
- `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`.
- `findIndex` is like `find`, but returns the index instead of a value.
@@ -733,7 +765,7 @@ A cheat sheet of array methods:
- `reduce/reduceRight(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls.
- Additionally:
- - `Array.isArray(arr)` checks `arr` for being an array.
+ - `Array.isArray(value)` checks `value` for being an array, if so returns `true`, otherwise `false`.
Please note that methods `sort`, `reverse` and `splice` modify the array itself.
@@ -748,6 +780,7 @@ These methods are the most used ones, they cover 99% of use cases. But there are
These methods behave sort of like `||` and `&&` operators: if `fn` returns a truthy value, `arr.some()` immediately returns `true` and stops iterating over the rest of items; if `fn` returns a falsy value, `arr.every()` immediately returns `false` and stops iterating over the rest of items as well.
We can use `every` to compare arrays:
+
```js run
function arraysEqual(arr1, arr2) {
return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
@@ -764,7 +797,7 @@ These methods are the most used ones, they cover 99% of use cases. But there are
For the full list, see the [manual](mdn:js/Array).
-From the first sight it may seem that there are so many methods, quite difficult to remember. But actually that's much easier.
+At first sight, it may seem that there are so many methods, quite difficult to remember. But actually, that's much easier.
Look through the cheat sheet just to be aware of them. Then solve the tasks of this chapter to practice, so that you have experience with array methods.
diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md
index 37d7e31e5..e2c0d4f97 100644
--- a/1-js/05-data-types/06-iterable/article.md
+++ b/1-js/05-data-types/06-iterable/article.md
@@ -31,7 +31,7 @@ To make the `range` object iterable (and thus let `for..of` work) we need to add
1. When `for..of` starts, it calls that method once (or errors if not found). The method must return an *iterator* -- an object with the method `next`.
2. Onward, `for..of` works *only with that returned object*.
3. When `for..of` wants the next value, it calls `next()` on that object.
-4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the iteration is finished, otherwise `value` is the next value.
+4. The result of `next()` must have the form `{done: Boolean, value: any}`, where `done=true` means that the loop is finished, otherwise `value` is the next value.
Here's the full implementation for `range` with remarks:
@@ -45,10 +45,10 @@ let range = {
range[Symbol.iterator] = function() {
// ...it returns the iterator object:
- // 2. Onward, for..of works only with this iterator, asking it for next values
+ // 2. Onward, for..of works only with the iterator object below, asking it for next values
return {
current: this.from,
- last: this.to,
+ last: this.to,
// 3. next() is called on each iteration by the for..of loop
next() {
@@ -174,7 +174,7 @@ When we use JavaScript for practical tasks in a browser or any other environment
For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`).
-But an iterable may be not array-like. And vice versa an array-like may be not iterable.
+But an iterable may not be array-like. And vice versa an array-like may not be iterable.
For example, the `range` in the example above is iterable, but not array-like, because it does not have indexed properties and `length`.
@@ -218,7 +218,7 @@ alert(arr.pop()); // World (method works)
The same happens for an iterable:
-```js
+```js run
// assuming that range is taken from the example above
let arr = Array.from(range);
alert(arr); // 1,2,3,4,5 (array toString conversion works)
@@ -233,7 +233,7 @@ The optional second argument `mapFn` can be a function that will be applied to e
For instance:
-```js
+```js run
// assuming that range is taken from the example above
// square each number
@@ -270,7 +270,7 @@ for (let char of str) {
alert(chars);
```
-...But it is shorter.
+...But it is shorter.
We can even build surrogate-aware `slice` on it:
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 bd6cad562..37f5e48c2 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 8d5a86981..9795017d4 100644
--- a/1-js/05-data-types/08-weakmap-weakset/article.md
+++ b/1-js/05-data-types/08-weakmap-weakset/article.md
@@ -1,8 +1,10 @@
+
# WeakMap and WeakSet
As we know from the chapter , JavaScript engine 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/09-keys-values-entries/article.md b/1-js/05-data-types/09-keys-values-entries/article.md
index 28c48a533..bef678f53 100644
--- a/1-js/05-data-types/09-keys-values-entries/article.md
+++ b/1-js/05-data-types/09-keys-values-entries/article.md
@@ -77,7 +77,7 @@ Objects lack many methods that exist for arrays, e.g. `map`, `filter` and others
If we'd like to apply them, then we can use `Object.entries` followed by `Object.fromEntries`:
1. Use `Object.entries(obj)` to get an array of key/value pairs from `obj`.
-2. Use array methods on that array, e.g. `map`.
+2. Use array methods on that array, e.g. `map`, to transform these key/value pairs.
3. Use `Object.fromEntries(array)` on the resulting array to turn it back into an object.
For example, we have an object with prices, and would like to double them:
@@ -91,12 +91,13 @@ let prices = {
*!*
let doublePrices = Object.fromEntries(
- // convert to array, map, and then fromEntries gives back the object
- Object.entries(prices).map(([key, value]) => [key, value * 2])
+ // convert prices to array, map each key/value pair into another pair
+ // and then fromEntries gives back the object
+ Object.entries(prices).map(entry => [entry[0], entry[1] * 2])
);
*/!*
alert(doublePrices.meat); // 8
-```
+```
-It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way.
+It may look difficult at first sight, but becomes easy to understand after you use it once or twice. We can make powerful chains of transforms this way.
diff --git a/1-js/05-data-types/10-destructuring-assignment/article.md b/1-js/05-data-types/10-destructuring-assignment/article.md
index fb9346aa2..0c52741d1 100644
--- a/1-js/05-data-types/10-destructuring-assignment/article.md
+++ b/1-js/05-data-types/10-destructuring-assignment/article.md
@@ -2,21 +2,21 @@
The two most used data structures in JavaScript are `Object` and `Array`.
-- Objects allow us to create a single entity that stores data items by key.
+- Objects allow us to create a single entity that stores data items by key.
- Arrays allow us to gather data items into an ordered list.
-Although, when we pass those to a function, it may need not an object/array as a whole. It may need individual pieces.
+However, when we pass these to a function, we may not need all of it. The function might only require certain elements or properties.
-*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient.
+*Destructuring assignment* is a special syntax that allows us to "unpack" arrays or objects into a bunch of variables, as sometimes that's more convenient.
-Destructuring also works great with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that.
+Destructuring also works well with complex functions that have a lot of parameters, default values, and so on. Soon we'll see that.
## Array destructuring
Here's an example of how an array is destructured into variables:
```js
-// we have an array with the name and surname
+// we have an array with a name and surname
let arr = ["John", "Smith"]
*!*
@@ -40,10 +40,10 @@ alert(firstName); // John
alert(surname); // Smith
```
-As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples, to better understand it.
+As you can see, the syntax is simple. There are several peculiar details though. Let's see more examples to understand it better.
````smart header="\"Destructuring\" does not mean \"destructive\"."
-It's called "destructuring assignment," because it "destructurizes" by copying items into variables. But the array itself is not modified.
+It's called "destructuring assignment," because it "destructurizes" by copying items into variables. However, the array itself is not modified.
It's just a shorter way to write:
```js
@@ -65,7 +65,7 @@ let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic
alert( title ); // Consul
```
-In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items is also skipped (as there are no variables for them).
+In the code above, the second element of the array is skipped, the third one is assigned to `title`, and the rest of the array items are also skipped (as there are no variables for them).
````
````smart header="Works with any iterable on the right-side"
@@ -76,12 +76,12 @@ In the code above, the second element of the array is skipped, the third one is
let [a, b, c] = "abc"; // ["a", "b", "c"]
let [one, two, three] = new Set([1, 2, 3]);
```
-That works, because internally a destructuring assignment works by iterating over the right value. It's kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values.
+That works, because internally a destructuring assignment works by iterating over the right value. It's a kind of syntax sugar for calling `for..of` over the value to the right of `=` and assigning the values.
````
````smart header="Assign to anything at the left-side"
-We can use any "assignables" at the left side.
+We can use any "assignables" on the left side.
For instance, an object property:
```js run
@@ -95,9 +95,9 @@ alert(user.surname); // Smith
````
````smart header="Looping with .entries()"
-In the previous chapter we saw the [Object.entries(obj)](mdn:js/Object/entries) method.
+In the previous chapter, we saw the [Object.entries(obj)](mdn:js/Object/entries) method.
-We can use it with destructuring to loop over keys-and-values of an object:
+We can use it with destructuring to loop over the keys-and-values of an object:
```js run
let user = {
@@ -105,7 +105,7 @@ let user = {
age: 30
};
-// loop over keys-and-values
+// loop over the keys-and-values
*!*
for (let [key, value] of Object.entries(user)) {
*/!*
@@ -169,14 +169,14 @@ If we'd like also to gather all that follows -- we can add one more parameter th
let [name1, name2, *!*...rest*/!*] = ["Julius", "Caesar", *!*"Consul", "of the Roman Republic"*/!*];
*!*
-// rest is array of items, starting from the 3rd one
+// rest is an array of items, starting from the 3rd one
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
*/!*
```
-The value of `rest` is the array of the remaining array elements.
+The value of `rest` is the array of the remaining array elements.
We can use any other variable name in place of `rest`, just make sure it has three dots before it and goes last in the destructuring assignment.
@@ -187,7 +187,7 @@ let [name1, name2, *!*...titles*/!*] = ["Julius", "Caesar", "Consul", "of the Ro
### Default values
-If the array is shorter than the list of variables at the left, there'll be no errors. Absent values are considered undefined:
+If the array is shorter than the list of variables on the left, there will be no errors. Absent values are considered undefined:
```js run
*!*
@@ -234,7 +234,7 @@ The basic syntax is:
let {var1, var2} = {var1:…, var2:…}
```
-We should have an existing object at the right side, that we want to split into variables. The left side contains an object-like "pattern" for corresponding properties. In the simplest case, that's a list of variable names in `{...}`.
+We should have an existing object on the right side, that we want to split into variables. The left side contains an object-like "pattern" for corresponding properties. In the simplest case, that's a list of variable names in `{...}`.
For instance:
@@ -254,7 +254,7 @@ alert(width); // 100
alert(height); // 200
```
-Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables.
+Properties `options.title`, `options.width` and `options.height` are assigned to the corresponding variables.
The order does not matter. This works too:
@@ -418,9 +418,9 @@ alert( title ); // Menu
## Nested destructuring
-If an object or an array contain other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions.
+If an object or an array contains other nested objects and arrays, we can use more complex left-side patterns to extract deeper portions.
-In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern at the left side of the assignment has the same structure to extract values from them:
+In the code below `options` has another object in the property `size` and an array in the property `items`. The pattern on the left side of the assignment has the same structure to extract values from them:
```js run
let options = {
@@ -429,7 +429,7 @@ let options = {
height: 200
},
items: ["Cake", "Donut"],
- extra: true
+ extra: true
};
// destructuring assignment split in multiple lines for clarity
@@ -449,7 +449,7 @@ alert(item1); // Cake
alert(item2); // Donut
```
-All properties of `options` object except `extra` that is absent in the left part, are assigned to corresponding variables:
+All properties of `options` object except `extra` which is absent in the left part, are assigned to corresponding variables:

@@ -459,9 +459,9 @@ Note that there are no variables for `size` and `items`, as we take their conten
## Smart function parameters
-There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on.
+There are times when a function has many parameters, most of which are optional. That's especially true for user interfaces. Imagine a function that creates a menu. It may have a width, a height, a title, an item list and so on.
-Here's a bad way to write such function:
+Here's a bad way to write such a function:
```js
function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
@@ -469,7 +469,7 @@ function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
}
```
-In real-life, the problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default.
+In real-life, the problem is how to remember the order of arguments. Usually, IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default.
Like this?
@@ -534,7 +534,7 @@ function({
})
```
-Then, for an object of parameters, there will be a variable `varName` for property `incomingProperty`, with `defaultValue` by default.
+Then, for an object of parameters, there will be a variable `varName` for the property `incomingProperty`, with `defaultValue` by default.
Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object:
@@ -561,7 +561,7 @@ In the code above, the whole arguments object is `{}` by default, so there's alw
- Destructuring assignment allows for instantly mapping an object or array onto many variables.
- The full object syntax:
```js
- let {prop : varName = default, ...rest} = object
+ let {prop : varName = defaultValue, ...rest} = object
```
This means that property `prop` should go into the variable `varName` and, if no such property exists, then the `default` value should be used.
@@ -571,9 +571,9 @@ In the code above, the whole arguments object is `{}` by default, so there's alw
- The full array syntax:
```js
- let [item1 = default, item2, ...rest] = array
+ let [item1 = defaultValue, item2, ...rest] = array
```
- The first item goes to `item1`; the second goes into `item2`, all the rest makes the array `rest`.
+ The first item goes to `item1`; the second goes into `item2`, and all the rest makes the array `rest`.
- It's possible to extract data from nested arrays/objects, for that the left side must have the same structure as the right one.
diff --git a/1-js/05-data-types/11-date/1-new-date/solution.md b/1-js/05-data-types/11-date/1-new-date/solution.md
index bed449453..18286c336 100644
--- a/1-js/05-data-types/11-date/1-new-date/solution.md
+++ b/1-js/05-data-types/11-date/1-new-date/solution.md
@@ -13,6 +13,6 @@ We could also create a date from a string, like this:
```js run
//new Date(datastring)
-let d2 = new Date("February 20, 2012 03:12:00");
+let d2 = new Date("2012-02-20T03:12");
alert( d2 );
```
diff --git a/1-js/05-data-types/11-date/article.md b/1-js/05-data-types/11-date/article.md
index ed4e21359..6958a3a97 100644
--- a/1-js/05-data-types/11-date/article.md
+++ b/1-js/05-data-types/11-date/article.md
@@ -57,7 +57,7 @@ To create a new `Date` object call `new Date()` with one of the following argume
`new Date(year, month, date, hours, minutes, seconds, ms)`
: Create the date with the given components in the local time zone. Only the first two arguments are obligatory.
- - The `year` must have 4 digits: `2013` is okay, `98` is not.
+ - The `year` should have 4 digits. For compatibility, 2 digits are also accepted and considered `19xx`, e.g. `98` is the same as `1998` here, but always using 4 digits is strongly encouraged.
- The `month` count starts with `0` (Jan), up to `11` (Dec).
- The `date` parameter is actually the day of month, if absent then `1` is assumed.
- If `hours/minutes/seconds/ms` is absent, they are assumed to be equal `0`.
@@ -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
@@ -407,7 +407,7 @@ We can instantly create a `new Date` object from the timestamp:
```js run
let date = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );
-alert(date);
+alert(date);
```
## Summary
diff --git a/1-js/05-data-types/12-json/article.md b/1-js/05-data-types/12-json/article.md
index 425022f8a..133ffb353 100644
--- a/1-js/05-data-types/12-json/article.md
+++ b/1-js/05-data-types/12-json/article.md
@@ -27,7 +27,7 @@ Luckily, there's no need to write the code to handle all this. The task has been
## JSON.stringify
-The [JSON](http://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](http://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever.
+The [JSON](https://en.wikipedia.org/wiki/JSON) (JavaScript Object Notation) is a general format to represent values and objects. It is described as in [RFC 4627](https://tools.ietf.org/html/rfc4627) standard. Initially it was made for JavaScript, but many other languages have libraries to handle it as well. So it's easy to use JSON for data exchange when the client uses JavaScript and the server is written on Ruby/PHP/Java/Whatever.
JavaScript provides methods:
@@ -41,7 +41,7 @@ let student = {
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
- wife: null
+ spouse: null
};
*!*
@@ -58,7 +58,7 @@ alert(json);
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
- "wife": null
+ "spouse": null
}
*/
*/!*
@@ -405,7 +405,7 @@ To decode a JSON-string, we need another method named [JSON.parse](mdn:js/JSON/p
The syntax:
```js
-let value = JSON.parse(str, [reviver]);
+let value = JSON.parse(str[, reviver]);
```
str
@@ -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 3a281ef3f..11667f940 100644
--- a/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md
+++ b/1-js/06-advanced-functions/01-recursion/01-sum-to/solution.md
@@ -37,4 +37,4 @@ P.S. Naturally, the formula is the fastest solution. It uses only 3 operations f
The loop variant is the second in terms of speed. In both the recursive and the loop variant we sum the same numbers. But the recursion involves nested calls and execution stack management. That also takes resources, so it's slower.
-P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function (like in `sumTo` above), then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory, so counting `sumTo(100000)` becomes possible. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size.
+P.P.S. Some engines support the "tail call" optimization: if a recursive call is the very last one in the function, with no other calculations performed, then the outer function will not need to resume the execution, so the engine doesn't need to remember its execution context. That removes the burden on memory. But if the JavaScript engine does not support tail call optimization (most of them don't), there will be an error: maximum stack size exceeded, because there's usually a limitation on the total stack size.
diff --git a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md
index 4357ff208..0eb76ea1c 100644
--- a/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md
+++ b/1-js/06-advanced-functions/01-recursion/05-output-single-linked-list-reverse/solution.md
@@ -33,7 +33,7 @@ printReverseList(list);
# Using a loop
-The loop variant is also a little bit more complicated then the direct output.
+The loop variant is also a little bit more complicated than the direct output.
There is no way to get the last value in our `list`. We also can't "go back".
diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md
index 17fe5ea3e..5ae894474 100644
--- a/1-js/06-advanced-functions/01-recursion/article.md
+++ b/1-js/06-advanced-functions/01-recursion/article.md
@@ -61,7 +61,7 @@ When `pow(x, n)` is called, the execution splits into two branches:
if n==1 = x
/
pow(x, n) =
- \
+ \
else = x * pow(x, n - 1)
```
@@ -285,7 +285,7 @@ The iterative `pow` uses a single context changing `i` and `result` in the proce
**Any recursion can be rewritten as a loop. The loop variant usually can be made more effective.**
-...But sometimes the rewrite is non-trivial, especially when function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts.
+...But sometimes the rewrite is non-trivial, especially when a function uses different recursive subcalls depending on conditions and merges their results or when the branching is more intricate. And the optimization may be unneeded and totally not worth the efforts.
Recursion can give a shorter code, easier to understand and support. Optimizations are not required in every place, mostly we need a good code, that's why it's used.
@@ -535,7 +535,7 @@ Terms:
list = { value, next -> list }
```
- Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they branch and every branch can have other branches.
+ Trees like HTML elements tree or the department tree from this chapter are also naturally recursive: they have branches and every branch can have other branches.
Recursive functions can be used to walk them as we've seen in the `sumSalary` example.
diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md
index c63fe70cd..dbdfbd6c0 100644
--- a/1-js/06-advanced-functions/02-rest-parameters-spread/article.md
+++ b/1-js/06-advanced-functions/02-rest-parameters-spread/article.md
@@ -23,7 +23,7 @@ function sum(a, b) {
alert( sum(1, 2, 3, 4, 5) );
```
-There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted.
+There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted, so the result in the code above is `3`.
The rest of the parameters can be included in the function definition by using three dots `...` followed by the name of the array that will contain them. The dots literally mean "gather the remaining parameters into an array".
diff --git a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md
index d02c53b99..4e386eec5 100644
--- a/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md
+++ b/1-js/06-advanced-functions/03-closure/5-function-in-if/task.md
@@ -1,4 +1,6 @@
+importance: 5
+---
# Function in if
Look at the code. What will be the result of the call at the last line?
diff --git a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js
index e3c335e03..802f28c4d 100644
--- a/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js
+++ b/1-js/06-advanced-functions/03-closure/9-sort-by-field/_js.view/test.js
@@ -23,7 +23,7 @@ describe("byField", function(){
{ name: "John", age: 20, surname: "Johnson"},
];
let ageSortedAnswer = users.sort(byField("age"));
- assert.deepEqual(ageSortedKey, ageSortedKey);
+ assert.deepEqual(ageSortedKey, ageSortedAnswer);
});
it("sorts users by surname", function(){
diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md
index 199887063..cb43a7968 100644
--- a/1-js/06-advanced-functions/03-closure/article.md
+++ b/1-js/06-advanced-functions/03-closure/article.md
@@ -7,7 +7,7 @@ We already know that a function can access variables outside of it ("outer" vari
But what happens if outer variables change since a function is created? Will the function get newer values or the old ones?
-And what if a function is passed along as a parameter and called from another place of code, will it get access to outer variables at the new place?
+And what if a function is passed along as an argument and called from another place of code, will it get access to outer variables at the new place?
Let's expand our knowledge to understand these scenarios and more complex ones.
diff --git a/1-js/06-advanced-functions/04-var/article.md b/1-js/06-advanced-functions/04-var/article.md
index d433cbc5d..28d7a76ec 100644
--- a/1-js/06-advanced-functions/04-var/article.md
+++ b/1-js/06-advanced-functions/04-var/article.md
@@ -4,7 +4,7 @@
```smart header="This article is for understanding old scripts"
The information in this article is useful for understanding old scripts.
-That's not how we write a new code.
+That's not how we write new code.
```
In the very first chapter about [variables](info:variables), we mentioned three ways of variable declaration:
@@ -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";
@@ -256,11 +256,11 @@ There exist other ways besides parentheses to tell JavaScript that we mean a Fun
```js run
// Ways to create IIFE
-(function() {
+*!*(*/!*function() {
alert("Parentheses around the function");
}*!*)*/!*();
-(function() {
+*!*(*/!*function() {
alert("Parentheses around the whole thing");
}()*!*)*/!*;
diff --git a/1-js/06-advanced-functions/05-global-object/article.md b/1-js/06-advanced-functions/05-global-object/article.md
index 40131e339..cf4839d94 100644
--- a/1-js/06-advanced-functions/05-global-object/article.md
+++ b/1-js/06-advanced-functions/05-global-object/article.md
@@ -25,7 +25,7 @@ var gVar = 5;
alert(window.gVar); // 5 (became a property of the global object)
```
-The same effect have function declarations (statements with `function` keyword in the main code flow, not function expressions).
+Function declarations have the same effect (statements with `function` keyword in the main code flow, not function expressions).
Please don't rely on that! This behavior exists for compatibility reasons. Modern scripts use [JavaScript modules](info:modules) where such a thing doesn't happen.
diff --git a/1-js/06-advanced-functions/06-function-object/article.md b/1-js/06-advanced-functions/06-function-object/article.md
index 12342f45a..8419ae763 100644
--- a/1-js/06-advanced-functions/06-function-object/article.md
+++ b/1-js/06-advanced-functions/06-function-object/article.md
@@ -326,7 +326,7 @@ welcome(); // Hello, Guest (nested call works)
Now it works, because the name `"func"` is function-local. It is not taken from outside (and not visible there). The specification guarantees that it will always reference the current function.
-The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", how the function can call itself internally.
+The outer code still has its variable `sayHi` or `welcome`. And `func` is an "internal function name", the way for the function to call itself reliably.
```smart header="There's no such thing for Function Declaration"
The "internal name" feature described here is only available for Function Expressions, not for Function Declarations. For Function Declarations, there is no syntax for adding an "internal" name.
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 984102687..f96959988 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
@@ -232,7 +232,7 @@ setTimeout(function() {...}, 100);
For `setInterval` the function stays in memory until `clearInterval` is called.
-There's a side-effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small.
+There's a side effect. A function references the outer lexical environment, so, while it lives, outer variables live too. They may take much more memory than the function itself. So when we don't need the scheduled function anymore, it's better to cancel it, even if it's very small.
````
## Zero delay setTimeout
@@ -256,7 +256,7 @@ The first line "puts the call into calendar after 0ms". But the scheduler will o
There are also advanced browser-related use cases of zero-delay timeout, that we'll discuss in the chapter .
````smart header="Zero delay is in fact not zero (in a browser)"
-In the browser, there's a limitation of how often nested timers can run. The [HTML5 standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.".
+In the browser, there's a limitation of how often nested timers can run. The [HTML Living Standard](https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers) says: "after five nested timers, the interval is forced to be at least 4 milliseconds.".
Let's demonstrate what it means with the example below. The `setTimeout` call in it re-schedules itself with zero delay. Each call remembers the real time from the previous one in the `times` array. What do the real delays look like? Let's see:
@@ -297,6 +297,6 @@ Please note that all scheduling methods do not *guarantee* the exact delay.
For example, the in-browser timer may slow down for a lot of reasons:
- The CPU is overloaded.
- The browser tab is in the background mode.
-- The laptop is on battery.
+- The laptop is on battery saving mode.
All that may increase the minimal timer resolution (the minimal delay) to 300ms or even 1000ms depending on the browser and OS-level performance settings.
diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
index 6df7af132..cbd473196 100644
--- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
+++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md
@@ -8,7 +8,7 @@ Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper.
When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds.
-The difference with debounce is that it's completely different decorator:
+Compared to the debounce decorator, the behavior is completely different:
- `debounce` runs the function once after the "cooldown" period. Good for processing the final result.
- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often.
diff --git a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
index 403107ca6..4a381c0b4 100644
--- a/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
+++ b/1-js/06-advanced-functions/10-bind/5-question-use-bind/solution.md
@@ -1,5 +1,5 @@
-The error occurs because `ask` gets functions `loginOk/loginFail` without the object.
+The error occurs because `askPassword` gets functions `loginOk/loginFail` without the object.
When it calls them, they naturally assume `this=undefined`.
diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md
index 3cee4fe83..7a6e47b90 100644
--- a/1-js/06-advanced-functions/10-bind/article.md
+++ b/1-js/06-advanced-functions/10-bind/article.md
@@ -125,7 +125,7 @@ funcUser(); // John
*/!*
```
-Here `func.bind(user)` as a "bound variant" of `func`, with fixed `this=user`.
+Here `func.bind(user)` is a "bound variant" of `func`, with fixed `this=user`.
All arguments are passed to the original `func` "as is", for instance:
@@ -187,8 +187,8 @@ let user = {
let say = user.say.bind(user);
-say("Hello"); // Hello, John ("Hello" argument is passed to say)
-say("Bye"); // Bye, John ("Bye" is passed to say)
+say("Hello"); // Hello, John! ("Hello" argument is passed to say)
+say("Bye"); // Bye, John! ("Bye" is passed to say)
```
````smart header="Convenience method: `bindAll`"
@@ -202,7 +202,7 @@ for (let key in user) {
}
```
-JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](http://lodash.com/docs#bindAll) in lodash.
+JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) in lodash.
````
## Partial functions
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 f8f8d21d4..0a945b377 100644
--- a/1-js/07-object-properties/01-property-descriptors/article.md
+++ b/1-js/07-object-properties/01-property-descriptors/article.md
@@ -19,7 +19,7 @@ We didn't see them yet, because generally they do not show up. When we create a
First, let's see how to get those flags.
-The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
+The method [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property.
The syntax is:
```js
@@ -54,7 +54,7 @@ alert( JSON.stringify(descriptor, null, 2 ) );
*/
```
-To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty).
+To change the flags, we can use [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty).
The syntax is:
@@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name'
Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours.
```smart header="Errors appear only in strict mode"
-In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
+In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict.
```
Here's the same example, but the property is created from scratch:
@@ -194,7 +194,7 @@ alert(Object.keys(user)); // name
The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties.
-A non-configurable property can not be deleted.
+A non-configurable property can't be deleted, its attributes can't be modified.
For instance, `Math.PI` is non-writable, non-enumerable and non-configurable:
@@ -214,20 +214,23 @@ alert( JSON.stringify(descriptor, null, 2 ) );
So, a programmer is unable to change the value of `Math.PI` or overwrite it.
```js run
-Math.PI = 3; // Error
+Math.PI = 3; // Error, because it has writable: false
// delete Math.PI won't work either
```
-Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`.
+We also can't change `Math.PI` to be `writable` again:
+
+```js run
+// Error, because of configurable: false
+Object.defineProperty(Math, "PI", { writable: true });
+```
-To be precise, non-configurability imposes several restrictions on `defineProperty`:
-1. Can't change `configurable` flag.
-2. Can't change `enumerable` flag.
-3. Can't change `writable: false` to `true` (the other way round works).
-4. Can't change `get/set` for an accessor property (but can assign them if absent).
+There's absolutely nothing we can do with `Math.PI`.
-**The idea of "configurable: false" is to prevent changes of property flags and its deletion, while allowing to change its value.**
+Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`.
+
+**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.**
Here `user.name` is non-configurable, but we can still change it (as it's writable):
@@ -244,7 +247,7 @@ user.name = "Pete"; // works fine
delete user.name; // Error
```
-And here we make `user.name` a "forever sealed" constant:
+And here we make `user.name` a "forever sealed" constant, just like the built-in `Math.PI`:
```js run
let user = {
@@ -263,10 +266,15 @@ delete user.name;
Object.defineProperty(user, "name", { value: "Pete" });
```
+```smart header="The only attribute change possible: writable true -> false"
+There's a minor exception about changing flags.
+
+We can change `writable: true` to `false` for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though.
+```
## Object.defineProperties
-There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once.
+There's a method [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) that allows to define many properties at once.
The syntax is:
@@ -292,7 +300,7 @@ So, we can set many properties at once.
## Object.getOwnPropertyDescriptors
-To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors).
+To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors).
Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object:
@@ -310,7 +318,7 @@ for (let key in user) {
...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred.
-Another difference is that `for..in` ignores symbolic properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic ones.
+Another difference is that `for..in` ignores symbolic and non-enumerable properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic and non-enumerable ones.
## Sealing an object globally
@@ -318,24 +326,24 @@ Property descriptors work at the level of individual properties.
There are also methods that limit access to the *whole* object:
-[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions)
+[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions)
: Forbids the addition of new properties to the object.
-[Object.seal(obj)](mdn:js/Object/seal)
+[Object.seal(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal)
: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties.
-[Object.freeze(obj)](mdn:js/Object/freeze)
+[Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
: Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties.
And also there are tests for them:
-[Object.isExtensible(obj)](mdn:js/Object/isExtensible)
+[Object.isExtensible(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible)
: Returns `false` if adding properties is forbidden, otherwise `true`.
-[Object.isSealed(obj)](mdn:js/Object/isSealed)
+[Object.isSealed(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed)
: Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`.
-[Object.isFrozen(obj)](mdn:js/Object/isFrozen)
+[Object.isFrozen(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen)
: Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`.
These methods are rarely used in practice.
diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md
index 45b9e70ed..c2aa35d53 100644
--- a/1-js/07-object-properties/02-property-accessors/article.md
+++ b/1-js/07-object-properties/02-property-accessors/article.md
@@ -5,7 +5,7 @@ There are two kinds of object properties.
The first kind is *data properties*. We already know how to work with them. All properties that we've been using until now were data properties.
-The second type of properties is something new. It's *accessor properties*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code.
+The second type of property is something new. It's an *accessor property*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code.
## Getters and setters
diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md
index 02236af75..ef6c7ffeb 100644
--- a/1-js/08-prototypes/01-prototype-inheritance/article.md
+++ b/1-js/08-prototypes/01-prototype-inheritance/article.md
@@ -54,7 +54,7 @@ alert( rabbit.eats ); // true (**)
alert( rabbit.jumps ); // true
```
-Here the line `(*)` sets `animal` to be a prototype of `rabbit`.
+Here the line `(*)` sets `animal` to be the prototype of `rabbit`.
Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up):
@@ -131,7 +131,6 @@ There are only two limitations:
Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others.
-
```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`"
It's a common mistake of novice developers not to know the difference between these two.
@@ -287,7 +286,7 @@ for(let prop in rabbit) alert(prop); // jumps, then eats
*/!*
```
-If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
+If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`.
So we can filter out inherited properties (or do something else with them):
diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md
index 6cf7aebb4..bdfc86dd8 100644
--- a/1-js/08-prototypes/03-native-prototypes/article.md
+++ b/1-js/08-prototypes/03-native-prototypes/article.md
@@ -2,7 +2,7 @@
The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it.
-First we'll see at the details, and then how to use it for adding new capabilities to built-in objects.
+First we'll look at the details, and then how to use it for adding new capabilities to built-in objects.
## Object.prototype
diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
index a92e17900..f3c9cf0e5 100644
--- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
+++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md
@@ -28,4 +28,4 @@ alert(dictionary); // "apple,__proto__"
When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable.
-See the the chapter [](info:property-descriptors) for review.
+See the chapter [](info:property-descriptors) for review.
diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md
index a4ce2646c..9c5f1eb3d 100644
--- a/1-js/08-prototypes/04-prototype-methods/article.md
+++ b/1-js/08-prototypes/04-prototype-methods/article.md
@@ -3,15 +3,18 @@
In the first chapter of this section, we mentioned that there are modern methods to setup a prototype.
-The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard).
+Setting or reading the prototype with `obj.__proto__` is considered outdated and somewhat deprecated (moved to the so-called "Annex B" of the JavaScript standard, meant for browsers only).
-The modern methods are:
+The modern methods to get/set a prototype are:
-- [Object.create(proto, [descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
- [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`.
- [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`.
-These should be used instead of `__proto__`.
+The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`.
+
+Although, there's a special method for this too:
+
+- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors.
For instance:
@@ -22,7 +25,7 @@ let animal = {
// create a new object with animal as a prototype
*!*
-let rabbit = Object.create(animal);
+let rabbit = Object.create(animal); // same as {__proto__: animal}
*/!*
alert(rabbit.eats); // true
@@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {}
*/!*
```
-`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this:
+The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors.
+
+We can provide additional properties to the new object there, like this:
```js run
let animal = {
@@ -57,26 +62,34 @@ The descriptors are in the same format as described in the chapter , arrow functions do not have `super`.
If accessed, it's taken from the outer function. For instance:
+
```js
class Rabbit extends Animal {
stop() {
@@ -176,7 +177,6 @@ setTimeout(function() { super.stop() }, 1000);
```
````
-
## Overriding constructor
With constructors it gets a little bit tricky.
@@ -280,8 +280,6 @@ alert(rabbit.earLength); // 10
*/!*
```
-
-
### Overriding class fields: a tricky note
```warn header="Advanced note"
@@ -317,13 +315,13 @@ new Rabbit(); // animal
*/!*
```
-Here, class `Rabbit` extends `Animal` and overrides `name` field with its own value.
+Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value.
There's no own constructor in `Rabbit`, so `Animal` constructor is called.
What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`.
-**In other words, parent constructor always uses its own field value, not the overridden one.**
+**In other words, the parent constructor always uses its own field value, not the overridden one.**
What's odd about it?
@@ -360,9 +358,9 @@ And that's what we naturally expect. When the parent constructor is called in th
...But for class fields it's not so. As said, the parent constructor always uses the parent field.
-Why is there the difference?
+Why is there a difference?
-Well, the reason is in the field initialization order. The class field is initialized:
+Well, the reason is the field initialization order. The class field is initialized:
- Before constructor for the base class (that doesn't extend anything),
- Immediately after `super()` for the derived class.
@@ -370,13 +368,12 @@ In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As
So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used.
-This subtle difference between fields and methods is specific to JavaScript
+This subtle difference between fields and methods is specific to JavaScript.
Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here.
If it becomes a problem, one can fix it by using methods or getters/setters instead of fields.
-
## Super: internals, [[HomeObject]]
```warn header="Advanced information"
diff --git a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md
index ca9e80601..cb9829ce0 100644
--- a/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md
+++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md
@@ -21,14 +21,14 @@ alert( rabbit.hasOwnProperty('name') ); // true
But that's not all yet.
-Even after the fix, there's still important difference in `"class Rabbit extends Object"` versus `class Rabbit`.
+Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`.
As we know, the "extends" syntax sets up two prototypes:
1. Between `"prototype"` of the constructor functions (for methods).
2. Between the constructor functions themselves (for static methods).
-In our case, for `class Rabbit extends Object` it means:
+In the case of `class Rabbit extends Object` it means:
```js run
class Rabbit extends Object {}
@@ -37,7 +37,7 @@ alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true
alert( Rabbit.__proto__ === Object ); // (2) true
```
-So `Rabbit` now provides access to static methods of `Object` via `Rabbit`, like this:
+So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this:
```js run
class Rabbit extends Object {}
@@ -67,7 +67,7 @@ alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
So `Rabbit` doesn't provide access to static methods of `Object` in that case.
-By the way, `Function.prototype` has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`.
+By the way, `Function.prototype` also has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`.
Here's the picture:
diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md
index c75ec257f..4b493a5e8 100644
--- a/1-js/09-classes/03-static-properties-methods/article.md
+++ b/1-js/09-classes/03-static-properties-methods/article.md
@@ -1,9 +1,9 @@
# Static properties and methods
-We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*.
+We can also assign a method to the class as a whole. Such methods are called *static*.
-In a class, they are prepended by `static` keyword, like this:
+In a class declaration, they are prepended by `static` keyword, like this:
```js run
class User {
@@ -31,9 +31,11 @@ User.staticMethod(); // true
The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule).
-Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it.
+Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it.
-For instance, we have `Article` objects and need a function to compare them. A natural solution would be to add `Article.compare` method, like this:
+For instance, we have `Article` objects and need a function to compare them.
+
+A natural solution would be to add `Article.compare` static method:
```js run
class Article {
@@ -63,9 +65,11 @@ articles.sort(Article.compare);
alert( articles[0].title ); // CSS
```
-Here `Article.compare` stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
+Here `Article.compare` method stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class.
+
+Another example would be a so-called "factory" method.
-Another example would be a so-called "factory" method. Imagine, we need few ways to create an article:
+Let's say, we need multiple ways to create an article:
1. Create by given parameters (`title`, `date` etc).
2. Create an empty article with today's date.
@@ -73,7 +77,7 @@ Another example would be a so-called "factory" method. Imagine, we need few ways
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
-Like `Article.createTodays()` here:
+Such as `Article.createTodays()` here:
```js run
class Article {
@@ -101,10 +105,21 @@ Static methods are also used in database-related classes to search/save/remove e
```js
// assuming Article is a special class for managing articles
-// static method to remove the article:
+// static method to remove the article by id:
Article.remove({id: 12345});
```
+````warn header="Static methods aren't available for individual objects"
+Static methods are callable on classes, not on individual objects.
+
+E.g. such code won't work:
+
+```js
+// ...
+article.createTodays(); /// Error: article.createTodays is not a function
+```
+````
+
## Static properties
[recent browser=Chrome]
diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md
index e26d0ec81..91efb89ee 100644
--- a/1-js/09-classes/04-private-protected-properties-methods/article.md
+++ b/1-js/09-classes/04-private-protected-properties-methods/article.md
@@ -116,7 +116,7 @@ class CoffeeMachine {
let coffeeMachine = new CoffeeMachine(100);
// add water
-coffeeMachine.waterAmount = -10; // Error: Negative water
+coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10
```
Now the access is under control, so setting the water amount below zero becomes impossible.
diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md
index 630818188..f9db989ca 100644
--- a/1-js/09-classes/06-instanceof/article.md
+++ b/1-js/09-classes/06-instanceof/article.md
@@ -93,7 +93,7 @@ The algorithm of `obj instanceof Class` works roughly as follows:
alert(rabbit instanceof Animal); // true
*/!*
- // rabbit.__proto__ === Rabbit.prototype
+ // rabbit.__proto__ === Animal.prototype (no match)
*!*
// rabbit.__proto__.__proto__ === Animal.prototype (match!)
*/!*
diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md
index 06001d900..526b832ef 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 a928da289..cad2e1a3e 100644
--- a/1-js/10-error-handling/1-try-catch/article.md
+++ b/1-js/10-error-handling/1-try-catch/article.md
@@ -632,7 +632,7 @@ For instance:
The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers.
-There are also web-services that provide error-logging for such cases, like or .
+There are also web-services that provide error-logging for such cases, like or .
They work like this:
diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md
index ff2e4c529..d28b07439 100644
--- a/1-js/10-error-handling/2-custom-errors/article.md
+++ b/1-js/10-error-handling/2-custom-errors/article.md
@@ -21,9 +21,9 @@ Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it thr
Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field.
-Our `ValidationError` class should inherit from the built-in `Error` class.
+Our `ValidationError` class should inherit from the `Error` class.
-That class is built-in, but here's its approximate code so we can understand what we're extending:
+The `Error` class is built-in, but here's its approximate code so we can understand what we're extending:
```js
// The "pseudocode" for the built-in Error class defined by JavaScript itself
@@ -38,7 +38,7 @@ class Error {
Now let's inherit `ValidationError` from it and try it in action:
-```js run untrusted
+```js run
*!*
class ValidationError extends Error {
*/!*
@@ -117,15 +117,15 @@ We could also look at `err.name`, like this:
// instead of (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...
-```
+```
The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof.
-Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or other unknown ones) should fall through.
+Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (caused by a typo in the code or other unknown reasons) should fall through.
## Further inheritance
-The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age`). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing.
+The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age` instead of a number). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing.
```js run
class ValidationError extends Error {
diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md
index 344d92b74..57115a909 100644
--- a/1-js/11-async/01-callbacks/article.md
+++ b/1-js/11-async/01-callbacks/article.md
@@ -28,7 +28,7 @@ function loadScript(src) {
}
```
-It appends to the document the new, dynamically created, tag `
+