diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index a029ce31c..e88d95c3e 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -6,29 +6,33 @@ *जावास्क्रिप्ट* को पहली बार *वेब पेजेस चलाने* के लिए बनाया गया था +<<<<<<< HEAD इस भाषा में प्रोग्राम को *स्क्रिप्ट्स* कहा जाता है They can be written right in the HTML and executed automatically as the page loads. +======= +The programs in this language are called *scripts*. They can be written right in a web page's HTML and run automatically as the page loads. +>>>>>>> a0266c574c0ab8a0834dd38ed65e7e4ee27f9cdb -Scripts are provided and executed as a plain text. They don't need a special preparation or a compilation to run. +Scripts are provided and executed as plain text. They don't need special preparation or compilation to run. In this aspect, JavaScript is very different from another language called [Java](https://en.wikipedia.org/wiki/Java_(programming_language)). ```smart header="Why JavaScript?" -When JavaScript was created, it initially had another name: "LiveScript". But Java language was very popular at that time, so it was decided that positioning a new language as a "younger brother" of Java would help. +When JavaScript was created, it initially had another name: "LiveScript". But Java was very popular at that time, so it was decided that positioning a new language as a "younger brother" of Java would help. -But as it evolved, JavaScript became a fully independent language, with its own specification called [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript), and now it has no relation to Java at all. +But as it evolved, JavaScript became a fully independent language with its own specification called [ECMAScript](http://en.wikipedia.org/wiki/ECMAScript), and now it has no relation to Java at all. ``` -At present, JavaScript can not only execute in the browser, but also on the server, or actually on any device that has a special program called [the JavaScript engine](https://en.wikipedia.org/wiki/JavaScript_engine). +Today, JavaScript can execute not only in the browser, but also on the server, or actually on any device that has a special program called [the JavaScript engine](https://en.wikipedia.org/wiki/JavaScript_engine). -The browser has an embedded engine, sometimes called a "JavaScript virtual machine". +The browser has an embedded engine sometimes called a "JavaScript virtual machine". -Different engines have different "codenames", for example: +Different engines have different "codenames". For example: - [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- in Chrome and Opera. - [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- in Firefox. - ...There are other codenames like "Trident" and "Chakra" for different versions of IE, "ChakraCore" for Microsoft Edge, "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 and Opera. ```smart header="How do engines work?" @@ -38,14 +42,14 @@ Engines are complicated. But the basics are easy. 2. Then it converts ("compiles") the script to the machine language. 3. And then the machine code runs, pretty fast. -The engine applies optimizations on every stage of the process. It even watches the compiled script as it runs, analyzes the data that flows through it and applies optimizations to the machine code based on that knowledge. At the end, scripts are quite 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 applies optimizations to the machine code based on that knowledge. When it's done, scripts run quite fast. ``` ## What can in-browser JavaScript do? -The 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 CPU, because it was initially created for browsers which do not require it. -The capabilities greatly depend on the environment that runs JavaScript. For instance, [Node.JS](https://wikipedia.org/wiki/Node.js) supports functions that allow JavaScript to read/write arbitrary files, perform network requests, etc. +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. In-browser JavaScript can do everything related to webpage manipulation, interaction with the user, and the webserver. @@ -61,7 +65,7 @@ For instance, in-browser JavaScript is able to: 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. -The examples of such restrictions are: +Examples of such restrictions include: - JavaScript on a webpage may not read/write arbitrary files on the hard disk, copy them or execute programs. It has no direct access to OS system functions. @@ -70,14 +74,14 @@ The examples of such restrictions are: 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). - This is called the "Same Origin Policy". To work around that, *both pages* must contain a special JavaScript code that handles data exchange. + 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. - The limitation is again for 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. -- 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 safety limitations. + 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. +- JavaScript can easily communicate over the net to the server where the current page came from. But its ability to receive data from other sites/domains is crippled. Though possible, it requires explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's a safety limitation. ![](limitations.png) -Such limits do not exist if JavaScript is used outside of the browser, for example on a server. Modern browsers also allow installing plugin/extensions which may get extended permissions. +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. ## What makes JavaScript unique? @@ -86,14 +90,13 @@ There are at least *three* great things about JavaScript: ```compare + Full integration with HTML/CSS. + Simple things are done simply. -+ Supported by all major browsers and enabled by default. ++ Support by all major browsers and enabled by default. ``` +JavaScript is the only browser technology that combines these three things. -Combined, these three things exist only in JavaScript and no other browser technology. +That's what makes JavaScript unique. That's why it's the most widespread tool for creating browser interfaces. -That's what makes JavaScript unique. That's why it's the most widespread tool to create browser interfaces. - -While planning to learn a new technology, it's beneficial to check its perspectives. So let's move on to the modern trends that include new languages and browser abilities. +While planning to learn a new technology, it's beneficial to check its perspectives. So let's move on to the modern trends affecting it, including new languages and browser abilities. ## Languages "over" JavaScript @@ -108,14 +111,14 @@ Modern tools make the transpilation very fast and transparent, actually allowing Examples of such languages: -- [CoffeeScript](http://coffeescript.org/) is a "syntactic sugar" for JavaScript, it introduces shorter syntax, allowing to write more precise and clear 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. +- [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. - [Dart](https://www.dartlang.org/) is a standalone language that has its own engine that runs in non-browser environments (like mobile apps). It was initially offered by Google as a replacement for JavaScript, but as of now, browsers require it to be transpiled to JavaScript just like the ones above. -There are more. Of course, even if we use one of those 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 languages, we should also know JavaScript to really understand what we're doing. ## Summary -- JavaScript was initially created as a browser-only language, but now it is used in many other environments as well. -- At this moment, JavaScript has a unique position as the most widely-adopted browser language with full integration with HTML/CSS. +- JavaScript was initially created as a browser-only language, but 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 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/1-intro/limitations.png b/1-js/01-getting-started/1-intro/limitations.png index 4063bbcf2..3ef6b1e08 100644 Binary files a/1-js/01-getting-started/1-intro/limitations.png and b/1-js/01-getting-started/1-intro/limitations.png differ diff --git a/1-js/01-getting-started/1-intro/limitations@2x.png b/1-js/01-getting-started/1-intro/limitations@2x.png index 55c030ef3..7aa1c41ae 100644 Binary files a/1-js/01-getting-started/1-intro/limitations@2x.png and b/1-js/01-getting-started/1-intro/limitations@2x.png differ diff --git a/1-js/01-getting-started/2-code-editors/article.md b/1-js/01-getting-started/2-code-editors/article.md index c84d32025..d36561bc6 100644 --- a/1-js/01-getting-started/2-code-editors/article.md +++ b/1-js/01-getting-started/2-code-editors/article.md @@ -2,23 +2,22 @@ A code editor is the place where programmers spend most of their time. -There are two archetypes: IDE and lightweight editors. Many people feel comfortable choosing one tool of each type. +There are two main types of code editors: IDEs and lightweight editors. Many people use one tool of each type. ## IDE -The term [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) means a powerful editor with many features that usually operates on a "whole project." As the name suggests, that's not just an editor, but a full-scale "development environment." +The term [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) refers to a powerful editor with many features that usually operates on a "whole project." As the name suggests, it's not just an editor, but a full-scale "development environment." -An IDE loads the project (can be many files), allows navigation between files, provides autocompletion based on the whole project (not just the open file), integrates with a version management system (like [git](https://git-scm.com/)), a testing environment and other "project-level" stuff. +An IDE loads the project (which can be many files), allows navigation between files, provides autocompletion based on the whole project (not just the open file), and integrates with a version management system (like [git](https://git-scm.com/)), a testing environment, and other "project-level" stuff. -If you haven't considered selecting an IDE yet, look at the following variants: +If you haven't selected an IDE yet, consider the following options: -- [WebStorm](http://www.jetbrains.com/webstorm/) for frontend development and other editors of the same company if you need additional languages (paid). -- [Visual Studio Code](https://code.visualstudio.com/) (free). -- [Netbeans](http://netbeans.org/) (paid). +- [WebStorm](http://www.jetbrains.com/webstorm/) for frontend development. The same company offers other editors for other languages (paid). +- [Netbeans](http://netbeans.org/) (free). -All of the IDEs are cross-platform. +All of these IDEs are cross-platform. -For Windows, there's also a "Visual Studio" editor, don't confuse it with "Visual Studio Code." "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. A free version of it is called ([Visual Studio Community](https://www.visualstudio.com/vs/community/). +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. A free version of it is called [Visual Studio Community](https://www.visualstudio.com/vs/community/). Many IDEs are paid but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose the best one for you. @@ -34,7 +33,7 @@ In practice, lightweight editors may have a lot of plugins including directory-l The following options deserve your attention: -- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). +- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free) also has many IDE-like features. - [Atom](https://atom.io/) (cross-platform, free). - [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). - [Notepad++](https://notepad-plus-plus.org/) (Windows, free). @@ -46,7 +45,7 @@ The personal preference of the author is to have both an IDE for projects and a I'm using: -- [WebStorm](http://www.jetbrains.com/webstorm/) for JS, and if there is one more language in the project, then I switch to one of the other JetBrains offerings listed above. +- As an IDE for JS -- [WebStorm](http://www.jetbrains.com/webstorm/) (I switch to one of the other JetBrains offerings when using other languages) - As a lightweight editor -- [Sublime Text](http://www.sublimetext.com) or [Atom](https://atom.io/). ## Let's not argue @@ -55,4 +54,4 @@ 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, personal preferences. +The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. diff --git a/1-js/01-getting-started/3-devtools/article.md b/1-js/01-getting-started/3-devtools/article.md index 7f5b357fc..ae5b3845d 100644 --- a/1-js/01-getting-started/3-devtools/article.md +++ b/1-js/01-getting-started/3-devtools/article.md @@ -1,14 +1,14 @@ # Developer console -Code is prone to errors. You are quite likely to make errors... Oh, what am I talking about? You are *absolutely* going to make errors, at least if you're a human, not a [robot](https://en.wikipedia.org/wiki/Bender_(Futurama)). +Code is prone to errors. You will quite likely make errors... Oh, what am I talking about? You are *absolutely* going to make errors, at least if you're a human, not a [robot](https://en.wikipedia.org/wiki/Bender_(Futurama)). -But in the browser, a user doesn't see the errors by default. So, if something goes wrong in the script, we won't see what's broken and can't fix it. +But in the browser, users don't see errors by default. So, if something goes wrong in the script, we won't see what's broken and can't fix it. To see errors and get a lot of other useful information about scripts, "developer tools" have been embedded in browsers. -Most often developers lean towards Chrome or Firefox for development because those browsers have the best developer tools. Other browsers also provide developer tools, sometimes with special features, but are usually playing "catch-up" to Chrome or Firefox. So most people have a "favorite" browser and switch to others if a problem is browser-specific. +Most developers lean towards Chrome or Firefox for development because those browsers have the best developer tools. Other browsers also provide developer tools, sometimes with special features, but are usually playing "catch-up" to Chrome or Firefox. So most developers have a "favorite" browser and switch to others if a problem is browser-specific. -Developer tools are potent; there are many features. To start, we'll learn how to open them, look at errors and run JavaScript commands. +Developer tools are potent; they have many features. To start, we'll learn how to open them, look at errors, and run JavaScript commands. ## Google Chrome @@ -31,28 +31,34 @@ The exact look of developer tools depends on your version of Chrome. It changes Below the error message, there is a blue `>` symbol. It marks a "command line" where we can type JavaScript commands. Press `key:Enter` to run them (`key:Shift+Enter` to input multi-line commands). -Now we can see errors, and that's enough for a start. We'll be back to developer tools later and cover debugging more in-depth in the chapter . +Now we can see errors, and that's enough for a start. We'll come back to developer tools later and cover debugging more in-depth in the chapter . ## Firefox, Edge, and others Most other browsers use `key:F12` to open developer tools. -The look & feel of them is quite similar. Once you know how to use one of those tools (you can start with Chrome), you can easily switch to another. +The look & feel of them is quite similar. Once you know how to use one of these tools (you can start with Chrome), you can easily switch to another. ## Safari 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 "Advanced" pane. There's a checkbox at the bottom: +Open Preferences and go to the "Advanced" pane. There's a checkbox at the bottom: ![safari](safari.png) Now `key:Cmd+Opt+C` can toggle the console. Also, note that the new top menu item named "Develop" has appeared. It has many commands and options. +## Multi-line input + +Usually, when we put a line of code into the console, and then press `key:Enter`, it executes. + +To insert multiple lines, press `key:Shift+Enter`. + ## Summary -- Developer tools allow us to see errors, run commands, examine variables and much more. -- They can be opened with `key:F12` for most browsers under Windows. Chrome for Mac needs `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (need to enable first). +- Developer tools allow us to see errors, run commands, examine variables, and much more. +- They can be opened with `key:F12` for most browsers on Windows. Chrome for Mac needs `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (need to enable first). Now we have the environment ready. In the next section, we'll get down to JavaScript. 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 af486b3f1..4c384da19 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -1,15 +1,15 @@ # Hello, world! -The tutorial that you're reading is about core JavaScript, which is platform-independent. Further on, you will learn Node.JS and other platforms that use it. +This part of the tutorial is about core JavaScript, the language itself. Later on, you'll learn about Node.js and other platforms that use it. -But, we need a working environment to run our scripts, and, just because this book is online, the browser is a good choice. We'll keep the amount of browser-specific commands (like `alert`) to a minimum so that you don't spend time on them if you plan to concentrate on another environment like Node.JS. On the other hand, browser details are explained in detail in the [next part](/ui) of the tutorial. +But we need a working environment to run our scripts and, since this book is online, the browser is a good choice. We'll keep the amount of browser-specific commands (like `alert`) to a minimum so that you don't spend time on them if you plan to concentrate on another environment (like Node.js). We'll focus on JavaScript in the browser in the [next part](/ui) of the tutorial. -So first, let's see how to attach a script to a webpage. For server-side environments, you can just execute it with a command like `"node my.js"` for Node.JS. +So first, let's see how we attach a script to a webpage. For server-side environments (like Node.js), you can execute the script with a command like `"node my.js"`. ## The "script" tag -JavaScript programs can be inserted in any part of an HTML document with the help of the ` ``` - This trick isn't used in modern JavaScript. These comments were used to hide the JavaScript code from old browsers that didn't know about a ` ``` -Here `/path/to/script.js` is an absolute path to the file with the script (from the site root). +Here, `/path/to/script.js` is an absolute path to the script file (from the site root). -It is also possible to provide a path relative to the current page. For instance, `src="script.js"` would mean a file `"script.js"` in the current folder. +You can also provide a relative path from the current page. For instance, `src="script.js"` would mean a file `"script.js"` in the current folder. We can give a full URL as well. For instance: @@ -95,15 +94,15 @@ To attach several scripts, use multiple tags: ```smart As a rule, only the simplest scripts are put into HTML. More complex ones reside in separate files. -The benefit of a separate file is that the browser will download it and then store it in its [cache](https://en.wikipedia.org/wiki/Web_cache). +The benefit of a separate file is that the browser will download it and store it in its [cache](https://en.wikipedia.org/wiki/Web_cache). -After this, other pages that want the same script will take it from the cache instead of downloading it. So the file is actually downloaded only once. +Other pages that reference the same script will take it from the cache instead of downloading it, so the file is actually downloaded only once. -That saves traffic and makes pages faster. +That reduces traffic and makes pages faster. ``` ````warn header="If `src` is set, the script content is ignored." -A single ` ``` -We must choose: either it's an external ``. -There is much more to learn about browser scripts and their interaction with the web-page. But let's keep in mind that this part of the tutorial is devoted to the JavaScript language, so we shouldn't distract ourselves from it. We'll be using a browser as a way to run JavaScript, which is very convenient for online reading, but yet one of many. +There is much more to learn about browser scripts and their interaction with the webpage. But let's keep in mind that this part of the tutorial is devoted to the JavaScript language, so we shouldn't distract ourselves with browser-specific implementations of it. We'll be using the browser as a way to run JavaScript, which is very convenient for online reading, but only one of many. diff --git a/1-js/02-first-steps/01-hello-world/hello-world-render.png b/1-js/02-first-steps/01-hello-world/hello-world-render.png index ffe810697..ba94e017d 100644 Binary files a/1-js/02-first-steps/01-hello-world/hello-world-render.png and b/1-js/02-first-steps/01-hello-world/hello-world-render.png differ diff --git a/1-js/02-first-steps/01-hello-world/hello-world-render@2x.png b/1-js/02-first-steps/01-hello-world/hello-world-render@2x.png index c4411027c..9b2d4479f 100644 Binary files a/1-js/02-first-steps/01-hello-world/hello-world-render@2x.png and b/1-js/02-first-steps/01-hello-world/hello-world-render@2x.png differ diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index 66bf227fe..b18aab19e 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -1,22 +1,22 @@ # Code structure -The first thing to study is the building blocks of the code. +The first thing we'll study is the building blocks of code. ## Statements Statements are syntax constructs and commands that perform actions. -We've already seen a statement `alert('Hello, world!')`, which shows the message "Hello world!". +We've already seen a statement, `alert('Hello, world!')`, which shows the message "Hello, world!". -We can have as many statements in the code as we want. Another statement can be separated with a semicolon. +We can have as many statements in our code as we want. Statements can be separated with a semicolon. -For example, here we split the message into two: +For example, here we split "Hello World" into two alerts: ```js run no-beautify alert('Hello'); alert('World'); ``` -Usually each statement is written on a separate line -- thus the code becomes more readable: +Usually, statements are written on separate lines to make the code more readable: ```js run no-beautify alert('Hello'); @@ -34,11 +34,11 @@ alert('Hello') alert('World') ``` -Here JavaScript interprets the line break as an "implicit" semicolon. That's also called an [automatic semicolon insertion](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion). +Here, JavaScript interprets the line break as an "implicit" semicolon. This is called an [automatic semicolon insertion](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion). -**In most cases a newline implies a semicolon. But "in most cases" does not mean "always"!** +**In most cases, a newline implies a semicolon. But "in most cases" does not mean "always"!** -There are cases when a newline does not mean a semicolon, for example: +There are cases when a newline does not mean a semicolon. For example: ```js run no-beautify alert(3 + @@ -59,9 +59,9 @@ If you're curious to see a concrete example of such an error, check this code ou [1, 2].forEach(alert) ``` -No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later, for now it does not matter. Let's just remember the result: it shows `1`, then `2`. +No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of the code: it shows `1` then `2`. -Now let's add an `alert` before the code and *not* finish it with a semicolon: +Now, let's add an `alert` before the code and *not* finish it with a semicolon: ```js run no-beautify alert("There will be an error") @@ -69,7 +69,7 @@ alert("There will be an error") [1, 2].forEach(alert) ``` -Now if we run it, only the first `alert` is shown, and then we have an error! +Now if we run the code, only the first `alert` is shown and then we have an error! But everything is fine again if we add a semicolon after `alert`: ```js run @@ -78,27 +78,27 @@ alert("All fine now"); [1, 2].forEach(alert) ``` -Now we have the "All fine now" message and then `1` and `2`. +Now we have the "All fine now" message followed by `1` and `2`. -The error in the no-semicolon variant occurs because JavaScript does not imply a semicolon before square brackets `[...]`. +The error in the no-semicolon variant occurs because JavaScript does not assume a semicolon before square brackets `[...]`. -So, because the semicolon is not auto-inserted, the code in the first example is treated as a single statement. That's how the engine sees it: +So, because the semicolon is not auto-inserted, the code in the first example is treated as a single statement. Here's how the engine sees it: ```js run no-beautify alert("There will be an error")[1, 2].forEach(alert) ``` -But it should be two separate statements, not a single one. Such a merging in this case is just wrong, hence the error. There are other situations when such a thing happens. +But it should be two separate statements, not one. Such a merging in this case is just wrong, hence the error. This can happen in other situations. ```` -It's recommended to put semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again -- *it is possible* to leave out semicolons most of the time. But it's safer -- especially for a beginner -- to use them. +We recommend putting semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again -- *it is possible* to leave out semicolons most of the time. But it's safer -- especially for a beginner -- to use them. ## Comments -As time goes on, the program becomes more and more complex. It becomes necessary to add *comments* which describe what happens and why. +As time goes on, programs become more and more complex. It becomes necessary to add *comments* which describe what the code does and why. -Comments can be put into any place of the script. They don't affect the execution because the engine simply ignores them. +Comments can be put into any place of a script. They don't affect its execution because the engine simply ignores them. **One-line comments start with two forward slash characters `//`.** @@ -124,9 +124,9 @@ alert('Hello'); alert('World'); ``` -The content of comments is ignored, so if we put code inside /* ... */ it won't execute. +The content of comments is ignored, so if we put code inside /* ... */, it won't execute. -Sometimes it comes in handy to temporarily disable a part of code: +Sometimes it can be handy to temporarily disable a part of code: ```js run /* Commenting out the code @@ -136,7 +136,7 @@ alert('World'); ``` ```smart header="Use hotkeys!" -In most editors a line of code can be commented out by `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac try `key:Cmd` instead of `key:Ctrl`. +In most editors, a line of code can be commented out by pressing the `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac, try `key:Cmd` instead of `key:Ctrl`. ``` ````warn header="Nested comments are not supported!" @@ -154,6 +154,6 @@ alert( 'World' ); Please, don't hesitate to comment your code. -Comments increase the overall code footprint, but that's not a problem at all. There are many tools which minify the code before publishing to the production server. They remove comments, so they don't appear in the working scripts. Therefore comments do not have any negative effects on production at all. +Comments increase the overall code footprint, but that's not a problem at all. There are many tools which minify code before publishing to a production server. They remove comments, so they don't appear in the working scripts. Therefore, comments do not have negative effects on production at all. -Further in the tutorial there will be a chapter that also explains how to write better comments. +Later in the tutorial there will be a chapter that also explains how to write better comments. diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index 6864078b3..0aab06893 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -1,16 +1,16 @@ # The modern mode, "use strict" -For a long time JavaScript was evolving without compatibility issues. New features were added to the language, but the old functionality did not change. +For a long time, JavaScript evolved without compatibility issues. New features were added to the language while old functionality didn't change. -That had the benefit of never breaking existing code. But the downside was that any mistake or an imperfect decision made by JavaScript creators got stuck in the language forever. +That had the benefit of never breaking existing code. But the downside was that any mistake or an imperfect decision made by JavaScript's creators got stuck in the language forever. -It had been so until 2009 when ECMAScript 5 (ES5) appeared. It added new features to the language and modified some of the existing ones. To keep the old code working, most modifications are off by default. One needs to enable them explicitly with a special directive `"use strict"`. +This was the case until 2009 when ECMAScript 5 (ES5) appeared. It added new features to the language and modified some of the existing ones. To keep the old code working, most modifications are off by default. You need to explicitly enable them with a special directive: `"use strict"`. ## "use strict" -The directive looks like a string: `"use strict"` or `'use strict'`. When it is located on the top of the script, then the whole script works the "modern" way. +The directive looks like a string: `"use strict"` or `'use strict'`. When it is located at the top of a script, the whole script works the "modern" way. -For example +For example: ```js "use strict"; @@ -21,17 +21,17 @@ For example We will learn functions (a way to group commands) soon. -Looking ahead let's just note that `"use strict"` can be put at the start of a function (most kinds of functions) instead of the whole script. Then strict mode is enabled in that function only. But usually people use it for the whole script. +Looking ahead, let's just note that `"use strict"` can be put at the start of most kinds of functions instead of the whole script. Doing that enables strict mode in that function only. But usually, people use it for the whole script. ````warn header="Ensure that \"use strict\" is at the top" -Please make sure that `"use strict"` is on the top of the script, otherwise the strict mode may not be enabled. +Please make sure that `"use strict"` is at the top of your scripts, otherwise strict mode may not be enabled. -There is no strict mode here: +Strict mode isn't enabled here: ```js no-strict alert("some code"); -// "use strict" below is ignored, must be on the top +// "use strict" below is ignored--it must be at the top "use strict"; @@ -42,20 +42,38 @@ Only comments may appear above `"use strict"`. ```` ```warn header="There's no way to cancel `use strict`" -There is no directive `"no use strict"` or alike, that would return the old behavior. +There is no directive like `"no use strict"` that reverts the engine to old behavior. -Once we enter the strict mode, there's no return. +Once we enter strict mode, there's no return. +``` + +## Browser console + +For the future, when you use a browser console to test features, please note that it doesn't `use strict` by default. + +Sometimes, when `use strict` makes a difference, you'll get incorrect results. + +Even if we press `key:Shift+Enter` to input multiple lines, and put `use strict` on top, it doesn't work. That's because of how the console executes the code internally. + +The reliable way to ensure `use strict` would be to input the code into console like this: + +```js +(function() { + 'use strict'; + + // ...your code... +})() ``` ## Always "use strict" -The differences of `"use strict"` versus the "default" mode are still to be covered. +We have yet to cover the differences between strict mode and the "default" mode. -In the next chapters, as we learn language features, we'll make notes about the differences of the strict and default mode. Luckily, there are not so many. And they actually make our life better. +In the next chapters, as we learn language features, we'll note the differences between the strict and default modes. Luckily, there aren't many and they actually make our lives better. -At this point in time it's enough to know about it in general: +For now, it's enough to know about it in general: -1. The `"use strict"` directive switches the engine to the "modern" mode, changing the behavior of some built-in features. We'll see the details as we study. -2. The strict mode is enabled by `"use strict"` at the top. Also there are several language features like "classes" and "modules" that enable strict mode automatically. -3. The strict mode is supported by all modern browsers. -4. It's always recommended to start scripts with `"use strict"`. All examples in this tutorial assume so, unless (very rarely) specified otherwise. +1. The `"use strict"` directive switches the engine to the "modern" mode, changing the behavior of some built-in features. We'll see the details later in the tutorial. +2. Strict mode is enabled by placing `"use strict"` at the top of a script or function. Several language features, like "classes" and "modules", enable strict mode automatically. +3. Strict mode is supported by all modern browsers. +4. We recommended always starting scripts with `"use strict"`. All examples in this tutorial assume strict mode unless (very rarely) specified otherwise. diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/task.md b/1-js/02-first-steps/04-variables/2-declare-variables/task.md index 21631e260..f364badf4 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/task.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/task.md @@ -4,5 +4,5 @@ importance: 3 # Giving the right name -1. Create the variable with the name of our planet. How would you name such a variable? -2. Create the variable to store the name of the current visitor. How would you name that variable? +1. Create a variable with the name of our planet. How would you name such a variable? +2. Create a variable to store the name of a current visitor to a website. How would you name that variable? diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 50ec5b3e0..e69befeda 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -1,16 +1,16 @@ # Variables -Most of the time, a JavaScript application needs to work with information. Here are 2 examples: -1. An online-shop -- the information might include goods being sold and a shopping cart. +Most of the time, a JavaScript application needs to work with information. Here are two examples: +1. An online shop -- the information might include goods being sold and a shopping cart. 2. A chat application -- the information might include users, messages, and much more. Variables are used to store this information. ## A variable -A [variable](https://en.wikipedia.org/wiki/Variable_(computer_science)) is a "named storage" for data. We can use variables to store goodies, visitors and other data. +A [variable](https://en.wikipedia.org/wiki/Variable_(computer_science)) is a "named storage" for data. We can use variables to store goodies, visitors, and other data. -To create a variable in JavaScript, we need to use the `let` keyword. +To create a variable in JavaScript, use the `let` keyword. The statement below creates (in other words: *declares* or *defines*) a variable with the name "message": @@ -18,7 +18,7 @@ The statement below creates (in other words: *declares* or *defines*) a variable let message; ``` -Now we can put some data into it by using the assignment operator `=`: +Now, we can put some data into it by using the assignment operator `=`: ```js let message; @@ -39,7 +39,7 @@ alert(message); // shows the variable content */!* ``` -To be concise we can merge the variable declaration and assignment into a single line: +To be concise, we can combine the variable declaration and assignment into a single line: ```js run let message = 'Hello!'; // define the variable and assign the value @@ -53,7 +53,7 @@ We can also declare multiple variables in one line: let user = 'John', age = 25, message = 'Hello'; ``` -That might seem shorter, but it's not recommended. For the sake of better readability, please use a single line per variable. +That might seem shorter, but we don't recommend it. For the sake of better readability, please use a single line per variable. The multiline variant is a bit longer, but easier to read: @@ -63,7 +63,7 @@ let age = 25; let message = 'Hello'; ``` -Some people also write many variables like that: +Some people also define multiple variables in this multiline style: ```js no-beautify let user = 'John', age = 25, @@ -78,19 +78,19 @@ let user = 'John' , message = 'Hello'; ``` -Technically, all these variants do the same. So, it's a matter of personal taste and aesthetics. +Technically, all these variants do the same thing. So, it's a matter of personal taste and aesthetics. ````smart header="`var` instead of `let`" -In older scripts you may also find another keyword: `var` instead of `let`: +In older scripts, you may also find another keyword: `var` instead of `let`: ```js *!*var*/!* message = 'Hello'; ``` -The `var` keyword is *almost* the same as `let`. It also declares a variable, but in a slightly different, "old-school" fashion. +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 later, in the chapter . +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 . ```` ## A real-life analogy @@ -101,10 +101,9 @@ For instance, the variable `message` can be imagined as a box labeled `"message" ![](variable.png) -We can put any value into the box. - -Also we can change it. The value can be changed as many times as needed: +We can put any value in the box. +We can also change it as many times as we want: ```js run let message; @@ -137,28 +136,28 @@ alert(message); // Hello world! ``` ```smart header="Functional languages" -It may be interesting to know that there also exist [functional](https://en.wikipedia.org/wiki/Functional_programming) programming languages that forbid changing a variable value. For example, [Scala](http://www.scala-lang.org/) or [Erlang](http://www.erlang.org/). +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. 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 bit 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 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. Studying such a language (even if you're not planning to use it soon) is recommended to broaden the mind. ``` ## Variable naming [#variable-naming] -There are two limitations for a variable name in JavaScript: +There are two limitations on variable names in JavaScript: -1. The name must contain only letters, digits, symbols `$` and `_`. +1. The name must contain only letters, digits, or the symbols `$` and `_`. 2. The first character must not be a digit. -Valid names, for instance: +Examples of valid names: ```js let userName; let test123; ``` -When the name contains multiple words, [camelCase](https://en.wikipedia.org/wiki/CamelCase) is commonly used. That is: words go one after another, each word starts with a capital letter: `myVeryLongName`. +When the name contains multiple words, [camelCase](https://en.wikipedia.org/wiki/CamelCase) is commonly used. That is: words go one after another, each word except first starting with a capital letter: `myVeryLongName`. What's interesting -- the dollar sign `'$'` and the underscore `'_'` can also be used in names. They are regular symbols, just like letters, without any special meaning. @@ -176,14 +175,14 @@ Examples of incorrect variable names: ```js no-beautify let 1a; // cannot start with a digit -let my-name; // a hyphen '-' is not allowed in the name +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-English letters are allowed, but not recommended" +````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: ```js @@ -195,9 +194,9 @@ Technically, there is no error here, such names are allowed, but there is an int ```` ````warn header="Reserved names" -There is a [list of reserved words](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords), which cannot be used as variable names, because they are used by the language itself. +There is a [list of reserved words](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords), which cannot be used as variable names because they are used by the language itself. -For example, words `let`, `class`, `return`, `function` are reserved. +For example: `let`, `class`, `return`, and `function` are reserved. The code below gives a syntax error: @@ -209,19 +208,19 @@ let return = 5; // also can't name it "return", error! ````warn header="An assignment without `use strict`" -Normally, we need to define a variable before using it. But in the old times, it was technically possible to create a variable by a mere assignment of the value, without `let`. This still works now if we don't put `use strict`. The behavior is kept for compatibility with old scripts. +Normally, we need to define a variable before using it. But in the old times, it was technically possible to create a variable by a mere assignment of the value without using `let`. This still works now if we don't put `use strict` in our scripts to maintain compatibility with old scripts. ```js run no-strict // note: no "use strict" in this example -num = 5; // the variable "num" is created if didn't exist +num = 5; // the variable "num" is created if it didn't exist alert(num); // 5 ``` -That's a bad practice, it gives an error in the strict mode: +This is a bad practice and would cause an error in strict mode: -```js run refresh untrusted +```js "use strict"; *!* @@ -232,13 +231,13 @@ num = 5; // error: num is not defined ## Constants -To declare a constant (unchanging) variable, one can use `const` instead of `let`: +To declare a constant (unchanging) variable, use `const` instead of `let`: ```js const myBirthday = '18.04.1982'; ``` -Variables declared using `const` are called "constants". They cannot be changed. An attempt to do it would cause an error: +Variables declared using `const` are called "constants". They cannot be changed. An attempt to do so would cause an error: ```js run const myBirthday = '18.04.1982'; @@ -246,7 +245,7 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // error, can't reassign the constant! ``` -When a programmer is sure that the variable should never change, they can use `const` to guarantee it, and also to clearly show that fact to everyone. +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. ### Uppercase constants @@ -255,7 +254,7 @@ There is a widespread practice to use constants as aliases for difficult-to-reme Such constants are named using capital letters and underscores. -Like this: +For instance, let's make constants for colors in so-called "web" (hexadecimal) format: ```js run const COLOR_RED = "#F00"; @@ -271,19 +270,19 @@ alert(color); // #FF7F00 Benefits: - `COLOR_ORANGE` is much easier to remember than `"#FF7F00"`. -- It is much easier to mistype in `"#FF7F00"` than in `COLOR_ORANGE`. +- It is much easier to mistype `"#FF7F00"` than `COLOR_ORANGE`. - When reading the code, `COLOR_ORANGE` is much more meaningful than `#FF7F00`. -When should we use capitals for a constant, and when should we name them normally? Let's make that clear. +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 the value never changes. But there are constants that are known prior to execution (like a hexadecimal value for red), and there are those that are *calculated* in run-time, during the execution, but do not change after the assignment. +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. 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 prior to the page load, so it's named normally. But it's still a constant because it doesn't change after assignment. In other words, capital-named constants are only used as aliases for "hard-coded" values. @@ -291,41 +290,41 @@ In other words, capital-named constants are only used as aliases for "hard-coded Talking about variables, there's one more extremely important thing. -Please name the variables sensibly. Take time to think if needed. +A variable name should have a clean, obvious meaning, describe 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 is written by a beginner and which by an experienced developer. +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. -In a real project, most of the time is spent on modifying and extending the existing code base, rather than writing something completely separate from scratch. And when we return to the code after some time of doing something else, 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-labeled. Or, in other words, when the variables have good names. -Please spend some time thinking about the right name for a variable before declaring it. This will repay you a lot. +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. -- Make the name maximally descriptive and concise. Examples of bad names are `data` and `value`. Such a name says nothing. It is only ok to use them if it's exceptionally obvious from the context which data or value is meant. -- 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 like `currentUser` or `newUser`, but not `currentVisitor` or a `newManInTown`. +- 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`. -Sounds simple? Indeed it is, but creating good descriptive-and-concise names in practice is not. Go for it. +Sounds simple? Indeed it is, but creating descriptive and concise variable names in practice is not. Go for it. ```smart header="Reuse or create?" -And the last note. There are some lazy programmers who, instead of declaring a new variable, tend to reuse the existing ones. +And the last note. There are some lazy programmers who, instead of declaring new variables, tend to reuse existing ones. -As a result, the variable is like a box where people throw different things without changing the sticker. What is inside it now? Who knows... We need to come closer and check. +As a result, their variables are like boxes into which people throw different things without changing their stickers. What's inside the box now? Who knows? We need to come closer and check. -Such a programmer saves a little bit on variable declaration, but loses ten times more on debugging the code. +Such programmers save a little bit on variable declaration but lose ten times more on debugging. An extra variable is good, not evil. -Modern JavaScript minifiers and browsers optimize code well enough, so it won't create performance issues. Using different variables for different values can even help the engine to optimize. +Modern JavaScript minifiers and browsers optimize code well enough, so it won't create performance issues. Using different variables for different values can even help the engine optimize your code. ``` ## Summary -We can declare variables to store data. That can be done using `var` or `let` or `const`. +We can declare variables to store data by using the `var`, `let`, or `const` keywords. - `let` -- is a modern variable declaration. The code must be in strict mode to use `let` in Chrome (V8). - `var` -- is an old-school variable declaration. Normally we don't use it at all, but we'll cover subtle differences from `let` in the chapter , just in case you need them. - `const` -- is like `let`, but the value of the variable can't be changed. -Variables should be named in a way that allows us to easily understand what's inside. +Variables should be named in a way that allows us to easily understand what's inside them. diff --git a/1-js/02-first-steps/04-variables/variable-change.png b/1-js/02-first-steps/04-variables/variable-change.png index 6dd3803d9..9135e930a 100644 Binary files a/1-js/02-first-steps/04-variables/variable-change.png and b/1-js/02-first-steps/04-variables/variable-change.png differ diff --git a/1-js/02-first-steps/04-variables/variable-change@2x.png b/1-js/02-first-steps/04-variables/variable-change@2x.png index f57b04ab1..c9569e638 100644 Binary files a/1-js/02-first-steps/04-variables/variable-change@2x.png and b/1-js/02-first-steps/04-variables/variable-change@2x.png differ diff --git a/1-js/02-first-steps/04-variables/variable.png b/1-js/02-first-steps/04-variables/variable.png index ab532d91d..6d2482556 100644 Binary files a/1-js/02-first-steps/04-variables/variable.png and b/1-js/02-first-steps/04-variables/variable.png differ diff --git a/1-js/02-first-steps/04-variables/variable@2x.png b/1-js/02-first-steps/04-variables/variable@2x.png index c9c37f034..845f34408 100644 Binary files a/1-js/02-first-steps/04-variables/variable@2x.png and b/1-js/02-first-steps/04-variables/variable@2x.png differ diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 9ed9a2d3b..0c9954ecf 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -1,6 +1,6 @@ # Data types -A variable in JavaScript can contain any data. A variable can at one moment be a string and later receive a numeric value: +A variable in JavaScript can contain any data. A variable can at one moment be a string and at another be a number: ```js // no error @@ -10,7 +10,7 @@ message = 123456; Programming languages that allow such things are called "dynamically typed", meaning that there are data types, but variables are not bound to any of them. -There are seven basic data types in JavaScript. Here we'll study the basics, and in the next chapters we'll talk about each of them in detail. +There are seven basic data types in JavaScript. Here, we'll cover them in general and in the next chapters we'll talk about each of them in detail. ## A number @@ -19,11 +19,11 @@ let n = 123; n = 12.345; ``` -The *number* type serves both for integer and floating point numbers. +The *number* type represents both integer and floating point numbers. -There are many operations for numbers, e.g. multiplication `*`, division `/`, addition `+`, subtraction `-` and so on. +There are many operations for numbers, e.g. multiplication `*`, division `/`, addition `+`, subtraction `-`, and so on. -Besides regular numbers, there are so-called "special numeric values" which also belong to that type: `Infinity`, `-Infinity` and `NaN`. +Besides regular numbers, there are so-called "special numeric values" which also belong to this data type: `Infinity`, `-Infinity` and `NaN`. - `Infinity` represents the mathematical [Infinity](https://en.wikipedia.org/wiki/Infinity) ∞. It is a special value that's greater than any number. @@ -33,7 +33,7 @@ Besides regular numbers, there are so-called "special numeric values" which also alert( 1 / 0 ); // Infinity ``` - Or just mention it in the code directly: + Or just reference it directly: ```js run alert( Infinity ); // Infinity @@ -44,27 +44,27 @@ 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` would give `NaN`: + `NaN` is sticky. Any further operation on `NaN` returns `NaN`: ```js run alert( "not a number" / 2 + 5 ); // NaN ``` - So, if there's `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. ```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. +Doing maths is "safe" in JavaScript. We can do anything: divide by zero, treat non-numeric strings as numbers, etc. -The script will never stop with a fatal error ("die"). At worst we'll get `NaN` as the result. +The script will never stop with a fatal error ("die"). At worst, we'll get `NaN` as the result. ``` -Special numeric values formally belong to the "number" type. Of course they are not numbers in a common sense of this word. +Special numeric values formally belong to the "number" type. Of course they are not numbers in the common sense of this word. We'll see more about working with numbers in the chapter . ## A string -A string in JavaScript must be quoted. +A string in JavaScript must be surrounded by quotes. ```js let str = "Hello"; @@ -92,9 +92,9 @@ alert( `Hello, *!*${name}*/!*!` ); // Hello, John! alert( `the result is *!*${1 + 2}*/!*` ); // the result is 3 ``` -The expression inside `${…}` is evaluated and the result becomes a part of the string. We can put anything there: a variable like `name` or an arithmetical expression like `1 + 2` or something more complex. +The expression inside `${…}` is evaluated and the result becomes a part of the string. We can put anything in there: a variable like `name` or an arithmetical expression like `1 + 2` or something more complex. -Please note that this can only be done in backticks. Other quotes do not allow such embedding! +Please note that this can only be done in backticks. Other quotes don't have this embedding functionality! ```js run alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (double quotes do nothing) ``` @@ -128,31 +128,31 @@ let isGreater = 4 > 1; alert( isGreater ); // true (the comparison result is "yes") ``` -We'll cover booleans more deeply later in the chapter . +We'll cover booleans more deeply in the chapter . ## The "null" value -The special `null` value does not belong to any type of those described above. +The special `null` value does not belong to any of the types described above. -It forms a separate type of its own, which contains only the `null` value: +It forms a separate type of its own which contains only the `null` value: ```js let age = null; ``` -In JavaScript `null` is not a "reference to a non-existing object" or a "null pointer" like in some other languages. +In JavaScript, `null` is not a "reference to a non-existing object" or a "null pointer" like in some other languages. -It's just a special value which has the sense of "nothing", "empty" or "value unknown". +It's just a special value which represents "nothing", "empty" or "value unknown". -The code above states that the `age` is unknown or empty for some reason. +The code above states that `age` is unknown or empty for some reason. ## The "undefined" value -The special value `undefined` stands apart. It makes a type of its own, just like `null`. +The special value `undefined` also stands apart. It makes a type of its own, just like `null`. The meaning of `undefined` is "value is not assigned". -If a variable is declared, but not assigned, then its value is exactly `undefined`: +If a variable is declared, but not assigned, then its value is `undefined`: ```js run let x; @@ -170,26 +170,26 @@ x = undefined; alert(x); // "undefined" ``` -...But it's not recommended to do that. Normally, we use `null` to write an "empty" or an "unknown" value into the variable, and `undefined` is only used for checks, to see if the variable is assigned or similar. +...But we don't recommend doing that. Normally, we use `null` to assign an "empty" or "unknown" value to a variable, and we use `undefined` for checks like seeing if a variable has been assigned. ## Objects and Symbols The `object` type is special. -All other types are called "primitive", because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities. We'll deal with them later in the chapter after we know enough about primitives. +All other types are called "primitive" because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities. We'll deal with them later in the chapter after we learn more about primitives. -The `symbol` type is used to create unique identifiers for objects. We have to mention it here for completeness, but it's better to study them after objects. +The `symbol` type is used to create unique identifiers for objects. We have to mention it here for completeness, but it's better to study this type after objects. ## The 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 make a quick check. +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. Function style: `typeof(x)`. +2. As a function: `typeof(x)`. -In other words, it works both with parentheses or without them. The result is the same. +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: @@ -217,16 +217,16 @@ typeof alert // "function" (3) */!* ``` -The last three lines may need additional explanations: +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 wrong. It is an officially recognized error in `typeof`, kept for compatibility. Of course, `null` is not an object. It is a special value with a separate type of its own. So, again, that's an error in the language. -3. The result of `typeof alert` is `"function"`, because `alert` is a function of the language. We'll study functions in the next chapters, and we'll see that there's no special "function" type in the language. Functions belong to the object type. But `typeof` treats them differently. Formally, it's incorrect, but very convenient in practice. +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 wrong. It is an officially recognized error in `typeof`, kept for compatibility. Of course, `null` is not an object. It is a special value with a separate type of its own. So, again, this is an error in the language. +3. The result of `typeof alert` is `"function"`, because `alert` is a function of the language. We'll study functions in the next chapters where we'll see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently. Formally, it's incorrect, but very convenient in practice. ## Summary -There are 7 basic types in JavaScript. +There are 7 basic data types in JavaScript. - `number` for numbers of any kind: integer or floating-point. - `string` for strings. A string may have one or more characters, there's no separate single-character type. @@ -236,10 +236,10 @@ There are 7 basic types in JavaScript. - `object` for more complex data structures. - `symbol` for unique identifiers. -The `typeof` operator allows us to see which type is stored in the variable. +The `typeof` operator allows us to see which type is stored in a variable. - Two forms: `typeof x` or `typeof(x)`. - Returns a string with the name of the type, like `"string"`. -- For `null` returns `"object"` -- that's an error in the language, it's not an object in fact. +- For `null` returns `"object"` -- this is an error in the language, it's not actually an object. -In the next chapters we'll concentrate on primitive values and once we're familiar with them, then we'll move on to objects. +In the next chapters, we'll concentrate on primitive values and once we're familiar with them, we'll move on to objects. diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md index ce48cbff1..7dd0d61c2 100644 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md @@ -10,13 +10,15 @@ true + false = 1 "4" - 2 = 2 "4px" - 2 = NaN 7 / 0 = Infinity -" -9\n" + 5 = " -9\n5" -" -9\n" - 5 = -14 -null + 1 = 1 // (3) -undefined + 1 = NaN // (4) +" -9 " + 5 = " -9 5" // (3) +" -9 " - 5 = -14 // (4) +null + 1 = 1 // (5) +undefined + 1 = NaN // (6) ``` 1. The addition with a string `"" + 1` converts `1` to a string: `"" + 1 = "1"`, and then we have `"1" + 0`, the same rule is applied. 2. The subtraction `-` (like most math operations) only works with numbers, it converts an empty string `""` to `0`. -3. `null` becomes `0` after the numeric conversion. -4. `undefined` becomes `NaN` after the numeric conversion. +3. The addition with a string appends the number `5` to the string. +4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it). +5. `null` becomes `0` after the numeric conversion. +6. `undefined` becomes `NaN` after the numeric conversion. diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md index 83b295f92..f17e870de 100644 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md @@ -17,8 +17,8 @@ true + false "4" - 2 "4px" - 2 7 / 0 -" -9\n" + 5 -" -9\n" - 5 +" -9 " + 5 +" -9 " - 5 null + 1 undefined + 1 ``` diff --git a/1-js/02-first-steps/06-type-conversions/article.md b/1-js/02-first-steps/06-type-conversions/article.md index 9f28bc5d0..6ac695e84 100644 --- a/1-js/02-first-steps/06-type-conversions/article.md +++ b/1-js/02-first-steps/06-type-conversions/article.md @@ -1,13 +1,13 @@ # Type Conversions -Most of the time, operators and functions automatically convert a value to the right type. That's called "type conversion". +Most of the time, operators and functions automatically convert the values given to them to the right type. For example, `alert` automatically converts any value to a string to show it. Mathematical operations convert values to numbers. -There are also cases when we need to explicitly convert a value to put things right. +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 don't cover objects yet. Here we study primitives first. Later, after we learn objects, we'll see how object conversion works in the chapter . +In this chapter, we won't cover objects. Instead, we'll study primitives first. Later, after we learn about objects, we'll see how object conversion works in the chapter . ``` ## ToString @@ -16,7 +16,7 @@ String conversion happens when we need the string form of a value. For example, `alert(value)` does it to show the value. -We can also use a call `String(value)` function for that: +We can also call the `String(value)` function to convert a value to a string: ```js run let value = true; @@ -28,7 +28,7 @@ alert(typeof value); // string */!* ``` -String conversion is mostly obvious. A `false` becomes `"false"`, `null` becomes `"null"` etc. +String conversion is mostly obvious. A `false` becomes `"false"`, `null` becomes `"null"`, etc. ## ToNumber @@ -40,7 +40,7 @@ For example, when division `/` is applied to non-numbers: alert( "6" / "2" ); // 3, strings are converted to numbers ``` -We can use a `Number(value)` function to explicitly convert a `value`: +We can use the `Number(value)` function to explicitly convert a `value` to a number: ```js run let str = "123"; @@ -51,9 +51,9 @@ let num = Number(str); // becomes a number 123 alert(typeof num); // number ``` -Explicit conversion is usually required when we read a value from a string-based source like a text form, but we expect a number to be entered. +Explicit conversion is usually required when we read a value from a string-based source like a text form but expect a number to be entered. -If the string is not a valid number, the result of such conversion is `NaN`, for instance: +If the string is not a valid number, the result of such a conversion is `NaN`. For instance: ```js run let age = Number("an arbitrary string instead of a number"); @@ -68,7 +68,7 @@ Numeric conversion rules: |`undefined`|`NaN`| |`null`|`0`| |true and false | `1` and `0` | -| `string` | Whitespaces from the start and the end are removed. Then, if the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | +| `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`. | Examples: @@ -79,30 +79,30 @@ alert( Number(true) ); // 1 alert( Number(false) ); // 0 ``` -Please note that `null` and `undefined` behave differently here: `null` becomes a zero, while `undefined` becomes `NaN`. +Please note that `null` and `undefined` behave differently here: `null` becomes zero while `undefined` becomes `NaN`. ````smart header="Addition '+' concatenates strings" -Almost all mathematical operations convert values to numbers. With a notable exception of the addition `+`. If one of the added values is a string, then another one is also converted to a string. +Almost all mathematical operations convert values to numbers. A notable exception is addition `+`. If one of the added values is a string, the other one is also converted to a string. -Then it concatenates (joins) them: +Then, it concatenates (joins) them: ```js run alert( 1 + '2' ); // '12' (string to the right) alert( '1' + 2 ); // '12' (string to the left) ``` -That only happens when at least one of the arguments is a string. Otherwise, values are converted to numbers. +This only happens when at least one of the arguments is a string. Otherwise, values are converted to numbers. ```` ## ToBoolean Boolean conversion is the simplest one. -It happens in logical operations (later we'll meet condition tests and other kinds of them), but also can be performed manually with the call of `Boolean(value)`. +It happens in logical operations (later we'll meet condition tests and other similar things) but can also be performed explicitly with a call to `Boolean(value)`. The conversion rule: -- Values that are intuitively "empty", like `0`, an empty string, `null`, `undefined` and `NaN` become `false`. +- Values that are intuitively "empty", like `0`, an empty string, `null`, `undefined`, and `NaN`, become `false`. - Other values become `true`. For instance: @@ -116,7 +116,7 @@ alert( Boolean("") ); // false ``` ````warn header="Please note: the string with zero `\"0\"` is `true`" -Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript a non-empty string is always `true`. +Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript, a non-empty string is always `true`. ```js run alert( Boolean("0") ); // true @@ -127,11 +127,11 @@ alert( Boolean(" ") ); // spaces, also true (any non-empty string is true) ## Summary -There are three most widely used type conversions: to string, to number and to boolean. +The three most widely used type conversions are to string, to number, and to boolean. -**`ToString`** -- Occurs when we output something, can be performed with `String(value)`. The conversion to string is usually obvious for primitive values. +**`ToString`** -- Occurs when we output something. Can be performed with `String(value)`. The conversion to string is usually obvious for primitive values. -**`ToNumber`** -- Occurs in math operations, can be performed with `Number(value)`. +**`ToNumber`** -- Occurs in math operations. Can be performed with `Number(value)`. The conversion follows the rules: @@ -142,7 +142,7 @@ The conversion follows the rules: |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`. | -**`ToBoolean`** -- Occurs in logical operations, or can be performed with `Boolean(value)`. +**`ToBoolean`** -- Occurs in logical operations. Can be performed with `Boolean(value)`. Follows the rules: @@ -157,4 +157,4 @@ Most of these rules are easy to understand and memorize. The notable exceptions - `undefined` is `NaN` as a number, not `0`. - `"0"` and space-only strings like `" "` are true as a boolean. -Objects are not covered here, we'll return to them later in the chapter that is devoted exclusively to objects, after we learn more basic things about JavaScript. +Objects aren't covered here. We'll return to them later in the chapter that is devoted exclusively to objects after we learn more basic things about JavaScript. diff --git a/1-js/02-first-steps/07-operators/article.md b/1-js/02-first-steps/07-operators/article.md index 6a2121fba..74b27e871 100644 --- a/1-js/02-first-steps/07-operators/article.md +++ b/1-js/02-first-steps/07-operators/article.md @@ -1,15 +1,15 @@ # Operators -Many operators are known to us from school. They are addition `+`, a multiplication `*`, a subtraction `-` and so on. +We know many operators from school. They are things like addition `+`, multiplication `*`, subtraction `-`, and so on. -In this chapter we concentrate on aspects that are not covered by school arithmetic. +In this chapter, we'll concentrate on aspects of operators that are not covered by school arithmetic. ## Terms: "unary", "binary", "operand" -Before we move on, let's grasp the common terminology. +Before we move on, let's grasp some common terminology. -- *An operand* -- is what operators are applied to. For instance in multiplication `5 * 2` there are two operands: the left operand is `5`, and the right operand is `2`. Sometimes people say "arguments" instead of "operands". -- An operator is *unary* if it has a single operand. For example, the unary negation `-` reverses the sign of the number: +- *An operand* -- is what operators are applied to. For instance, in the multiplication of `5 * 2` there are two operands: the left operand is `5` and the right operand is `2`. Sometimes, people call these "arguments" instead of "operands". +- An operator is *unary* if it has a single operand. For example, the unary negation `-` reverses the sign of a number: ```js run let x = 1; @@ -19,29 +19,29 @@ Before we move on, let's grasp the common terminology. */!* alert( x ); // -1, unary negation was applied ``` -- An operator is *binary* if it has two operands. The same minus exists in the binary form as well: +- An operator is *binary* if it has two operands. The same minus exists in binary form as well: ```js run no-beautify let x = 1, y = 3; alert( y - x ); // 2, binary minus subtracts values ``` - Formally, we're talking about two different operators here: the unary negation (single operand, reverses the sign) and the binary subtraction (two operands, subtracts). + Formally, we're talking about two different operators here: the unary negation (single operand: reverses the sign) and the binary subtraction (two operands: subtracts). -## Strings concatenation, binary + +## String concatenation, binary + -Now let's see special features of JavaScript operators that are beyond school arithmetics. +Now, let's see special features of JavaScript operators that are beyond school arithmetics. -Usually the plus operator `+` sums numbers. +Usually, the plus operator `+` sums numbers. -But if the binary `+` is applied to strings, it merges (concatenates) them: +But, if the binary `+` is applied to strings, it merges (concatenates) them: ```js let s = "my" + "string"; alert(s); // mystring ``` -Note that if any of the operands is a string, then the other one is converted to a string too. +Note that if one of the operands is a string, the other one is converted to a string too. For example: @@ -50,7 +50,7 @@ alert( '1' + 2 ); // "12" alert( 2 + '1' ); // "21" ``` -See, it doesn't matter whether the first operand is a string or the second one. The rule is simple: if either operand is a string, then convert the other one into a string as well. +See, it doesn't matter whether the first operand is a string or the second one. The rule is simple: if either operand is a string, the other one is converted into a string as well. However, note that operations run from left to right. If there are two numbers followed by a string, the numbers will be added before being converted to a string: @@ -59,7 +59,7 @@ However, note that operations run from left to right. If there are two numbers f alert(2 + 2 + '1' ); // "41" and not "221" ``` -String concatenation and conversion is a special feature of the binary plus `+`. Other arithmetic operators work only with numbers. They always convert their operands to numbers. +String concatenation and conversion is a special feature of the binary plus `+`. Other arithmetic operators work only with numbers and always convert their operands to numbers. For instance, subtraction and division: @@ -70,9 +70,9 @@ alert( '6' / '2' ); // 3 ## Numeric conversion, unary + -The plus `+` exists in two forms. The binary form that we used above and the unary form. +The plus `+` exists in two forms: the binary form that we used above and the unary form. -The unary plus or, in other words, the plus operator `+` applied to a single value, doesn't do anything with numbers, but if the operand is not a number, then it is converted into it. +The unary plus or, in other words, the plus operator `+` applied to a single value, doesn't do anything to numbers. But if the operand is not a number, the unary plus converts it into a number. For example: @@ -91,9 +91,9 @@ alert( +"" ); // 0 */!* ``` -It actually does the same as `Number(...)`, but is shorter. +It actually does the same thing as `Number(...)`, but is shorter. -A need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, then they are usually strings. +The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings. What if we want to sum them? @@ -106,7 +106,7 @@ let oranges = "3"; alert( apples + oranges ); // "23", the binary plus concatenates strings ``` -If we want to treat them as numbers, then we can convert and then sum: +If we want to treat them as numbers, we need to convert and then sum them: ```js run let apples = "2"; @@ -121,21 +121,21 @@ alert( +apples + +oranges ); // 5 // alert( Number(apples) + Number(oranges) ); // 5 ``` -From a mathematician's standpoint the abundance of pluses may seem strange. But from a programmer's standpoint, there's nothing special: unary pluses are applied first, they convert strings to numbers, and then the binary plus sums them up. +From a mathematician's standpoint, the abundance of pluses may seem strange. But from a programmer's standpoint, there's nothing special: unary pluses are applied first, they convert strings to numbers, and then the binary plus sums them up. -Why are unary pluses applied to values before the binary one? As we're going to see, that's because of their *higher precedence*. +Why are unary pluses applied to values before the binary ones? As we're going to see, that's because of their *higher precedence*. -## Operators precedence +## Operator precedence -If an expression has more than one operator, the execution order is defined by their *precedence*, or, in other words, there's an implicit priority order among the operators. +If an expression has more than one operator, the execution order is defined by their *precedence*, or, in other words, the implicit priority order of operators. -From school we all know that the multiplication in the expression `1 + 2 * 2` should be calculated before the addition. That's exactly the precedence thing. The multiplication is said to have *a higher precedence* than the addition. +From school, we all know that the multiplication in the expression `1 + 2 * 2` should be calculated before the addition. That's exactly the precedence thing. The multiplication is said to have *a higher precedence* than the addition. -Parentheses override any precedence, so if we're not satisfied with the order, we can use them, like: `(1 + 2) * 2`. +Parentheses override any precedence, so if we're not satisfied with the implicit order, we can use them to change it. For example: `(1 + 2) * 2`. -There are many operators in JavaScript. Every operator has a corresponding precedence number. The one with the bigger number executes first. If the precedence is the same, the execution order is from left to right. +There are many operators in JavaScript. Every operator has a corresponding precedence number. The one with the larger number executes first. If the precedence is the same, the execution order is from left to right. -An extract from the [precedence table](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones): +Here's an extract from the [precedence table](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones): | Precedence | Name | Sign | |------------|------|------| @@ -150,13 +150,13 @@ An extract from the [precedence table](https://developer.mozilla.org/en/JavaScri | 3 | assignment | `=` | | ... | ... | ... | -As we can see, the "unary plus" has a priority of `16`, which is higher than `13` for the "addition" (binary plus). That's why in the expression `"+apples + +oranges"` unary pluses work first, and then the addition. +As we can see, the "unary plus" has a priority of `16` which is higher than the `13` 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`. -That's why when we assign a variable, like `x = 2 * 2 + 1`, then the calculations are done first, and afterwards the `=` is evaluated, storing the result in `x`. +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`. ```js let x = 2 * 2 + 1; @@ -178,14 +178,14 @@ alert( b ); // 4 alert( c ); // 4 ``` -Chained assignments evaluate from right to left. First the rightmost expression `2 + 2` is evaluated then assigned to the variables on the left: `c`, `b` and `a`. At the end, all variables share a single value. +Chained assignments evaluate from right to left. First, the rightmost expression `2 + 2` is evaluated and then assigned to the variables on the left: `c`, `b` and `a`. At the end, all the variables share a single value. ````smart header="The assignment operator `\"=\"` returns a value" -An operator always returns a value. That's obvious for most of them like an addition `+` or a multiplication `*`. But the assignment operator follows that rule too. +An operator always returns a value. That's obvious for most of them like addition `+` or multiplication `*`. But the assignment operator follows this rule too. The call `x = value` writes the `value` into `x` *and then returns it*. -Here's the demo that uses an assignment as part of a more complex expression: +Here's a demo that uses an assignment as part of a more complex expression: ```js run let a = 1; @@ -201,12 +201,12 @@ alert( c ); // 0 In the example above, the result of `(a = b + 1)` is the value which is assigned to `a` (that is `3`). It is then used to subtract from `3`. -Funny code, isn't it? We should understand how it works, because sometimes we can see it in 3rd-party libraries, but shouldn't write anything like that ourselves. Such tricks definitely don't make the code clearer and readable. +Funny code, isn't it? We should understand how it works, because sometimes we see it in 3rd-party libraries, but shouldn't write anything like that ourselves. Such tricks definitely don't make code clearer or readable. ```` ## Remainder % -The remainder operator `%` despite its look does not have a relation to percents. +The remainder operator `%`, despite its appearance, is not related to percents. The result of `a % b` is the remainder of the integer division of `a` by `b`. @@ -232,7 +232,9 @@ alert( 2 ** 3 ); // 8 (2 * 2 * 2) alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) ``` -The operator works for non-integer numbers of `a` and `b` as well, for instance: +The operator works for non-integer numbers as well. + +For instance: ```js run alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root, that's maths) @@ -245,7 +247,7 @@ alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root) Increasing or decreasing a number by one is among the most common numerical operations. -So, there are special operators for that: +So, there are special operators for it: - **Increment** `++` increases a variable by 1: @@ -263,21 +265,21 @@ So, there are special operators for that: ``` ```warn -Increment/decrement can be applied only to a variable. An attempt to use it on a value like `5++` will give an error. +Increment/decrement can only be applied to variables. Trying to use it on a value like `5++` will give an error. ``` -Operators `++` and `--` can be placed both after and before the variable. +The operators `++` and `--` can be placed either before or after a variable. -- When the operator goes after the variable, it is called a "postfix form": `counter++`. -- The "prefix form" is when the operator stands before the variable: `++counter`. +- When the operator goes after the variable, it is in "postfix form": `counter++`. +- The "prefix form" is when the operator goes before the variable: `++counter`. -Both of these records do the same: increase `counter` by `1`. +Both of these statements do the same thing: increase `counter` by `1`. Is there any difference? Yes, but we can only see it if we use the returned value of `++/--`. -Let's clarify. As we know, all operators return a value. Increment/decrement is not an exception here. The prefix form returns the new value, while the postfix form returns the old value (prior to increment/decrement). +Let's clarify. As we know, all operators return a value. Increment/decrement is no exception. The prefix form returns the new value while the postfix form returns the old value (prior to increment/decrement). -To see the difference, here's the example: +To see the difference, here's an example: ```js run let counter = 1; @@ -286,9 +288,9 @@ let a = ++counter; // (*) alert(a); // *!*2*/!* ``` -Here in the line `(*)` the prefix call `++counter` increments `counter` and returns the new value that is `2`. So the `alert` shows `2`. +In the line `(*)`, the *prefix* form `++counter` increments `counter` and returns the new value, `2`. So, the `alert` shows `2`. -Now let's use the postfix form: +Now, let's use the postfix form: ```js run let counter = 1; @@ -297,11 +299,11 @@ let a = counter++; // (*) changed ++counter to counter++ alert(a); // *!*1*/!* ``` -In the line `(*)` the *postfix* form `counter++` also increments `counter`, but returns the *old* value (prior to increment). So the `alert` shows `1`. +In the line `(*)`, the *postfix* form `counter++` also increments `counter` but returns the *old* value (prior to increment). So, the `alert` shows `1`. To summarize: -- If the result of increment/decrement is not used, then there is no difference in which form to use: +- If the result of increment/decrement is not used, there is no difference in which form to use: ```js run let counter = 0; @@ -309,13 +311,13 @@ To summarize: ++counter; alert( counter ); // 2, the lines above did the same ``` -- If we'd like to increase the value *and* use the result of the operator right now, then we need the prefix form: +- If we'd like to increase a value *and* immediately use the result of the operator, we need the prefix form: ```js run let counter = 0; alert( ++counter ); // 1 ``` -- If we'd like to increment, but use the previous value, then we need the postfix form: +- If we'd like to increment a value but use its previous value, we need the postfix form: ```js run let counter = 0; @@ -323,7 +325,7 @@ To summarize: ``` ````smart header="Increment/decrement among other operators" -Operators `++/--` can be used inside an expression as well. Their precedence is higher than most other arithmetical operations. +The operators `++/--` can be used inside expressions as well. Their precedence is higher than most other arithmetical operations. For instance: @@ -339,11 +341,11 @@ let counter = 1; alert( 2 * counter++ ); // 2, because counter++ returns the "old" value ``` -Though technically allowable, such notation usually makes the code less readable. One line does multiple things -- not good. +Though technically okay, such notation usually makes code less readable. One line does multiple things -- not good. -While reading the code, a fast "vertical" eye-scan can easily miss such `counter++`, and it won't be obvious that the variable increases. +While reading code, a fast "vertical" eye-scan can easily miss something like `counter++` and it won't be obvious that the variable increased. -The "one line -- one action" style is advised: +We advise a style of "one line -- one action": ```js run let counter = 1; @@ -368,11 +370,11 @@ The list of operators: - RIGHT SHIFT ( `>>` ) - ZERO-FILL RIGHT SHIFT ( `>>>` ) -These operators are used very rarely. To understand them, we should delve into low-level number representation, and it would not be optimal to do that right now. Especially because we won't need them any time soon. If you're curious, you can read the [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) article in MDN. It would be more practical to do that when a real need arises. +These operators are used very rarely. To understand them, we need to delve into low-level number representation and it would not be optimal to do that right now, especially since we won't need them any time soon. If you're curious, you can read the [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) article on MDN. It would be more practical to do that when a real need arises. ## Modify-in-place -We often need to apply an operator to a variable and store the new result in it. +We often need to apply an operator to a variable and store the new result in that same variable. For example: @@ -382,7 +384,7 @@ n = n + 5; n = n * 2; ``` -This notation can be shortened using operators `+=` and `*=`: +This notation can be shortened using the operators `+=` and `*=`: ```js run let n = 2; @@ -392,7 +394,7 @@ n *= 2; // now n = 14 (same as n = n * 2) alert( n ); // 14 ``` -Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: `/=`, `-=` etc. +Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: `/=`, `-=`, etc. Such operators have the same precedence as a normal assignment, so they run after most other calculations: @@ -406,9 +408,9 @@ alert( n ); // 16 (right part evaluated first, same as n *= 8) ## Comma -The comma operator `,` is one of most rare and unusual operators. Sometimes it's used to write shorter code, so we need to know it in order to understand what's going on. +The comma operator `,` is one of the rarest and most unusual operators. Sometimes, it's used to write shorter code, so we need to know it in order to understand what's going on. -The comma operator allows us to evaluate several expressions, dividing them with a comma `,`. Each of them is evaluated, but the result of only the last one is returned. +The comma operator allows us to evaluate several expressions, dividing them with a comma `,`. Each of them is evaluated but only the result of the last one is returned. For example: @@ -420,17 +422,17 @@ let a = (1 + 2, 3 + 4); alert( a ); // 7 (the result of 3 + 4) ``` -Here, the first expression `1 + 2` is evaluated, and its result is thrown away, then `3 + 4` is evaluated and returned as the result. +Here, the first expression `1 + 2` is evaluated and its result is thrown away. Then, `3 + 4` is evaluated and returned as the result. ```smart header="Comma has a very low precedence" Please note that the comma operator has very low precedence, lower than `=`, so parentheses are important in the example above. -Without them: `a = 1 + 2, 3 + 4` evaluates `+` first, summing the numbers into `a = 3, 7`, then the assignment operator `=` assigns `a = 3`, and then the number after the comma `7` is not processed anyhow, so it's ignored. +Without them: `a = 1 + 2, 3 + 4` evaluates `+` first, summing the numbers into `a = 3, 7`, then the assignment operator `=` assigns `a = 3`, and finally the number after the comma, `7`, is not processed so it's ignored. ``` -Why do we need such an operator which throws away everything except the last part? +Why do we need an operator that throws away everything except the last part? -Sometimes people use it in more complex constructs to put several actions in one line. +Sometimes, people use it in more complex constructs to put several actions in one line. For example: @@ -441,4 +443,4 @@ for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { } ``` -Such tricks are used in many JavaScript frameworks, that's why we mention them. But usually they don't improve the code readability, so we should think well before writing like that. +Such tricks are used in many JavaScript frameworks. That's why we're mentioning them. But, usually, they don't improve code readability so we should think well before using them. diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md index 891e703d0..be7f75ddd 100644 --- a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md +++ b/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md @@ -4,7 +4,7 @@ importance: 5 # Comparisons -What will be the result for expressions? +What will be the result for these expressions? ```js no-beautify 5 > 4 diff --git a/1-js/02-first-steps/08-comparison/article.md b/1-js/02-first-steps/08-comparison/article.md index 6a69c95e1..8697076a4 100644 --- a/1-js/02-first-steps/08-comparison/article.md +++ b/1-js/02-first-steps/08-comparison/article.md @@ -1,18 +1,18 @@ # Comparisons -Many comparison operators we know from maths: +We know many comparison operators from maths: - Greater/less than: a > b, a < b. - Greater/less than or equals: a >= b, a <= b. -- Equality check is written as `a == b` (please note the double equation sign `=`. A single symbol `a = b` would mean an assignment). -- Not equals. In maths the notation is , in JavaScript it's written as an assignment with an exclamation sign before it: a != b. +- Equals: `a == b` (please note the double equals sign `=`. A single symbol `a = b` would mean an assignment). +- Not equals. In maths the notation is , but in JavaScript it's written as an assignment with an exclamation sign before it: a != b. ## Boolean is the result -Just as all other operators, a comparison returns a value. The value is of the boolean type. +Like all other operators, a comparison returns a value. In this case, the value is a boolean. - `true` -- means "yes", "correct" or "the truth". -- `false` -- means "no", "wrong" or "a lie". +- `false` -- means "no", "wrong" or "not the truth". For example: @@ -31,7 +31,7 @@ alert( result ); // true ## String comparison -To see which string is greater than the other, the so-called "dictionary" or "lexicographical" order is used. +To see whether a string is greater than another, JavaScript uses the so-called "dictionary" or "lexicographical" order. In other words, strings are compared letter-by-letter. @@ -45,29 +45,27 @@ alert( 'Bee' > 'Be' ); // true The algorithm to compare two strings is simple: -1. Compare first characters of both strings. -2. If the first one is greater(or less), then the first string is greater(or less) than the second. We're done. -3. Otherwise if first characters are equal, compare the second characters the same way. -4. Repeat until the end of any string. -5. If both strings ended simultaneously, then they are equal. Otherwise the longer string is greater. +1. Compare the first character of both strings. +2. If the first character from the first string is greater (or less) than the other string's, then the first string is greater (or less) than the second. We're done. +3. Otherwise, if both strings' first characters are the same, compare the second characters the same way. +4. Repeat until the end of either string. +5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater. -In the example above, the comparison `'Z' > 'A'` gets the result at the first step. - -Strings `"Glow"` and `"Glee"` are compared character-by-character: +In the examples above, the comparison `'Z' > 'A'` gets to a result at the first step while the strings `"Glow"` and `"Glee"` are compared character-by-character: 1. `G` is the same as `G`. 2. `l` is the same as `l`. 3. `o` is greater than `e`. Stop here. The first string is greater. ```smart header="Not a real dictionary, but Unicode order" -The comparison algorithm given above is roughly equivalent to the one used in book dictionaries or phone books. But it's not exactly the same. +The comparison algorithm given above is roughly equivalent to the one used in dictionaries or phone books, but it's not exactly the same. -For instance, case matters. A capital letter `"A"` is not equal to the lowercase `"a"`. Which one is greater? Actually, the lowercase `"a"` is. Why? Because the lowercase character has a greater index in the internal encoding table (Unicode). We'll get back to specific details and consequences in the chapter . +For instance, case matters. A capital letter `"A"` is not equal to the lowercase `"a"`. Which one is greater? The lowercase `"a"`. Why? Because the lowercase character has a greater index in the internal encoding table JavaScript uses (Unicode). We'll get back to specific details and consequences of this in the chapter . ``` ## Comparison of different types -When compared values belong to different types, they are converted to numbers. +When comparing values of different types, JavaScript converts the values to numbers. For example: @@ -76,7 +74,9 @@ alert( '2' > 1 ); // true, string '2' becomes a number 2 alert( '01' == 1 ); // true, string '01' becomes a number 1 ``` -For boolean values, `true` becomes `1` and `false` becomes `0`, that's why: +For boolean values, `true` becomes `1` and `false` becomes `0`. + +For example: ```js run alert( true == 1 ); // true @@ -101,24 +101,24 @@ alert( Boolean(b) ); // true alert(a == b); // true! ``` -From JavaScript's standpoint that's quite normal. An equality check converts using the numeric conversion (hence `"0"` becomes `0`), while `Boolean` conversion uses another set of rules. +From JavaScript's standpoint, this result is quite normal. An equality check converts values using the numeric conversion (hence `"0"` becomes `0`), while the explicit `Boolean` conversion uses another set of rules. ```` ## Strict equality -A regular equality check `==` has a problem. It cannot differ `0` from `false`: +A regular equality check `==` has a problem. It cannot differentiate `0` from `false`: ```js run alert( 0 == false ); // true ``` -The same thing with an empty string: +The same thing happens with an empty string: ```js run alert( '' == false ); // true ``` -That's because operands of different types are converted to a number by the equality operator `==`. An empty string, just like `false`, becomes a zero. +This happens because operands of different types are converted to numbers by the equality operator `==`. An empty string, just like `false`, becomes a zero. What to do if we'd like to differentiate `0` from `false`? @@ -132,19 +132,19 @@ Let's try it: alert( 0 === false ); // false, because the types are different ``` -There also exists a "strict non-equality" operator `!==`, as an analogy for `!=`. +There is also a "strict non-equality" operator `!==` analogous to `!=`. -The strict equality check operator is a bit longer to write, but makes it obvious what's going on and leaves less space for errors. +The strict equality operator is a bit longer to write, but makes it obvious what's going on and leaves less room for errors. ## Comparison with null and undefined Let's see more edge cases. -There's a non-intuitive behavior when `null` or `undefined` are compared with other values. +There's a non-intuitive behavior when `null` or `undefined` are compared to other values. For a strict equality check `===` -: These values are different, because each of them belongs to a separate type of its own. +: These values are different, because each of them is a different type. ```js run alert( null === undefined ); // false @@ -158,9 +158,9 @@ For a non-strict check `==` ``` For maths and other comparisons `< > <= >=` -: Values `null/undefined` are converted to a number: `null` becomes `0`, while `undefined` becomes `NaN`. +: `null/undefined` are converted to numbers: `null` becomes `0`, while `undefined` becomes `NaN`. -Now let's see funny things that happen when we apply those rules. And, what's more important, how to not fall into a trap with these features. +Now let's see some funny things that happen when we apply these rules. And, what's more important, how to not fall into a trap with them. ### Strange result: null vs 0 @@ -172,15 +172,15 @@ alert( null == 0 ); // (2) false alert( null >= 0 ); // (3) *!*true*/!* ``` -Yeah, mathematically that's strange. The last result states that "`null` is greater than or equal to zero". Then one of the comparisons above must be correct, but they are both false. +Mathematically, that's strange. The last result states that "`null` is greater than or equal to zero", so in one of the comparisons above it must be `true`, but they are both false. -The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, hence treat it as `0`. That's why (3) `null >= 0` is true and (1) `null > 0` is false. +The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, treating it as `0`. That's why (3) `null >= 0` is true and (1) `null > 0` is false. On the other hand, the equality check `==` for `undefined` and `null` is defined such that, without any conversions, they equal each other and don't equal anything else. That's why (2) `null == 0` is false. ### An incomparable undefined -The value `undefined` shouldn't participate in comparisons at all: +The value `undefined` shouldn't be compared to other values: ```js run alert( undefined > 0 ); // false (1) @@ -188,25 +188,25 @@ alert( undefined < 0 ); // false (2) alert( undefined == 0 ); // false (3) ``` -Why does it dislike a zero so much? Always false! +Why does it dislike zero so much? Always false! -We've got these results because: +We get these results because: -- Comparisons `(1)` and `(2)` return `false` because `undefined` gets converted to `NaN`. And `NaN` is a special numeric value which returns `false` for all comparisons. -- The equality check `(3)` returns `false`, because `undefined` only equals `null` and no other value. +- Comparisons `(1)` and `(2)` return `false` because `undefined` gets converted to `NaN` and `NaN` is a special numeric value which returns `false` for all comparisons. +- The equality check `(3)` returns `false` because `undefined` only equals `null`, `undefined`, and no other value. ### Evade problems -Why did we observe these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to evade any problems with them. +Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to evade problems with them: Just treat any comparison with `undefined/null` except the strict equality `===` with exceptional care. -Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you are really sure what you're doing. If a variable can have such values, then check for them separately. +Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately. ## Summary -- Comparison operators return a logical value. +- Comparison operators return a boolean value. - Strings are compared letter-by-letter in the "dictionary" order. - When values of different types are compared, they get converted to numbers (with the exclusion of a strict equality check). -- Values `null` and `undefined` equal `==` each other and do not equal any other value. -- Be careful when using comparisons like `>` or `<` with variables that can occasionally be `null/undefined`. Making a separate check for `null/undefined` is a good idea. +- The values `null` and `undefined` equal `==` each other and do not equal any other value. +- Be careful when using comparisons like `>` or `<` with variables that can occasionally be `null/undefined`. Checking for `null/undefined` separately is a good idea. diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/article.md b/1-js/02-first-steps/09-alert-prompt-confirm/article.md index 65a425577..c14e0c85a 100644 --- a/1-js/02-first-steps/09-alert-prompt-confirm/article.md +++ b/1-js/02-first-steps/09-alert-prompt-confirm/article.md @@ -1,8 +1,8 @@ # Interaction: alert, prompt, confirm -This part of the tutorial aims to cover JavaScript "as is", without environment-specific tweaks. +In this part of the tutorial we cover JavaScript language "as is", without environment-specific tweaks. -But still we use a browser as the demo environment. So we should know at least a few user-interface functions. In this chapter we'll get familiar with the browser functions `alert`, `prompt` and `confirm`. +But we'll still be using the browser as our demo environment, so we should know at least a few of its user-interface functions. In this chapter, we'll get familiar with the browser functions `alert`, `prompt` and `confirm`. ## alert @@ -12,7 +12,7 @@ Syntax: alert(message); ``` -This shows a message and pauses the script execution until the user presses "OK". +This shows a message and pauses script execution until the user presses "OK". For example: @@ -20,27 +20,27 @@ For example: alert("Hello"); ``` -The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons etc, until they have dealt with the window. In this case -- until they press "OK". +The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc. until they have dealt with the window. In this case -- until they press "OK". ## prompt -Function `prompt` accepts two arguments: +The function `prompt` accepts two arguments: ```js no-beautify -result = prompt(title[, default]); +result = prompt(title, [default]); ``` -It shows a modal window with a text message, an input field for the visitor and buttons OK/CANCEL. +It shows a modal window with a text message, an input field for the visitor, and the buttons OK/CANCEL. `title` -: The text to show to the visitor. +: The text to show the visitor. `default` : An optional second parameter, the initial value for the input field. -The visitor may type something in the prompt input field and press OK. Or they can cancel the input by pressing the CANCEL button or hitting the `key:Esc` key. +The visitor may type something in the prompt input field and press OK. Or they can cancel the input by pressing CANCEL or hitting the `key:Esc` key. -The call to `prompt` returns the text from the field or `null` if the input was canceled. +The call to `prompt` returns the text from the input field or `null` if the input was canceled. For instance: @@ -50,16 +50,16 @@ let age = prompt('How old are you?', 100); alert(`You are ${age} years old!`); // You are 100 years old! ``` -````warn header="IE: always supply a `default`" -The second parameter is optional. But if we don't supply it, Internet Explorer would insert the text `"undefined"` into the prompt. +````warn header="In IE: always supply a `default`" +The second parameter is optional, but if we don't supply it, Internet Explorer will insert the text `"undefined"` into the prompt. -Run this code in Internet Explorer to see that: +Run this code in Internet Explorer to see: ```js run let test = prompt("Test"); ``` -So, to look good in IE, it's recommended to always provide the second argument: +So, for prompts to look good in IE, we recommend always providing the second argument: ```js run let test = prompt("Test", ''); // <-- for IE @@ -74,7 +74,7 @@ The syntax: result = confirm(question); ``` -Function `confirm` shows a modal window with a `question` and two buttons: OK and CANCEL. +The function `confirm` shows a modal window with a `question` and two buttons: OK and CANCEL. The result is `true` if OK is pressed and `false` otherwise. @@ -88,22 +88,22 @@ alert( isBoss ); // true if OK is pressed ## Summary -We covered 3 browser-specific functions to interact with the visitor: +We covered 3 browser-specific functions to interact with visitors: `alert` : shows a message. `prompt` -: shows a message asking the user to input text. It returns the text or, if CANCEL or `key:Esc` is clicked, all browsers return `null`. +: shows a message asking the user to input text. It returns the text or, if CANCEL or `key:Esc` is clicked, `null`. `confirm` : shows a message and waits for the user to press "OK" or "CANCEL". It returns `true` for OK and `false` for CANCEL/`key:Esc`. -All these methods are modal: they pause the script execution and don't allow the visitor to interact with the rest of the page until the message has been dismissed. +All these methods are modal: they pause script execution and don't allow the visitor to interact with the rest of the page until the window has been dismissed. There are two limitations shared by all the methods above: -1. The exact location of the modal window is determined by the browser. Usually it's in the center. +1. The exact location of the modal window is determined by the browser. Usually, it's in the center. 2. The exact look of the window also depends on the browser. We can't modify it. That is the price for simplicity. There are other ways to show nicer windows and richer interaction with the visitor, but if "bells and whistles" do not matter much, these methods work just fine. diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.png b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.png index 8c57b1885..04fb1fb0b 100644 Binary files a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.png and b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.png differ diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2@2x.png b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2@2x.png index cbb2c611a..08226b790 100644 Binary files a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2@2x.png and b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2@2x.png differ diff --git a/1-js/02-first-steps/10-ifelse/4-check-login/ifelse_task.png b/1-js/02-first-steps/10-ifelse/4-check-login/ifelse_task.png deleted file mode 100644 index 8b54dc83d..000000000 Binary files a/1-js/02-first-steps/10-ifelse/4-check-login/ifelse_task.png and /dev/null differ diff --git a/1-js/02-first-steps/10-ifelse/4-check-login/ifelse_task@2x.png b/1-js/02-first-steps/10-ifelse/4-check-login/ifelse_task@2x.png deleted file mode 100644 index 92001dfe8..000000000 Binary files a/1-js/02-first-steps/10-ifelse/4-check-login/ifelse_task@2x.png and /dev/null differ diff --git a/1-js/02-first-steps/10-ifelse/article.md b/1-js/02-first-steps/10-ifelse/article.md index b3ab59972..49c1fc041 100644 --- a/1-js/02-first-steps/10-ifelse/article.md +++ b/1-js/02-first-steps/10-ifelse/article.md @@ -1,12 +1,12 @@ # Conditional operators: if, '?' -Sometimes we need to perform different actions based on a condition. +Sometimes, we need to perform different actions based on different conditions. -There is the `if` statement for that and also the conditional (ternary) operator for conditional evaluation which we will be referring as the “question mark” operator `?` for simplicity. +To do that, we can use the `if` statement and the conditional operator `?`, that's also called a "question mark" operator. ## The "if" statement -The `if` statement gets a condition, evaluates it and, if the result is `true`, executes the code. +The `if` statement evaluates a condition and, if the condition's result is `true`, executes a block of code. For example: @@ -18,9 +18,9 @@ if (year == 2015) alert( 'You are right!' ); */!* ``` -In the example above, the condition is a simple equality check: `year == 2015`, but it can be much more complex. +In the example above, the condition is a simple equality check (`year == 2015`), but it can be much more complex. -If there is more than one statement to be executed, we have to wrap our code block inside curly braces: +If we want to execute more than one statement, we have to wrap our code block inside curly braces: ```js if (year == 2015) { @@ -29,15 +29,15 @@ if (year == 2015) { } ``` -It is recommended to wrap your code block with curly braces `{}` every time with `if`, even if there is only one statement. That improves readability. +We recommend wrapping your code block with curly braces `{}` every time you use an `if` statement, even if there is only one statement to execute. Doing so improves readability. ## Boolean conversion -The `if (…)` statement evaluates the expression in parentheses and converts it to the boolean type. +The `if (…)` statement evaluates the expression in its parentheses and converts the result to a boolean. Let's recall the conversion rules from the chapter : -- A number `0`, an empty string `""`, `null`, `undefined` and `NaN` become `false`. Because of that they are called "falsy" values. +- A number `0`, an empty string `""`, `null`, `undefined`, and `NaN` all become `false`. Because of that they are called "falsy" values. - Other values become `true`, so they are called "truthy". So, the code under this condition would never execute: @@ -48,7 +48,7 @@ if (0) { // 0 is falsy } ``` -...And inside this condition -- always works: +...and inside this condition -- it always will: ```js if (1) { // 1 is truthy @@ -56,7 +56,7 @@ if (1) { // 1 is truthy } ``` -We can also pass a pre-evaluated boolean value to `if`, like here: +We can also pass a pre-evaluated boolean value to `if`, like this: ```js let cond = (year == 2015); // equality evaluates to true or false @@ -68,11 +68,11 @@ if (cond) { ## The "else" clause -The `if` statement may contain an optional "else" block. It executes when the condition is wrong. +The `if` statement may contain an optional "else" block. It executes when the condition is false. For example: ```js run -let year = prompt('In which year was ECMAScript-2015 specification published?', ''); +let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); if (year == 2015) { alert( 'You guessed it right!' ); @@ -83,12 +83,12 @@ if (year == 2015) { ## Several conditions: "else if" -Sometimes we'd like to test several variants of a condition. There is an `else if` clause for that. +Sometimes, we'd like to test several variants of a condition. The `else if` clause lets us do that. For example: ```js run -let year = prompt('In which year was ECMAScript-2015 specification published?', ''); +let year = prompt('In which year was the ECMAScript-2015 specification published?', ''); if (year < 2015) { alert( 'Too early...' ); @@ -99,13 +99,13 @@ if (year < 2015) { } ``` -In the code above JavaScript first checks `year < 2015`. If it is falsy it then goes to the next condition `year > 2015`, and otherwise shows the last `alert`. +In the code above, JavaScript first checks `year < 2015`. If that is falsy, it goes to the next condition `year > 2015`. If that is also falsy, it shows the last `alert`. -There can be more `else if` blocks. The ending `else` is optional. +There can be more `else if` blocks. The final `else` is optional. -## Ternary operator '?' +## Conditional operator '?' -Sometimes we need to assign a variable depending on a condition. +Sometimes, we need to assign a variable depending on a condition. For instance: @@ -124,16 +124,16 @@ if (age > 18) { alert(accessAllowed); ``` -The so-called "ternary" or "question mark" operator lets us do that shorter and simpler. +The so-called "conditional" or "question mark" operator lets us do that in a shorter and simpler way. -The operator is represented by a question mark `?`. The formal term "ternary" means that the operator has three operands. It is actually the one and only operator in JavaScript which has that many. +The operator is represented by a question mark `?`. Sometimes it's called "ternary", because the operator has three operands. It is actually the one and only operator in JavaScript which has that many. The syntax is: ```js -let result = condition ? value1 : value2 +let result = condition ? value1 : value2; ``` -The `condition` is evaluated, if it's truthy then `value1` is returned, otherwise -- `value2`. +The `condition` is evaluated: if it's truthy then `value1` is returned, otherwise -- `value2`. For example: @@ -141,7 +141,9 @@ For example: let accessAllowed = (age > 18) ? true : false; ``` -Technically, we can omit parentheses around `age > 18`. The question mark operator has a low precedence. It executes after the comparison `>`, so that'll do the same: +Technically, we can omit the parentheses around `age > 18`. The question mark operator has a low precedence, so it executes after the comparison `>`. + +This example will do the same thing as the previous one: ```js // the comparison operator "age > 18" executes first anyway @@ -149,10 +151,10 @@ Technically, we can omit parentheses around `age > 18`. The question mark operat let accessAllowed = age > 18 ? true : false; ``` -But parentheses make the code more readable, so it's recommended to use them. +But parentheses make the code more readable, so we recommend using them. ````smart -In the example above it's possible to evade the question mark operator, because the comparison by itself returns `true/false`: +In the example above, you can avoid using the question mark operator because the comparison itself returns `true/false`: ```js // the same @@ -162,7 +164,7 @@ let accessAllowed = age > 18; ## Multiple '?' -A sequence of question mark `?` operators allows returning a value that depends on more than one condition. +A sequence of question mark operators `?` can return a value that depends on more than one condition. For instance: ```js run @@ -176,14 +178,14 @@ let message = (age < 3) ? 'Hi, baby!' : 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. +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 -- returns `'Hi, baby!'`, otherwise -- goes after the colon `":"` and checks for `age < 18`. -3. If that's true -- returns `'Hello!'`, otherwise -- goes after the next colon `":"` and checks for `age < 100`. -4. If that's true -- returns `'Greetings!'`, otherwise -- goes after the last colon `":"` and returns `'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!'`. -The same logic using `if..else`: +Here's how this looks using `if..else`: ```js if (age < 3) { @@ -210,15 +212,15 @@ let company = prompt('Which company created JavaScript?', ''); */!* ``` -Depending on the condition `company == 'Netscape'`, either the first or the second part after `?` gets executed and shows the alert. +Depending on the condition `company == 'Netscape'`, either the first or the second expression after the `?` gets executed and shows an alert. -We don't assign a result to a variable here. The idea is to execute different code depending on the condition. +We don't assign a result to a variable here. Instead, we execute different code depending on the condition. -**It is not recommended to use the question mark operator in this way.** +**We don't recommend using the question mark operator in this way.** -The notation seems to be shorter than `if`, which appeals to some programmers. But it is less readable. +The notation is shorter than the equivalent `if` statement, which appeals to some programmers. But it is less readable. -Here is the same code with `if` for comparison: +Here is the same code using `if` for comparison: ```js run no-beautify let company = prompt('Which company created JavaScript?', ''); @@ -232,6 +234,6 @@ if (company == 'Netscape') { */!* ``` -Our eyes scan the code vertically. The constructs which span several lines are easier to understand than a long horizontal instruction set. +Our eyes scan the code vertically. Code blocks which span several lines are easier to understand than a long, horizontal instruction set. -The idea of a question mark `?` is to return one or another value depending on the condition. Please use it for exactly that. There is `if` to execute different branches of the code. +The purpose of the question mark operator `?` is to return one value or another depending on its condition. Please use it for exactly that. Use `if` when you need to execute different branches of code. diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.png b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.png new file mode 100644 index 000000000..32f0d4b94 Binary files /dev/null and b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task.png differ diff --git a/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task@2x.png b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task@2x.png new file mode 100644 index 000000000..c3867e62c Binary files /dev/null and b/1-js/02-first-steps/11-logical-operators/9-check-login/ifelse_task@2x.png differ diff --git a/1-js/02-first-steps/10-ifelse/4-check-login/solution.md b/1-js/02-first-steps/11-logical-operators/9-check-login/solution.md similarity index 100% rename from 1-js/02-first-steps/10-ifelse/4-check-login/solution.md rename to 1-js/02-first-steps/11-logical-operators/9-check-login/solution.md diff --git a/1-js/02-first-steps/10-ifelse/4-check-login/task.md b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md similarity index 82% rename from 1-js/02-first-steps/10-ifelse/4-check-login/task.md rename to 1-js/02-first-steps/11-logical-operators/9-check-login/task.md index 1d12cb096..780e674a9 100644 --- a/1-js/02-first-steps/10-ifelse/4-check-login/task.md +++ b/1-js/02-first-steps/11-logical-operators/9-check-login/task.md @@ -20,6 +20,6 @@ The schema: Please use nested `if` blocks. Mind the overall readability of the code. -Hint: passing an empty input to a prompt returns an empty string `''`. Pressing `key:ESC` during a prompt returns `null`. +Hint: passing an empty input to a prompt returns an empty string `''`. Pressing `key:ESC` during a prompt returns `null`. [demo] 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 c69e3ccda..0773a10cb 100644 --- a/1-js/02-first-steps/11-logical-operators/article.md +++ b/1-js/02-first-steps/11-logical-operators/article.md @@ -2,7 +2,7 @@ There are three logical operators in JavaScript: `||` (OR), `&&` (AND), `!` (NOT). -Although they are called "logical", they can be applied to values of any type, not only boolean. The result can also be of any type. +Although they are called "logical", they can be applied to values of any type, not only boolean. Their result can also be of any type. Let's see the details. @@ -14,9 +14,9 @@ The "OR" operator is represented with two vertical line symbols: result = a || b; ``` -In classical programming, logical OR is meant to manipulate boolean values only. If any of its arguments are `true`, then it returns `true`, otherwise it returns `false`. +In classical programming, the logical OR is meant to manipulate boolean values only. If any of its arguments are `true`, it returns `true`, otherwise it returns `false`. -In JavaScript the operator is a little bit more tricky and powerful. But first let's see what happens with boolean values. +In JavaScript, the operator is a little bit trickier and more powerful. But first, let's see what happens with boolean values. There are four possible logical combinations: @@ -29,9 +29,9 @@ alert( false || false ); // false As we can see, the result is always `true` except for the case when both operands are `false`. -If an operand is not boolean, then it's converted to boolean for the evaluation. +If an operand is not a boolean, it's converted to a boolean for the evaluation. -For instance, a number `1` is treated as `true`, a number `0` -- as `false`: +For instance, the number `1` is treated as `true`, the number `0` as `false`: ```js run if (1 || 0) { // works just like if( true || false ) @@ -39,7 +39,7 @@ if (1 || 0) { // works just like if( true || false ) } ``` -Most of the time, OR `||` is used in an `if` statement to test if *any* of the given conditions is correct. +Most of the time, OR `||` is used in an `if` statement to test if *any* of the given conditions is `true`. For example: @@ -64,9 +64,9 @@ if (hour < 10 || hour > 18 || isWeekend) { } ``` -## OR seeks the first truthy value +## OR finds the first truthy value -The logic described above is somewhat classical. Now let's bring in the "extra" features of JavaScript. +The logic described above is somewhat classical. Now, let's bring in the "extra" features of JavaScript. The extended algorithm works as follows. @@ -78,13 +78,13 @@ result = value1 || value2 || value3; The OR `||` operator does the following: -- Evaluate operands from left to right. -- For each operand, convert it to boolean. If the result is `true`, then stop and return the original value of that operand. -- If all other operands have been assessed (i.e. all were `false`), return the last operand. +- Evaluates operands from left to right. +- For each operand, converts it to boolean. If the result is `true`, stops and returns the original value of that operand. +- If all operands have been evaluated (i.e. all were `false`), returns the last operand. A value is returned in its original form, without the conversion. -In other words, a chain of OR `"||"` returns the first truthy value or the last one if no such value is found. +In other words, a chain of OR `"||"` returns the first truthy value or the last one if no truthy value is found. For instance: @@ -97,13 +97,13 @@ alert( null || 0 || 1 ); // 1 (the first truthy value) alert( undefined || null || 0 ); // 0 (all falsy, returns the last value) ``` -That leads to some interesting usages compared to a "pure, classical, boolean-only OR". +This leads to some interesting usage compared to a "pure, classical, boolean-only OR". -1. **Getting the first truthy value from the list of variables or expressions.** +1. **Getting the first truthy value from a list of variables or expressions.** - Imagine we have several variables, which can either contain the data or be `null/undefined`. And we need to choose the first one with data. + Imagine we have a list of variables which can either contain data or be `null/undefined`. How can we find the first one with data? - We can use OR `||` for that: + We can use OR `||`: ```js run let currentUser = null; @@ -116,14 +116,14 @@ That leads to some interesting usages compared to a "pure, classical, boolean-on alert( name ); // selects "John" – the first truthy value ``` - If both `currentUser` and `defaultUser` were falsy then `"unnamed"` would be the result. + If both `currentUser` and `defaultUser` were falsy, `"unnamed"` would be the result. 2. **Short-circuit evaluation.** - Operands can be not only values, but arbitrary expressions. OR evaluates and tests them from left to right. The evaluation stops when a truthy value is reached, and the value is returned. The process is called "a short-circuit evaluation", because it goes as short as possible from left to right. + Operands can be not only values, but arbitrary expressions. OR evaluates and tests them from left to right. The evaluation stops when a truthy value is reached, and the value is returned. This process is called "a short-circuit evaluation" because it goes as short as possible from left to right. - This is clearly seen when the expression given as the second argument has a side effect. Like a variable assignment. + This is clearly seen when the expression given as the second argument has a side effect like a variable assignment. - If we run the example below, `x` would not get assigned: + In the example below, `x` does not get assigned: ```js run no-beautify let x; @@ -133,7 +133,7 @@ That leads to some interesting usages compared to a "pure, classical, boolean-on alert(x); // undefined, because (x = 1) not evaluated ``` - ...And if the first argument is `false`, then `OR` goes on and evaluates the second one thus running the assignment: + If, instead, the first argument is `false`, `||` evaluates the second one, thus running the assignment: ```js run no-beautify let x; @@ -143,11 +143,11 @@ That leads to some interesting usages compared to a "pure, classical, boolean-on alert(x); // 1 ``` - An assignment is a simple case, other side effects can be involved. + An assignment is a simple case. There may be side effects, that won't show up if the evaluation doesn't reach them. - As we can see, such a use case is a "shorter way to do `if`". The first operand is converted to boolean and if it's false then the second one is evaluated. + As we can see, such a use case is a "shorter way of doing `if`". The first operand is converted to boolean. If it's false, the second one is evaluated. - Most of time it's better to use a "regular" `if` to keep the code easy to understand, but sometimes that can be handy. + Most of time, it's better to use a "regular" `if` to keep the code easy to understand, but sometimes this can be handy. ## && (AND) @@ -157,7 +157,7 @@ The AND operator is represented with two ampersands `&&`: result = a && b; ``` -In classical programming AND returns `true` if both operands are truthy and `false` otherwise: +In classical programming, AND returns `true` if both operands are truthy and `false` otherwise: ```js run alert( true && true ); // true @@ -173,11 +173,11 @@ let hour = 12; let minute = 30; if (hour == 12 && minute == 30) { - alert( 'Time is 12:30' ); + alert( 'The time is 12:30' ); } ``` -Just as for OR, any value is allowed as an operand of AND: +Just as with OR, any value is allowed as an operand of AND: ```js run if (1 && 0) { // evaluated as true && false @@ -186,7 +186,7 @@ if (1 && 0) { // evaluated as true && false ``` -## AND seeks the first falsy value +## AND finds the first falsy value Given multiple AND'ed values: @@ -196,9 +196,9 @@ result = value1 && value2 && value3; The AND `&&` operator does the following: -- Evaluate operands from left to right. -- For each operand, convert it to a boolean. If the result is `false`, stop and return the original value of that operand. -- If all other operands have been assessed (i.e. all were truthy), return the last operand. +- Evaluates operands from left to right. +- For each operand, converts it to a boolean. If the result is `false`, stops and returns the original value of that operand. +- If all operands have been evaluated (i.e. all were truthy), returns the last operand. In other words, AND returns the first falsy value or the last value if none were found. @@ -233,7 +233,7 @@ alert( 1 && 2 && 3 ); // 3, the last one ````smart header="Precedence of AND `&&` is higher than OR `||`" The precedence of AND `&&` operator is higher than OR `||`. -So the code `a && b || c && d` is essentially the same as if `&&` were in parentheses: `(a && b) || (c && d)`. +So the code `a && b || c && d` is essentially the same as if the `&&` expressions were in parentheses: `(a && b) || (c && d)`. ```` Just like OR, the AND `&&` operator can sometimes replace `if`. @@ -246,7 +246,7 @@ let x = 1; (x > 0) && alert( 'Greater than zero!' ); ``` -The action in the right part of `&&` would execute only if the evaluation reaches it. That is: only if `(x > 0)` is true. +The action in the right part of `&&` would execute only if the evaluation reaches it. That is, only if `(x > 0)` is true. So we basically have an analogue for: @@ -258,9 +258,9 @@ if (x > 0) { } ``` -The variant with `&&` appears to be shorter. But `if` is more obvious and tends to be a little bit more readable. +The variant with `&&` appears shorter. But `if` is more obvious and tends to be a little bit more readable. -So it is recommended to use every construct for its purpose. Use `if` if we want if. And use `&&` if we want AND. +So we recommend using every construct for its purpose: use `if` if we want if and use `&&` if we want AND. ## ! (NOT) @@ -275,7 +275,7 @@ result = !value; The operator accepts a single argument and does the following: 1. Converts the operand to boolean type: `true/false`. -2. Returns an inverse value. +2. Returns the inverse value. For instance: @@ -291,7 +291,7 @@ alert( !!"non-empty string" ); // true alert( !!null ); // false ``` -That is, the first NOT converts the value to boolean and returns the inverse, and the second NOT inverses it again. At the end we have a plain value-to-boolean conversion. +That is, the first NOT converts the value to boolean and returns the inverse, and the second NOT inverses it again. In the end, we have a plain value-to-boolean conversion. There's a little more verbose way to do the same thing -- a built-in `Boolean` function: @@ -300,4 +300,4 @@ alert( Boolean("non-empty string") ); // true alert( Boolean(null) ); // false ``` -The precedence of NOT `!` is the highest of all logical operators, so it always executes first, before any `&&`, `||`. +The precedence of NOT `!` is the highest of all logical operators, so it always executes first, before `&&` or `||`. diff --git a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md b/1-js/02-first-steps/12-while-for/7-list-primes/solution.md index ec9063173..9ff0663d7 100644 --- a/1-js/02-first-steps/12-while-for/7-list-primes/solution.md +++ b/1-js/02-first-steps/12-while-for/7-list-primes/solution.md @@ -26,4 +26,4 @@ for (let i = 2; i <= n; i++) { // for each i... } ``` -There's a lot of space to opimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc. +There's a lot of space to opimize it. For instance, we could look for the divisors from `2` to square root of `i`. But anyway, if we want to be really efficient for large intervals, we need to change the approach and rely on advanced maths and complex algorithms like [Quadratic sieve](https://en.wikipedia.org/wiki/Quadratic_sieve), [General number field sieve](https://en.wikipedia.org/wiki/General_number_field_sieve) etc. diff --git a/1-js/02-first-steps/12-while-for/article.md b/1-js/02-first-steps/12-while-for/article.md index e600b8c4b..c809581f5 100644 --- a/1-js/02-first-steps/12-while-for/article.md +++ b/1-js/02-first-steps/12-while-for/article.md @@ -1,10 +1,10 @@ # Loops: while and for -We often have a need to perform similar actions many times in a row. +We often need to repeat actions. -For example, when we need to output goods from a list one after another. Or just run the same code for each number from 1 to 10. +For example, outputting goods from a list one after another or just running the same code for each number from 1 to 10. -*Loops* are a way to repeat the same part of code multiple times. +*Loops* are a way to repeat the same code multiple times. ## The "while" loop @@ -31,11 +31,11 @@ while (i < 3) { // shows 0, then 1, then 2 A single execution of the loop body is called *an iteration*. The loop in the example above makes three iterations. -If there were no `i++` in the example above, the loop would repeat (in theory) forever. In practice, the browser provides ways to stop such loops, and for server-side JavaScript we can kill the process. +If `i++` was missing from the example above, the loop would repeat (in theory) forever. In practice, the browser provides ways to stop such loops, and in server-side JavaScript, we can kill the process. -Any expression or a variable can be a loop condition, not just a comparison. They are evaluated and converted to a boolean by `while`. +Any expression or variable can be a loop condition, not just comparisons: the condition is evaluated and converted to a boolean by `while`. -For instance, the shorter way to write `while (i != 0)` could be `while (i)`: +For instance, a shorter way to write `while (i != 0)` is `while (i)`: ```js run let i = 3; @@ -47,8 +47,8 @@ while (i) { // when i becomes 0, the condition becomes falsy, and the loop stops } ``` -````smart header="Brackets are not required for a single-line body" -If the loop body has a single statement, we can omit the brackets `{…}`: +````smart header="Curly braces are not required for a single-line body" +If the loop body has a single statement, we can omit the curly braces `{…}`: ```js run let i = 3; @@ -68,7 +68,7 @@ do { } while (condition); ``` -The loop will first execute the body, then check the condition and, while it's truthy, execute it again and again. +The loop will first execute the body, then check the condition, and, while it's truthy, execute it again and again. For example: @@ -80,11 +80,11 @@ do { } while (i < 3); ``` -This form of syntax is rarely used except when you want the body of the loop to execute **at least once** regardless of the condition being truthy. Usually, the other form is preferred: `while(…) {…}`. +This form of syntax should only be used when you want the body of the loop to execute **at least once** regardless of the condition being truthy. Usually, the other form is preferred: `while(…) {…}`. ## The "for" loop -The `for` loop is the most often used one. +The `for` loop is the most commonly used loop. It looks like this: @@ -102,14 +102,14 @@ for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2 } ``` -Let's examine the `for` statement part by part: +Let's examine the `for` statement part-by-part: | part | | | |-------|----------|----------------------------------------------------------------------------| | begin | `i = 0` | Executes once upon entering the loop. | -| condition | `i < 3`| Checked before every loop iteration, if fails the loop stops. | -| step| `i++` | Executes after the body on each iteration, but before the condition check. | -| body | `alert(i)`| Runs again and again while the condition is truthy | +| condition | `i < 3`| Checked before every loop iteration. If false, the loop stops. | +| step| `i++` | Executes after the body on each iteration but before the condition check. | +| body | `alert(i)`| Runs again and again while the condition is truthy. | The general loop algorithm works like this: @@ -121,9 +121,9 @@ Run begin → ... ``` -If you are new to loops, then maybe it would help if you go back to the example and reproduce how it runs step-by-step on a piece of paper. +If you are new to loops, it could help to go back to the example and reproduce how it runs step-by-step on a piece of paper. -Here's what exactly happens in our case: +Here's exactly what happens in our case: ```js // for (let i = 0; i < 3; i++) alert(i) @@ -140,7 +140,7 @@ if (i < 3) { alert(i); i++ } ``` ````smart header="Inline variable declaration" -Here the "counter" variable `i` is declared right in the loop. That's called an "inline" variable declaration. Such variables are visible only inside the loop. +Here, the "counter" variable `i` is declared right in the loop. This is called an "inline" variable declaration. Such variables are visible only inside the loop. ```js run for (*!*let*/!* i = 0; i < 3; i++) { @@ -149,7 +149,7 @@ for (*!*let*/!* i = 0; i < 3; i++) { alert(i); // error, no such variable ``` -Instead of defining a variable, we can use an existing one: +Instead of defining a variable, we could use an existing one: ```js run let i = 0; @@ -190,9 +190,9 @@ for (; i < 3;) { } ``` -The loop became identical to `while (i < 3)`. +This makes the loop identical to `while (i < 3)`. -We can actually remove everything, thus creating an infinite loop: +We can actually remove everything, creating an infinite loop: ```js for (;;) { @@ -200,15 +200,15 @@ for (;;) { } ``` -Please note that the two `for` semicolons `;` must be present, otherwise it would be a syntax error. +Please note that the two `for` semicolons `;` must be present. Otherwise, there would be a syntax error. ## Breaking the loop -Normally the loop exits when the condition becomes falsy. +Normally, a loop exits when its condition becomes falsy. -But we can force the exit at any moment. There's a special `break` directive for that. +But we can force the exit at any time using the special `break` directive. -For example, the loop below asks the user for a series of numbers, but "breaks" when no number is entered: +For example, the loop below asks the user for a series of numbers, "breaking" when no number is entered: ```js let sum = 0; @@ -227,15 +227,15 @@ while (true) { alert( 'Sum: ' + sum ); ``` -The `break` directive is activated at the line `(*)` if the user enters an empty line or cancels the input. It stops the loop immediately, passing the control to the first line after the loop. Namely, `alert`. +The `break` directive is activated at the line `(*)` if the user enters an empty line or cancels the input. It stops the loop immediately, passing control to the first line after the loop. Namely, `alert`. -The combination "infinite loop + `break` as needed" is great for situations when the condition must be checked not in the beginning/end of the loop, but in the middle, or even in several places of the body. +The combination "infinite loop + `break` as needed" is great for situations when a loop's condition must be checked not in the beginning or end of the loop, but in the middle or even in several places of its body. ## Continue to the next iteration [#continue] -The `continue` directive is a "lighter version" of `break`. It doesn't stop the whole loop. Instead it stops the current iteration and forces the loop to start a new one (if the condition allows). +The `continue` directive is a "lighter version" of `break`. It doesn't stop the whole loop. Instead, it stops the current iteration and forces the loop to start a new one (if the condition allows). -We can use it if we're done on the current iteration and would like to move on to the next. +We can use it if we're done with the current iteration and would like to move on to the next one. The loop below uses `continue` to output only odd values: @@ -249,9 +249,9 @@ for (let i = 0; i < 10; i++) { } ``` -For even values of `i` the `continue` directive stops body execution, passing the control to the next iteration of `for` (with the next number). So the `alert` is only called for odd values. +For even values of `i`, the `continue` directive stops executing the body and passes control to the next iteration of `for` (with the next number). So the `alert` is only called for odd values. -````smart header="The directive `continue` helps to decrease nesting level" +````smart header="The `continue` directive helps decrease nesting" A loop that shows odd values could look like this: ```js @@ -264,13 +264,13 @@ for (let i = 0; i < 10; i++) { } ``` -From a technical point of view it's identical to the example above. Surely, we can just wrap the code in the `if` block instead of `continue`. +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 we got one more nesting level (the `alert` call inside the curly braces). If the code inside `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 '?'" -Please note that syntax constructs that are not expressions cannot be used with the ternary operator `?`. In particular, directives such as `break/continue` are disallowed there. +Please note that syntax constructs that are not expressions cannot be used with the ternary operator `?`. In particular, directives such as `break/continue` aren't allowed there. For example, if we take this code: @@ -282,24 +282,24 @@ if (i > 5) { } ``` -...And rewrite it using a question mark: +...and rewrite it using a question mark: ```js no-beautify -(i > 5) ? alert(i) : *!*continue*/!*; // continue not allowed here +(i > 5) ? alert(i) : *!*continue*/!*; // continue isn't allowed here ``` -...Then it stops working. The code like this will give a syntax error: +...it stops working. Code like this will give a syntax error: -That's just another reason not to use a question mark operator `?` instead of `if`. +This is just another reason not to use the question mark operator `?` instead of `if`. ```` ## Labels for break/continue Sometimes we need to break out from multiple nested loops at once. -For example, in the code below we loop over `i` and `j` prompting for coordinates `(i, j)` from `(0,0)` to `(3,3)`: +For example, in the code below we loop over `i` and `j`, prompting for the coordinates `(i, j)` from `(0,0)` to `(3,3)`: ```js run no-beautify for (let i = 0; i < 3; i++) { @@ -318,7 +318,7 @@ alert('Done!'); 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. +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 @@ -327,9 +327,7 @@ labelName: for (...) { } ``` -The `break ` statement in the loop breaks out to the label. - -Like here: +The `break ` statement in the loop below breaks out to the label: ```js run no-beautify *!*outer:*/!* for (let i = 0; i < 3; i++) { @@ -347,7 +345,7 @@ Like here: alert('Done!'); ``` -In the code above `break outer` looks upwards for the label named `outer` and breaks out of that loop. +In the code above, `break outer` looks upwards for the label named `outer` and breaks out of that loop. So the control goes straight from `(*)` to `alert('Done!')`. @@ -358,10 +356,10 @@ outer: for (let i = 0; i < 3; i++) { ... } ``` -The `continue` directive can also be used with a label. In this case the execution jumps to the next iteration of the labeled loop. +The `continue` directive can also be used with a label. In this case, code execution jumps to the next iteration of the labeled loop. ````warn header="Labels are not a \"goto\"" -Labels do not allow us to jump into an arbitrary place of code. +Labels do not allow us to jump into an arbitrary place in the code. For example, it is impossible to do this: ```js @@ -370,7 +368,7 @@ break label; // jumps to label? No. label: for (...) ``` -The call to a `break/continue` is only possible from inside the loop, and the label must be somewhere upwards from the directive. +A call to `break/continue` is only possible from inside a loop and the label must be somewhere above the directive. ```` ## Summary @@ -383,6 +381,6 @@ We covered 3 types of loops: To make an "infinite" loop, usually the `while(true)` construct is used. Such a loop, just like any other, can be stopped with the `break` directive. -If we don't want to do anything on the current iteration and would like to forward to the next one, the `continue` directive does it. +If we don't want to do anything in the current iteration and would like to forward to the next one, we can use the `continue` directive. -`break/continue` support labels before the loop. A label is the only way for `break/continue` to escape the nesting and go to the outer loop. +`break/continue` support labels before the loop. A label is the only way for `break/continue` to escape a nested loop to go to an outer one. diff --git a/1-js/02-first-steps/13-switch/article.md b/1-js/02-first-steps/13-switch/article.md index ae1149230..258f24068 100644 --- a/1-js/02-first-steps/13-switch/article.md +++ b/1-js/02-first-steps/13-switch/article.md @@ -148,7 +148,7 @@ Let's emphasize that the equality check is always strict. The values must be of For example, let's consider the code: ```js run -let arg = prompt("Enter a value?") +let arg = prompt("Enter a value?"); switch (arg) { case '0': case '1': @@ -163,7 +163,7 @@ switch (arg) { alert( 'Never executes!' ); break; default: - alert( 'An unknown value' ) + alert( 'An unknown value' ); } ``` diff --git a/1-js/02-first-steps/14-function-basics/article.md b/1-js/02-first-steps/14-function-basics/article.md index dd395a112..ec34b744d 100644 --- a/1-js/02-first-steps/14-function-basics/article.md +++ b/1-js/02-first-steps/14-function-basics/article.md @@ -101,7 +101,7 @@ showMessage(); alert( userName ); // *!*Bob*/!*, the value was modified by the function ``` -The outer variable is only used if there's no local one. So an occasional modification may happen if we forget `let`. +The outer variable is only used if there's no local one. If a same-named variable is declared inside the function then it *shadows* the outer one. For instance, in the code below the function uses the local `userName`. The outer one is ignored: @@ -128,7 +128,7 @@ Variables declared outside of any function, such as the outer `userName` in the Global variables are visible from any function (unless shadowed by locals). -Usually, a function declares all variables specific to its task. Global variables only store project-level data, so when it's important that these variables are accesible from anywhere. Modern code has few or no globals. Most variables reside in their functions. +It's a good practice to minimize the use of global variables. Modern code has few or no globals. Most variables reside in their functions. Sometimes though, they can be useful to store project-level data. ``` ## Parameters @@ -205,7 +205,9 @@ function showMessage(from, text = anotherFunction()) { ``` ```smart header="Evaluation of default parameters" -In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. In the example above, `anotherFunctions()` is called everytime `someMessage()` is called without the `text` parameter. This is in contrast to some other languages like Python, where any default parameters are evaluated only once during the initial interpretation. + +In JavaScript, a default parameter is evaluated every time the function is called without the respective parameter. In the example above, `anotherFunction()` is called every time `showMessage()` is called without the `text` parameter. This is in contrast to some other languages like Python, where any default parameters are evaluated only once during the initial interpretation. + ``` @@ -372,15 +374,15 @@ A few examples of breaking this rule: - `getAge` -- would be bad if it shows an `alert` with the age (should only get). - `createForm` -- would be bad if it modifies the document, adding a form to it (should only create it and return). -- `checkPermission` -- would be bad if displays the `access granted/denied` message (should only perform the check and return the result). +- `checkPermission` -- would be bad if it displays the `access granted/denied` message (should only perform the check and return the result). -These examples assume common meanings of prefixes. What they mean for you is determined by you and your team. Maybe it's pretty normal for your code to behave differently. But you should have a firm understanding of what a prefix means, what a prefixed function can and cannot do. All same-prefixed functions should obey the rules. And the team should share the knowledge. +These examples assume common meanings of prefixes. You and your team are free to agree on other meanings, but usually they're not much different. In any case, you should have a firm understanding of what a prefix means, what a prefixed function can and cannot do. All same-prefixed functions should obey the rules. And the team should share the knowledge. ``` ```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](http://jquery.com) framework defines a function with `$`. The [Lodash](http://lodash.com/) library has its core function named `_`. These are exceptions. Generally functions names should be concise and descriptive. ``` diff --git a/1-js/02-first-steps/14-function-basics/function_basics.png b/1-js/02-first-steps/14-function-basics/function_basics.png index 5f3505811..f5e6f9418 100644 Binary files a/1-js/02-first-steps/14-function-basics/function_basics.png and b/1-js/02-first-steps/14-function-basics/function_basics.png differ diff --git a/1-js/02-first-steps/14-function-basics/function_basics@2x.png b/1-js/02-first-steps/14-function-basics/function_basics@2x.png index e2fe5bac0..c31b2636a 100644 Binary files a/1-js/02-first-steps/14-function-basics/function_basics@2x.png and b/1-js/02-first-steps/14-function-basics/function_basics@2x.png differ diff --git a/1-js/02-first-steps/15-function-expressions-arrows/article.md b/1-js/02-first-steps/15-function-expressions-arrows/article.md index b4ea19bac..9b63907d5 100644 --- a/1-js/02-first-steps/15-function-expressions-arrows/article.md +++ b/1-js/02-first-steps/15-function-expressions-arrows/article.md @@ -175,7 +175,7 @@ We can pass it between variables and run when we want. Let's formulate the key differences between Function Declarations and Expressions. -First, the syntax: how to see what is what in the code. +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. @@ -186,7 +186,7 @@ First, the syntax: how to see what is what in the code. } ``` - *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" `=`: - + ```js // Function Expression let sum = function(a, b) { @@ -202,7 +202,7 @@ Once the execution flow passes to the right side of the assignment `let sum = fu Function Declarations are different. -**A Function Declaration is usable in the whole script/code block.** +**A Function Declaration is usable in the whole script (or a code block, if it's inside a block).** In other words, when JavaScript *prepares* to run the script or a code block, it first looks for Function Declarations in it and creates the functions. We can think of it as an "initialization stage". diff --git a/1-js/02-first-steps/16-javascript-specials/article.md b/1-js/02-first-steps/16-javascript-specials/article.md index 525e72d4c..b1aefd1d4 100644 --- a/1-js/02-first-steps/16-javascript-specials/article.md +++ b/1-js/02-first-steps/16-javascript-specials/article.md @@ -102,8 +102,8 @@ 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) -: Ask a `question`, and return either what the visitor entered or `null` if they pressed "cancel". +[`prompt(question, [default])`](mdn: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) : Ask a `question` and suggest to choose between Ok and Cancel. The choice is returned as `true/false`. @@ -149,7 +149,7 @@ Ternary : The only operator with three parameters: `cond ? resultA : resultB`. If `cond` is truthy, returns `resultA`, otherwise `resultB`. Logical operators -: Logical AND `&&` and OR `||` perform short-circuit evaluation and then return the value where it stopped. +: Logical AND `&&` and OR `||` perform short-circuit evaluation and then return the value where it stopped. Logical NOT `!` converts the operand to boolean type and returns the inverse value. Comparisons : Equality check `==` for values of different types converts them to a number (except `null` and `undefined` that equal each other and nothing else), so these are equal: @@ -161,13 +161,13 @@ Comparisons Other comparisons convert to a number as well. - The strict equality operator `===` doesn't do the conversion: different types always mean different values for it, so: + The strict equality operator `===` doesn't do the conversion: different types always mean different values for it. Values `null` and `undefined` are special: they equal `==` each other and don't equal anything else. Greater/less comparisons compare strings character-by-character, other types are converted to a number. -Logical operators +Other operators : There are few others, like a comma operator. More in: , , . 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 d09493a2e..2ca0d7003 100644 --- a/1-js/03-code-quality/01-debugging-chrome/article.md +++ b/1-js/03-code-quality/01-debugging-chrome/article.md @@ -20,7 +20,7 @@ Here's what you should see if you are doing it for the first time: The toggler button opens the tab with files. -Let's click it and select `index.html` and then `hello.js` in the tree view. Here's what should show up: +Let's click it and select `hello.js` in the tree view. Here's what should show up: ![](chrome-tabs.png) @@ -34,7 +34,7 @@ Now you could click the same toggler is "on") +3. An error (if dev tools are open and the button is "on"). Then we can examine variables and step on to see where the execution goes wrong. diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.png b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.png index efa3c19df..abc59905a 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources@2x.png b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources@2x.png index e184bdd01..546615e30 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources@2x.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-open-sources@2x.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.png b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.png index 2fe449c9b..caf60ebb6 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint@2x.png b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint@2x.png index e4abc89d1..3f628b6ff 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint@2x.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-breakpoint@2x.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.png b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.png index 98b22e777..0fc22b2ea 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console@2x.png b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console@2x.png index 3269a80f0..f01a7d101 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console@2x.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-console@2x.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.png b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.png index 719293d2e..424ca26b0 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause@2x.png b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause@2x.png index 5c22ab361..04cc849d1 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause@2x.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-pause@2x.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.png b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.png index 1848ccfac..00507833a 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1@2x.png b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1@2x.png index fcabf722e..d2a38bf0c 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1@2x.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-sources-debugger-trace-1@2x.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.png b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.png index ff91c531f..df9e13f3f 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs.png differ diff --git a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs@2x.png b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs@2x.png index 09b10bf48..5793bd059 100644 Binary files a/1-js/03-code-quality/01-debugging-chrome/chrome-tabs@2x.png and b/1-js/03-code-quality/01-debugging-chrome/chrome-tabs@2x.png differ diff --git a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md index 810fdad53..764e36c63 100644 --- a/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md +++ b/1-js/03-code-quality/02-coding-style/1-style-errors/solution.md @@ -4,22 +4,22 @@ You could note the following: ```js no-beautify function pow(x,n) // <- no space between arguments { // <- figure bracket on a separate line - let result=1; // <- no spaces to the both sides of = + let result=1; // <- no spaces before or after = for(let i=0;i diff --git a/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js index 10a032d03..e5ce2ce43 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-4.view/test.js @@ -1,6 +1,6 @@ describe("pow", function() { - describe("raises x to power n", function() { + describe("raises x to power 3", function() { function makeTest(x) { let expected = x * x * x; diff --git a/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js index a5a345979..75ff5e99f 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-full.view/test.js @@ -1,6 +1,6 @@ describe("pow", function() { - describe("raises x to power n", function() { + describe("raises x to power 3", function() { function makeTest(x) { let expected = x * x * x; diff --git a/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js b/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js index a5a345979..75ff5e99f 100644 --- a/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js +++ b/1-js/03-code-quality/05-testing-mocha/pow-nan.view/test.js @@ -1,6 +1,6 @@ describe("pow", function() { - describe("raises x to power n", function() { + describe("raises x to power 3", function() { function makeTest(x) { let expected = x * x * x; diff --git a/1-js/03-code-quality/06-polyfills/article.md b/1-js/03-code-quality/06-polyfills/article.md index 907730fdc..f4df5998c 100644 --- a/1-js/03-code-quality/06-polyfills/article.md +++ b/1-js/03-code-quality/06-polyfills/article.md @@ -19,21 +19,20 @@ Here Babel comes to the rescue. Actually, there are two parts in Babel: -1. First, the transpiler program, which rewrites the code. The developer runs it on their own computer. It rewrites the code into the older standard. And then the code is delivered to the website for users. Modern project build system like [webpack](http://webpack.github.io/) or [brunch](http://brunch.io/) provide means to run transpiler automatically on every code change, so that doesn't involve any time loss from our side. +1. First, the transpiler program, which rewrites the code. The developer runs it on their own computer. It rewrites the code into the older standard. And then the code is delivered to the website for users. Modern project build system like [webpack](http://webpack.github.io/) provide means to run transpiler automatically on every code change, so that very easy to integrate into development process. 2. Second, the polyfill. - The transpiler rewrites the code, so syntax features are covered. But for new functions we need to write a special script that implements them. JavaScript is a highly dynamic language, scripts may not just add new functions, but also modify built-in ones, so that they behave according to the modern standard. + New language features may include new built-in functions and syntax constructs. + The transpiler rewrites the code, transforming syntax constructs into older ones. But as for new built-in functions, we need to implement them. JavaScript is a highly dynamic language, scripts may add/modify any functions, so that they behave according to the modern standard. - There's a term "polyfill" for scripts that "fill in" the gap and add missing implementations. + A script that updates/adds new functions is called "polyfill". It "fills in" the gap and adds missing implementations. Two interesting polyfills are: - [babel polyfill](https://babeljs.io/docs/usage/polyfill/) that supports a lot, but is big. - [polyfill.io](http://polyfill.io) service that allows to load/construct polyfills on-demand, depending on the features we need. -So, we need to setup the transpiler and add the polyfill for old engines to support modern features. - -If we orient towards modern engines and do not use features except those supported everywhere, then we don't need to use Babel. +So, if we're going to use modern language features, a transpiler and a polyfill are necessary. ## Examples in the tutorial @@ -49,9 +48,7 @@ Examples that use modern JS will work only if your browser supports it. ```` ```offline -As you're reading the offline version, examples are not runnable. But they usually work :) +As you're reading the offline version, in PDF examples are not runnable. In EPUB some of them can run. ``` -[Chrome Canary](https://www.google.com/chrome/browser/canary.html) is good for all examples, but other modern browsers are mostly fine too. - -Note that on production we can use Babel to translate the code into suitable for less recent browsers, so there will be no such limitation, the code will run everywhere. +Google Chrome is usually the most up-to-date with language features, good to run bleeding-edge demos without any transpilers, but other modern browsers also work fine. diff --git a/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js b/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js index e7f63284f..db3283e49 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js +++ b/1-js/04-object-basics/01-object/3-is-empty/_js.view/solution.js @@ -1,7 +1,7 @@ function isEmpty(obj) { for (let key in obj) { - // if the loop has started, there is a prorty + // if the loop has started, there is a property return false; } return true; -} \ No newline at end of file +} diff --git a/1-js/04-object-basics/01-object/3-is-empty/solution.md b/1-js/04-object-basics/01-object/3-is-empty/solution.md index b3f40e3d2..b876973b5 100644 --- a/1-js/04-object-basics/01-object/3-is-empty/solution.md +++ b/1-js/04-object-basics/01-object/3-is-empty/solution.md @@ -1,10 +1 @@ Just loop over the object and `return false` immediately if there's at least one property. - -```js -function isEmpty(obj) { - for (let key in obj) { - return false; - } - return true; -} -``` diff --git a/1-js/04-object-basics/01-object/article.md b/1-js/04-object-basics/01-object/article.md index 806eac44d..980670439 100644 --- a/1-js/04-object-basics/01-object/article.md +++ b/1-js/04-object-basics/01-object/article.md @@ -220,7 +220,7 @@ alert(obj.__proto__); // [object Object], didn't work as intended As we see from the code, the assignment to a primitive `5` is ignored. -That can become a source of bugs and even vulnerabilies if we intend to store arbitrary key-value pairs in an object, and allow a visitor to specify the keys. +That can become a source of bugs and even vulnerabilities if we intend to store arbitrary key-value pairs in an object, and allow a visitor to specify the keys. In that case the visitor may choose "__proto__" as the key, and the assignment logic will be ruined (as shown above). @@ -339,7 +339,7 @@ To walk over all keys of an object, there exists a special form of the loop: `fo The syntax: ```js -for(key in object) { +for (key in object) { // executes the body for each key among object properties } ``` @@ -353,7 +353,7 @@ let user = { isAdmin: true }; -for(let key in user) { +for (let key in user) { // keys alert( key ); // name, age, isAdmin // values for the keys @@ -363,7 +363,7 @@ for(let key in user) { Note that all "for" constructs allow us to declare the looping variable inside the loop, like `let key` here. -Also, we could use another variable name here instead of `key`. For instance, `"for(let prop in obj)"` is also widely used. +Also, we could use another variable name here instead of `key`. For instance, `"for (let prop in obj)"` is also widely used. ### Ordered like an object @@ -384,7 +384,7 @@ let codes = { }; *!* -for(let code in codes) { +for (let code in codes) { alert(code); // 1, 41, 44, 49 } */!* @@ -442,7 +442,7 @@ let codes = { "+1": "USA" }; -for(let code in codes) { +for (let code in codes) { alert( +code ); // 49, 41, 44, 1 } ``` @@ -616,7 +616,7 @@ Also we can use the method [Object.assign](mdn:js/Object/assign) for that. The syntax is: ```js -Object.assign(dest[, src1, src2, src3...]) +Object.assign(dest, [src1, src2, src3...]) ``` - Arguments `dest`, and `src1, ..., srcN` (can be as many as needed) are objects. @@ -720,7 +720,7 @@ To access a property, we can use: Additional operators: - To delete a property: `delete obj.prop`. - To check if a property with the given key exists: `"key" in obj`. -- To iterate over an object: `for(let key in obj)` loop. +- To iterate over an object: `for (let key in obj)` loop. Objects are assigned and copied by reference. In other words, a variable stores not the "object value", but a "reference" (address in memory) for the value. So copying such a variable or passing it as a function argument copies that reference, not the object. All operations via copied references (like adding/removing properties) are performed on the same single object. diff --git a/1-js/04-object-basics/01-object/object-user-delete.png b/1-js/04-object-basics/01-object/object-user-delete.png index 688158f9b..8702675c8 100644 Binary files a/1-js/04-object-basics/01-object/object-user-delete.png and b/1-js/04-object-basics/01-object/object-user-delete.png differ diff --git a/1-js/04-object-basics/01-object/object-user-delete@2x.png b/1-js/04-object-basics/01-object/object-user-delete@2x.png index e1ef65541..698766bb0 100644 Binary files a/1-js/04-object-basics/01-object/object-user-delete@2x.png and b/1-js/04-object-basics/01-object/object-user-delete@2x.png differ diff --git a/1-js/04-object-basics/01-object/object-user-empty.png b/1-js/04-object-basics/01-object/object-user-empty.png index 80fdc0d3b..6b1f27a69 100644 Binary files a/1-js/04-object-basics/01-object/object-user-empty.png and b/1-js/04-object-basics/01-object/object-user-empty.png differ diff --git a/1-js/04-object-basics/01-object/object-user-empty@2x.png b/1-js/04-object-basics/01-object/object-user-empty@2x.png index 8db894cb3..5f261eca4 100644 Binary files a/1-js/04-object-basics/01-object/object-user-empty@2x.png and b/1-js/04-object-basics/01-object/object-user-empty@2x.png differ diff --git a/1-js/04-object-basics/01-object/object-user-isadmin.png b/1-js/04-object-basics/01-object/object-user-isadmin.png index 4e76eeb76..2ce66a49d 100644 Binary files a/1-js/04-object-basics/01-object/object-user-isadmin.png and b/1-js/04-object-basics/01-object/object-user-isadmin.png differ diff --git a/1-js/04-object-basics/01-object/object-user-isadmin@2x.png b/1-js/04-object-basics/01-object/object-user-isadmin@2x.png index b40977690..4a15dac64 100644 Binary files a/1-js/04-object-basics/01-object/object-user-isadmin@2x.png and b/1-js/04-object-basics/01-object/object-user-isadmin@2x.png differ diff --git a/1-js/04-object-basics/01-object/object-user-props.png b/1-js/04-object-basics/01-object/object-user-props.png index 2bfdfabdb..b0486e900 100644 Binary files a/1-js/04-object-basics/01-object/object-user-props.png and b/1-js/04-object-basics/01-object/object-user-props.png differ diff --git a/1-js/04-object-basics/01-object/object-user-props@2x.png b/1-js/04-object-basics/01-object/object-user-props@2x.png index 4935b59ce..20859fe91 100644 Binary files a/1-js/04-object-basics/01-object/object-user-props@2x.png and b/1-js/04-object-basics/01-object/object-user-props@2x.png differ diff --git a/1-js/04-object-basics/01-object/object-user.png b/1-js/04-object-basics/01-object/object-user.png index 16179209f..6215b8207 100644 Binary files a/1-js/04-object-basics/01-object/object-user.png and b/1-js/04-object-basics/01-object/object-user.png differ diff --git a/1-js/04-object-basics/01-object/object-user@2x.png b/1-js/04-object-basics/01-object/object-user@2x.png index 720389532..c66fa5159 100644 Binary files a/1-js/04-object-basics/01-object/object-user@2x.png and b/1-js/04-object-basics/01-object/object-user@2x.png differ diff --git a/1-js/04-object-basics/01-object/object.png b/1-js/04-object-basics/01-object/object.png index f94d094a9..a853c9c39 100644 Binary files a/1-js/04-object-basics/01-object/object.png and b/1-js/04-object-basics/01-object/object.png differ diff --git a/1-js/04-object-basics/01-object/object@2x.png b/1-js/04-object-basics/01-object/object@2x.png index 003c2f6ea..12011ff5c 100644 Binary files a/1-js/04-object-basics/01-object/object@2x.png and b/1-js/04-object-basics/01-object/object@2x.png differ diff --git a/1-js/04-object-basics/01-object/variable-contains-reference.png b/1-js/04-object-basics/01-object/variable-contains-reference.png index d6e7fddff..cdd53d0b2 100644 Binary files a/1-js/04-object-basics/01-object/variable-contains-reference.png and b/1-js/04-object-basics/01-object/variable-contains-reference.png differ diff --git a/1-js/04-object-basics/01-object/variable-contains-reference@2x.png b/1-js/04-object-basics/01-object/variable-contains-reference@2x.png index 145bad29a..070126198 100644 Binary files a/1-js/04-object-basics/01-object/variable-contains-reference@2x.png and b/1-js/04-object-basics/01-object/variable-contains-reference@2x.png differ diff --git a/1-js/04-object-basics/01-object/variable-copy-reference.png b/1-js/04-object-basics/01-object/variable-copy-reference.png index 97510c4b2..287085842 100644 Binary files a/1-js/04-object-basics/01-object/variable-copy-reference.png and b/1-js/04-object-basics/01-object/variable-copy-reference.png differ diff --git a/1-js/04-object-basics/01-object/variable-copy-reference@2x.png b/1-js/04-object-basics/01-object/variable-copy-reference@2x.png index a64238a52..e7b994c53 100644 Binary files a/1-js/04-object-basics/01-object/variable-copy-reference@2x.png and b/1-js/04-object-basics/01-object/variable-copy-reference@2x.png differ diff --git a/1-js/04-object-basics/01-object/variable-copy-value.png b/1-js/04-object-basics/01-object/variable-copy-value.png index e21af0990..c360a0e13 100644 Binary files a/1-js/04-object-basics/01-object/variable-copy-value.png and b/1-js/04-object-basics/01-object/variable-copy-value.png differ diff --git a/1-js/04-object-basics/01-object/variable-copy-value@2x.png b/1-js/04-object-basics/01-object/variable-copy-value@2x.png index 2f0b2f47d..323bc4622 100644 Binary files a/1-js/04-object-basics/01-object/variable-copy-value@2x.png and b/1-js/04-object-basics/01-object/variable-copy-value@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/article.md b/1-js/04-object-basics/02-garbage-collection/article.md index 27682ef40..42d35e7b8 100644 --- a/1-js/04-object-basics/02-garbage-collection/article.md +++ b/1-js/04-object-basics/02-garbage-collection/article.md @@ -207,6 +207,6 @@ A general book "The Garbage Collection Handbook: The Art of Automatic Memory Man 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). -[V8 blog](http://v8project.blogspot.com/) 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. +[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. 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/02-garbage-collection/family-delete-refs.png b/1-js/04-object-basics/02-garbage-collection/family-delete-refs.png index 5c10f0e47..05160890e 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family-delete-refs.png and b/1-js/04-object-basics/02-garbage-collection/family-delete-refs.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-delete-refs@2x.png b/1-js/04-object-basics/02-garbage-collection/family-delete-refs@2x.png index 24d29630e..a92fc573b 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family-delete-refs@2x.png and b/1-js/04-object-basics/02-garbage-collection/family-delete-refs@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-family.png b/1-js/04-object-basics/02-garbage-collection/family-no-family.png index a4ce30a35..e60f9b88d 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-family.png and b/1-js/04-object-basics/02-garbage-collection/family-no-family.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-family@2x.png b/1-js/04-object-basics/02-garbage-collection/family-no-family@2x.png index 0d9949839..754903706 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-family@2x.png and b/1-js/04-object-basics/02-garbage-collection/family-no-family@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father-2.png b/1-js/04-object-basics/02-garbage-collection/family-no-father-2.png index e24dba5b5..823d0a618 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-father-2.png and b/1-js/04-object-basics/02-garbage-collection/family-no-father-2.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father-2@2x.png b/1-js/04-object-basics/02-garbage-collection/family-no-father-2@2x.png index a6c4ee36a..9e100d522 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-father-2@2x.png and b/1-js/04-object-basics/02-garbage-collection/family-no-father-2@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father.png b/1-js/04-object-basics/02-garbage-collection/family-no-father.png index df14624bc..5ddb6f68f 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-father.png and b/1-js/04-object-basics/02-garbage-collection/family-no-father.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family-no-father@2x.png b/1-js/04-object-basics/02-garbage-collection/family-no-father@2x.png index 5ab4b3792..868c748a0 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family-no-father@2x.png and b/1-js/04-object-basics/02-garbage-collection/family-no-father@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family.png b/1-js/04-object-basics/02-garbage-collection/family.png index dbbc01d2f..f37464e6c 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family.png and b/1-js/04-object-basics/02-garbage-collection/family.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/family@2x.png b/1-js/04-object-basics/02-garbage-collection/family@2x.png index 64b4619ba..4f5d47210 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/family@2x.png and b/1-js/04-object-basics/02-garbage-collection/family@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.png index 423191778..5cfe664c3 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1@2x.png index 223ea32a1..cf93a1605 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-1@2x.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-1@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.png index da63d3969..2bbe4241d 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2@2x.png index 1f614e3e6..f4a7abb52 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-2@2x.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-2@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.png index e77144c1d..665a22784 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3@2x.png index 37e349b62..60d4059cc 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-3@2x.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-3@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.png index 110e0d9c4..4ba6e17ec 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4@2x.png index c09d75f9d..8ac09c80d 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-4@2x.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-4@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.png index bc4ea9670..35c8816dc 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5@2x.png b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5@2x.png index 0ab697e68..4db0c1732 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/garbage-collection-5@2x.png and b/1-js/04-object-basics/02-garbage-collection/garbage-collection-5@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.png index 29c4fcbea..9ddac3e29 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.png and b/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin@2x.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin@2x.png index 2f80f19a2..9069781f2 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin@2x.png and b/1-js/04-object-basics/02-garbage-collection/memory-user-john-admin@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.png index cdc1d4904..ae1684b2d 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.png and b/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost@2x.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost@2x.png index d58afdb58..c510380f9 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost@2x.png and b/1-js/04-object-basics/02-garbage-collection/memory-user-john-lost@2x.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john.png index 3ba5730de..2ad00b6cb 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john.png and b/1-js/04-object-basics/02-garbage-collection/memory-user-john.png differ diff --git a/1-js/04-object-basics/02-garbage-collection/memory-user-john@2x.png b/1-js/04-object-basics/02-garbage-collection/memory-user-john@2x.png index 5aa81bb0c..f365ac036 100644 Binary files a/1-js/04-object-basics/02-garbage-collection/memory-user-john@2x.png and b/1-js/04-object-basics/02-garbage-collection/memory-user-john@2x.png differ diff --git a/1-js/04-object-basics/03-symbol/article.md b/1-js/04-object-basics/03-symbol/article.md index b43367623..8323d6643 100644 --- a/1-js/04-object-basics/03-symbol/article.md +++ b/1-js/04-object-basics/03-symbol/article.md @@ -18,7 +18,7 @@ let id = Symbol(); We can also give symbol a description (also called a symbol name), mostly useful for debugging purposes: -```js +```js run // id is a symbol with the description "id" let id = Symbol("id"); ``` @@ -50,6 +50,8 @@ alert(id); // TypeError: Cannot convert a Symbol value to a string */!* ``` +That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not occasionally convert one into another. + If we really want to show a symbol, we need to call `.toString()` on it, like here: ```js run let id = Symbol("id"); @@ -58,7 +60,14 @@ alert(id.toString()); // Symbol(id), now it works */!* ``` -That's a "language guard" against messing up, because strings and symbols are fundamentally different and should not occasionally convert one into another. +Or get `symbol.description` property to get the description only: +```js run +let id = Symbol("id"); +*!* +alert(id.description); // id +*/!* +``` + ```` ## "Hidden" properties diff --git a/1-js/04-object-basics/04-object-methods/7-calculator/solution.md b/1-js/04-object-basics/04-object-methods/7-calculator/solution.md index 22c4bf187..459997624 100644 --- a/1-js/04-object-basics/04-object-methods/7-calculator/solution.md +++ b/1-js/04-object-basics/04-object-methods/7-calculator/solution.md @@ -1,6 +1,5 @@ - -```js run demo +```js run demo solution let calculator = { sum() { return this.a + this.b; @@ -20,4 +19,3 @@ calculator.read(); alert( calculator.sum() ); alert( calculator.mul() ); ``` - 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 41edd7236..2b47873fc 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 @@ -1,6 +1,6 @@ The solution is to return the object itself from every call. -```js run +```js run demo let ladder = { step: 0, up() { @@ -28,7 +28,7 @@ ladder.up().up().down().up().down().showStep(); // 1 We also can write a single call per line. For long chains it's more readable: -```js +```js ladder .up() .up() @@ -37,4 +37,3 @@ ladder .down() .showStep(); // 1 ``` - 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 a989846f5..eca9f4e92 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 @@ -30,7 +30,7 @@ ladder.down(); ladder.showStep(); // 1 ``` -Modify the code of `up` and `down` 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 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 35055cc5c..9e38fae00 100644 --- a/1-js/04-object-basics/04-object-methods/article.md +++ b/1-js/04-object-basics/04-object-methods/article.md @@ -63,7 +63,7 @@ user.sayHi(); // Hello! ```smart header="Object-oriented programming" When we write our code using objects to represent entities, that's called an [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), in short: "OOP". -OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture, and there are great books on that topic, like "Design Patterns: Elements of Reusable Object-Oriented Software" by E.Gamma, R.Helm, R.Johnson, J.Vissides or "Object-Oriented Analysis and Design with Applications" by G.Booch, and more. We'll scratch the surface of that topic later in the chapter . +OOP is a big thing, an interesting science of its own. How to choose the right entities? How to organize the interaction between them? That's architecture, and there are great books on that topic, like "Design Patterns: Elements of Reusable Object-Oriented Software" by E.Gamma, R.Helm, R.Johnson, J.Vissides or "Object-Oriented Analysis and Design with Applications" by G.Booch, and more. ``` ### Method shorthand @@ -72,14 +72,14 @@ There exists a shorter syntax for methods in an object literal: ```js // these objects do the same -let user = { +user = { sayHi: function() { alert("Hello"); } }; // method shorthand looks better, right? -let user = { +user = { *!* sayHi() { // same as "sayHi: function()" */!* @@ -166,7 +166,7 @@ If we used `this.name` instead of `user.name` inside the `alert`, then the code ## "this" is not bound -In JavaScript, "this" keyword behaves unlike most other programming languages. First, it can be used in any function. +In JavaScript, "this" keyword behaves unlike most other programming languages. It can be used in any function. There's no syntax error in the code like that: @@ -176,9 +176,9 @@ function sayHi() { } ``` -The value of `this` is evaluated during the run-time. And it can be anything. +The value of `this` is evaluated during the run-time, depending on the context. And it can be anything. -For instance, the same function may have different "this" when called from different objects: +For instance, here the same function is assigned to two different objects and has different "this" in the calls: ```js run let user = { name: "John" }; @@ -189,7 +189,7 @@ function sayHi() { } *!* -// use the same functions in two objects +// use the same function in two objects user.f = sayHi; admin.f = sayHi; */!* @@ -202,7 +202,10 @@ admin.f(); // Admin (this == admin) admin['f'](); // Admin (dot or square brackets access the method – doesn't matter) ``` -Actually, we can call the function without an object at all: +The rule is simple: if `obj.f()` is called, then `this` is `obj` during the call of `f`. So it's either `user` or `admin` in the example above. + +````smart header="Calling without an object: `this == undefined`" +We can even call the function without an object at all: ```js run function sayHi() { @@ -214,9 +217,10 @@ sayHi(); // undefined In this case `this` is `undefined` in strict mode. If we try to access `this.name`, there will be an error. -In non-strict mode (if one forgets `use strict`) the value of `this` in such case will be the *global object* (`window` in a browser, we'll get to it later). This is a historical behavior that `"use strict"` fixes. +In non-strict mode the value of `this` in such case will be the *global object* (`window` in a browser, we'll get to it later in the chapter [](info:global-object)). This is a historical behavior that `"use strict"` fixes. -Please note that usually a call of a function that uses `this` without an object is not normal, but rather a programming mistake. If a function has `this`, then it is usually meant to be called in the context of an object. +Usually such call is an programming error. If there's `this` inside a function, it expects to be called in an object context. +```` ```smart header="The consequences of unbound `this`" If you come from another programming language, then you are probably used to the idea of a "bound `this`", where methods defined in an object always have `this` referencing that object. @@ -257,7 +261,7 @@ On the last line there is a ternary operator that chooses either `user.hi` or `u The method is immediately called with parentheses `()`. But it doesn't work right! -You can see that the call results in an error, cause the value of `"this"` inside the call becomes `undefined`. +You can see that the call results in an error, because the value of `"this"` inside the call becomes `undefined`. This works (object dot method): ```js diff --git a/1-js/04-object-basics/05-object-toprimitive/article.md b/1-js/04-object-basics/05-object-toprimitive/article.md index a44cf4f4d..a7270aa66 100644 --- a/1-js/04-object-basics/05-object-toprimitive/article.md +++ b/1-js/04-object-basics/05-object-toprimitive/article.md @@ -3,28 +3,24 @@ What happens when objects are added `obj1 + obj2`, subtracted `obj1 - obj2` or printed using `alert(obj)`? -There are special methods in objects that do the conversion. +In that case objects are auto-converted to primitives, and then the operation is carried out. -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 close it. +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. -For objects, there's no to-boolean conversion, because all objects are `true` in a boolean context. So there are only string and numeric conversions. - -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. - -As for the string conversion -- it usually happens when we output an object like `alert(obj)` and in similar contexts. +1. All objects are `true` in a boolean context. There are 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. ## ToPrimitive -When an object is used in the context where a primitive is required, for instance, in an `alert` or mathematical operations, it's converted to a primitive value using the `ToPrimitive` algorithm ([specification](https://tc39.github.io/ecma262/#sec-toprimitive)). - -That algorithm allows us to customize the conversion using a special object method. +We can fine-tune string and numeric conversion, using special object methods. -Depending on the context, the conversion has a so-called "hint". +The conversion algorithm is called `ToPrimitive` in the [specification](https://tc39.github.io/ecma262/#sec-toprimitive). It's called with a "hint" that specifies the conversion type. There are three variants: `"string"` -: When an operation expects a string, for object-to-string conversions, like `alert`: +: For an object-to-string conversion, when we're doing an operation on an object that expects a string, like `alert`: ```js // output @@ -35,7 +31,7 @@ There are three variants: ``` `"number"` -: When an operation expects a number, for object-to-number conversions, like maths: +: For an object-to-number conversion, like when we're doing maths: ```js // explicit conversion @@ -52,7 +48,7 @@ There are three variants: `"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. Or when an object is compared using `==` with a string, number or a symbol. + For instance, binary plus `+` can work both with strings (concatenates them) and numbers (adds them), so both strings and numbers would do. Or when an object is compared using `==` with a string, number or a symbol, it's also unclear which conversion should be done. ```js // binary plus @@ -159,14 +155,21 @@ alert(user + 500); // toString -> John500 In the absence of `Symbol.toPrimitive` and `valueOf`, `toString` will handle all primitive conversions. - -## ToPrimitive and ToString/ToNumber +## Return types 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". -**The only mandatory thing: these methods must return a primitive.** +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. +``` + +## Further operations An operation that initiated the conversion gets that primitive, and then continues to work with it, applying further conversions if necessary. @@ -208,11 +211,6 @@ For instance: alert(obj + 2); // 3 (ToPrimitive returned boolean, not string => ToNumber) ``` -```smart header="Historical notes" -For historical reasons, methods `toString` or `valueOf` *should* return a primitive: if any of them returns an object, then there's no error, but that object is ignored (like if the method didn't exist). - -In contrast, `Symbol.toPrimitive` *must* return a primitive, otherwise, there will be an error. -``` ## Summary diff --git a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md index e5583c5d0..86bb65416 100644 --- a/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md +++ b/1-js/04-object-basics/06-constructor-new/2-calculator-constructor/solution.md @@ -1,5 +1,3 @@ - - ```js run demo function Calculator() { 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 5fe6e5d90..4a7922a70 100644 --- a/1-js/04-object-basics/06-constructor-new/article.md +++ b/1-js/04-object-basics/06-constructor-new/article.md @@ -83,7 +83,7 @@ let user = new function() { The constructor can't be called again, because it is not saved anywhere, just created and called. So this trick aims to encapsulate the code that constructs the single object, without future reuse. ```` -## Dual-syntax constructors: new.target +## Constructor mode test: new.target ```smart header="Advanced stuff" The syntax from this section is rarely used, skip it unless you want to know everything. @@ -109,7 +109,9 @@ new User(); // function User { ... } */!* ``` -That can be used to allow both `new` and regular calls to work the same. That is, create the same object: +That can be used inside the function to know whether it was called with `new`, "in constructor mode", or without it, "in regular mode". + +We can also make both `new` and regular calls to do the same, like this: ```js run function User(name) { @@ -225,5 +227,5 @@ JavaScript provides constructor functions for many built-in language objects: li ```smart header="Objects, we'll be back!" In this chapter we only cover the basics about objects and constructors. They are essential for learning more about data types and functions in the next chapters. -After we learn that, in the chapter we return to objects and cover them in-depth, including inheritance and classes. +After we learn that, we return to objects and cover them in-depth in the chapters and . ``` diff --git a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md index a169f7769..13b71b2fe 100644 --- a/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md +++ b/1-js/05-data-types/01-primitives-methods/1-string-new-property/solution.md @@ -6,26 +6,21 @@ let str = "Hello"; str.test = 5; // (*) -alert(str.test); +alert(str.test); ``` -There may be two kinds of result: -1. `undefined` -2. An error. +Depending on whether you have `use strict` or not, the result may be: +1. `undefined` (no strict mode) +2. An error (strict mode). Why? Let's replay what's happening at line `(*)`: 1. When a property of `str` is accessed, a "wrapper object" is created. -2. The operation with the property is carried out on it. So, the object gets the `test` property. -3. The operation finishes and the "wrapper object" disappears. +2. In strict mode, writing into it is an error. +3. Otherwise, the operation with the property is carried on, the object gets the `test` property, but after that the "wrapper object" disappears. -So, on the last line, `str` has no trace of the property. A new wrapper object for every object operation on a string. - -Some browsers though may decide to further limit the programmer and disallow to assign properties to primitives at all. That's why in practice we can also see errors at line `(*)`. It's a little bit farther from the specification though. +So, without strict mode, in the last line `str` has no trace of the property. **This example clearly shows that primitives are not objects.** -They just can not store data. - -All property/method operations are performed with the help of temporary objects. - +They can't store additional data. 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 a40fdf553..bc94ef994 100644 --- a/1-js/05-data-types/01-primitives-methods/article.md +++ b/1-js/05-data-types/01-primitives-methods/article.md @@ -14,7 +14,7 @@ A primitive An object - Is capable of storing multiple values as properties. -- Can be created with `{}`, for instance: `{name: "John", age: 30}`. There are other kinds of objects in JavaScript; functions, for example, are objects. +- Can be created with `{}`, for instance: `{name: "John", age: 30}`. There are other kinds of objects in JavaScript: functions, for example, are objects. One of the best things about objects is that we can store a function as one of its properties. @@ -48,7 +48,7 @@ The solution looks a little bit awkward, but here it is: 1. Primitives are still primitive. A single value, as desired. 2. The language allows access to methods and properties of strings, numbers, booleans and symbols. -3. When this happens, a special "object wrapper" is created that provides the extra functionality, and then is destroyed. +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. @@ -91,18 +91,18 @@ In JavaScript, that's also possible for historical reasons, but highly **unrecom For instance: ```js run -alert( typeof 1 ); // "number" +alert( typeof 0 ); // "number" -alert( typeof new Number(1) ); // "object"! +alert( typeof new Number(0) ); // "object"! ``` -And because what follows, `zero`, is an object, the alert will show up: +Objects are always truthy in `if`, so here the alert will show up: ```js run let zero = new Number(0); if (zero) { // zero is true, because it's an object - alert( "zero is truthy?!?" ); + alert( "zero is truthy!?!" ); } ``` diff --git a/1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js b/1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js index 219fa8068..6bd0123db 100644 --- a/1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js +++ b/1-js/05-data-types/02-number/3-repeat-until-number/_js.view/test.js @@ -18,7 +18,7 @@ describe("readNumber", function() { assert.strictEqual(readNumber(), 0); }); - it("continues the loop unti meets a number", function() { + it("continues the loop until meets a number", function() { prompt.onCall(0).returns("not a number"); prompt.onCall(1).returns("not a number again"); prompt.onCall(2).returns("1"); @@ -35,4 +35,4 @@ describe("readNumber", function() { assert.isNull(readNumber()); }); -}); \ No newline at end of file +}); diff --git a/1-js/05-data-types/02-number/8-random-min-max/solution.md b/1-js/05-data-types/02-number/8-random-min-max/solution.md index 348f9e34b..8736c3d56 100644 --- a/1-js/05-data-types/02-number/8-random-min-max/solution.md +++ b/1-js/05-data-types/02-number/8-random-min-max/solution.md @@ -2,7 +2,7 @@ We need to "map" all values from the interval 0..1 into values from `min` to `ma That can be done in two stages: -1. If we multiply a random number from 0..1 by `max-min`, then it the interval of possible values increases `0..1` to `0..max-min`. +1. If we multiply a random number from 0..1 by `max-min`, then the interval of possible values increases `0..1` to `0..max-min`. 2. Now if we add `min`, the possible interval becomes from `min` to `max`. The function: diff --git a/1-js/05-data-types/02-number/9-random-int-min-max/task.md b/1-js/05-data-types/02-number/9-random-int-min-max/task.md index 29341b2af..4ac7b5fbb 100644 --- a/1-js/05-data-types/02-number/9-random-int-min-max/task.md +++ b/1-js/05-data-types/02-number/9-random-int-min-max/task.md @@ -12,9 +12,9 @@ Any number from the interval `min..max` must appear with the same probability. Examples of its work: ```js -alert( random(1, 5) ); // 1 -alert( random(1, 5) ); // 3 -alert( random(1, 5) ); // 5 +alert( randomInteger(1, 5) ); // 1 +alert( randomInteger(1, 5) ); // 3 +alert( randomInteger(1, 5) ); // 5 ``` You can use the solution of the [previous task](info:task/random-min-max) as the base. diff --git a/1-js/05-data-types/02-number/article.md b/1-js/05-data-types/02-number/article.md index 2a5a371eb..a205f3e26 100644 --- a/1-js/05-data-types/02-number/article.md +++ b/1-js/05-data-types/02-number/article.md @@ -1,6 +1,6 @@ # Numbers -All numbers in JavaScript are stored in 64-bit format [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), also known as "double precision". +All numbers in JavaScript are stored in 64-bit format [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754-2008_revision), also known as "double precision floating point numbers". Let's recap and expand upon what we currently know about them. @@ -26,11 +26,11 @@ In other words, `"e"` multiplies the number by `1` with the given zeroes count. ```js 1e3 = 1 * 1000 -1.23e6 = 1.23 * 1000000 +1.23e6 = 1.23 * 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; @@ -39,7 +39,7 @@ let ms = 0.000001; Just like before, using `"e"` can help. If we'd like to avoid writing the zeroes explicitly, we could say: ```js -let ms = 1e-6; // six zeroes to the left from 1 +let ms = 1e-6; // six 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`. @@ -153,7 +153,7 @@ There are two ways to do so: ``` 2. The method [toFixed(n)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) rounds the number to `n` digits after the point and returns a string representation of the result. - + ```js run let num = 12.34; alert( num.toFixed(1) ); // "12.3" @@ -170,19 +170,19 @@ There are two ways to do so: ```js run let num = 12.34; - alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits + 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)`. ## Imprecise calculations -Internally, a number is represented in 64-bit format [IEEE-754](http://en.wikipedia.org/wiki/IEEE_754-1985), 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-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. If a number is too big, it would overflow the 64-bit storage, potentially giving an infinity: ```js run -alert( 1e500 ); // Infinity +alert( 1e500 ); // Infinity ``` What may be a little less obvious, but happens quite often, is the loss of precision. @@ -193,7 +193,7 @@ Consider this (falsy!) test: alert( 0.1 + 0.2 == 0.3 ); // *!*false*/!* ``` -That's right, if we check whether the sum of `0.1` and `0.2` is `0.3`, we get `false`. +That's right, if we check whether the sum of `0.1` and `0.2` is `0.3`, we get `false`. Strange! What is it then if not `0.3`? @@ -205,9 +205,9 @@ Ouch! There are more consequences than an incorrect comparison here. Imagine you But why does this happen? -A number is stored in memory in its binary form, a sequence of 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. +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)`. +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)`. 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. @@ -227,40 +227,39 @@ 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, Ruby give exactly the same result, because they are based on the same numeric format. ``` -Can we work around the problem? Sure, there're a number of ways: - -1. We can 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): +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 - ``` +```js run +let sum = 0.1 + 0.2; +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: +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: - ```js run - let sum = 0.1 + 0.2; - alert( +sum.toFixed(2) ); // 0.3 - ``` +```js run +let sum = 0.1 + 0.2; +alert( +sum.toFixed(2) ); // 0.3 +``` -2. We can temporarily turn numbers into integers for the maths and then revert it back. It works like this: +We also can temporarily multiply the numbers by 100 (or a bigger number) to turn them into integers, do the maths, and then divide back. Then, as we're doing maths with integers, the error somewhat decreases, but we still get it on division: - ```js run - alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 - ``` +```js run +alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3 +alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001 +``` - This works because when we do `0.1 * 10 = 1` and `0.2 * 10 = 2` then both numbers become integers, and there's no precision loss. +So, multiply/divide approach reduces the error, but doesn't remove it totally. -3. If we were dealing with a shop, then the most radical solution would be to store all prices in cents and use no fractions at all. But what if we apply a discount of 30%? In practice, totally evading fractions is rarely feasible, so the solutions above help avoid this pitfall. +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. ````smart header="The funny thing" Try running this: ```js run -// Hello! I'm a self-increasing number! +// Hello! I'm a self-increasing number! alert( 9999999999999999 ); // shows 10000000000000000 ``` @@ -272,7 +271,7 @@ JavaScript doesn't trigger an error in such events. It does its best to fit the ```smart header="Two zeroes" Another funny consequence of the internal representation of numbers is the existence of two zeroes: `0` and `-0`. -That's because a sign is represented by a single bit, so every number can be positive or negative, including a zero. +That's because a sign is represented by a single bit, so every number can be positive or negative, including a zero. In most cases the distinction is unnoticeable, because operators are suited to treat them as the same. ``` @@ -326,10 +325,10 @@ Please note that an empty or a space-only string is treated as `0` in all numeri 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: -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`, it rarely matters, but these values technically are different. +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. -In all other cases, `Object.is(a, b)` is the same as `a === b`. +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)). ``` @@ -423,7 +422,7 @@ For different numeral systems: 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. +- 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. For fractions: @@ -433,5 +432,3 @@ 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. - - 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 4809cf123..527d3978c 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 @@ -15,7 +15,7 @@ There are two variants here: Here's the 2nd variant: -```js run +```js run demo function ucFirst(str) { if (!str) return str; diff --git a/1-js/05-data-types/03-string/2-check-spam/solution.md b/1-js/05-data-types/03-string/2-check-spam/solution.md index 2468916f1..de8dde57d 100644 --- a/1-js/05-data-types/03-string/2-check-spam/solution.md +++ b/1-js/05-data-types/03-string/2-check-spam/solution.md @@ -1,6 +1,6 @@ -To make the search case-insensitive, let's bring the stirng to lower case and then search: +To make the search case-insensitive, let's bring the string to lower case and then search: -```js run +```js run demo function checkSpam(str) { let lowerStr = str.toLowerCase(); diff --git a/1-js/05-data-types/03-string/2-check-spam/task.md b/1-js/05-data-types/03-string/2-check-spam/task.md index d073adc05..3e7220951 100644 --- a/1-js/05-data-types/03-string/2-check-spam/task.md +++ b/1-js/05-data-types/03-string/2-check-spam/task.md @@ -4,7 +4,7 @@ importance: 5 # Check for spam -Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise `false. +Write a function `checkSpam(str)` that returns `true` if `str` contains 'viagra' or 'XXX', otherwise false. The function must be case-insensitive: diff --git a/1-js/05-data-types/03-string/3-truncate/solution.md b/1-js/05-data-types/03-string/3-truncate/solution.md index a49b70909..5546c47ee 100644 --- a/1-js/05-data-types/03-string/3-truncate/solution.md +++ b/1-js/05-data-types/03-string/3-truncate/solution.md @@ -2,10 +2,9 @@ The maximal length must be `maxlength`, so we need to cut it a little shorter, t Note that there is actually a single unicode character for an ellipsis. That's not three dots. -```js run +```js run demo function truncate(str, maxlength) { - return (str.length > maxlength) ? + return (str.length > maxlength) ? str.slice(0, maxlength - 1) + '…' : str; } ``` - diff --git a/1-js/05-data-types/03-string/4-extract-currency/solution.md b/1-js/05-data-types/03-string/4-extract-currency/solution.md index 8b1378917..e69de29bb 100644 --- a/1-js/05-data-types/03-string/4-extract-currency/solution.md +++ b/1-js/05-data-types/03-string/4-extract-currency/solution.md @@ -1 +0,0 @@ - diff --git a/1-js/05-data-types/03-string/article.md b/1-js/05-data-types/03-string/article.md index b8a8ac73c..e748d65f0 100644 --- a/1-js/05-data-types/03-string/article.md +++ b/1-js/05-data-types/03-string/article.md @@ -551,7 +551,7 @@ This method actually has two additional arguments specified in [the documentatio ## 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 of hieroglyphs characters or other rare symbols. +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. ``` diff --git a/1-js/05-data-types/04-array/10-maximal-subarray/_js.view/test.js b/1-js/05-data-types/04-array/10-maximal-subarray/_js.view/test.js index 143ad5345..b44e76fe7 100644 --- a/1-js/05-data-types/04-array/10-maximal-subarray/_js.view/test.js +++ b/1-js/05-data-types/04-array/10-maximal-subarray/_js.view/test.js @@ -30,4 +30,8 @@ describe("getMaxSubSum", function() { it("maximal subsum of [-1, -2] equals 0", function() { assert.equal(getMaxSubSum([-1, -2]), 0); }); -}); \ No newline at end of file + + it("maximal subsum of [2, -8, 5, -1, 2, -3, 2] equals 6", function() { + assert.equal(getMaxSubSum([2, -8, 5, -1, 2, -3, 2]), 6); + }); +}); 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 a2ba50bfc..daadf494b 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 @@ -1,4 +1,4 @@ -# The slow solution +# Slow solution We can calculate all possible subsums. @@ -29,8 +29,8 @@ For instance, for `[-1, 2, 3, -9, 11]`: -9 -9 + 11 -// Starting from -11 --11 +// Starting from 11 +11 ``` The code is actually a nested loop: the external loop over array elements, and the internal counts subsums starting with the current element. @@ -59,7 +59,7 @@ alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100 The solution has a time complexety 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 seroius sluggishness. +For big arrays (1000, 10000 or more items) such algorithms can lead to a serious sluggishness. # Fast solution @@ -67,7 +67,7 @@ Let's walk the array and keep the current partial sum of elements in the variabl If the description is too vague, please see the code, it's short enough: -```js run +```js run demo function getMaxSubSum(arr) { let maxSum = 0; let partialSum = 0; diff --git a/1-js/05-data-types/04-array/2-create-array/solution.md b/1-js/05-data-types/04-array/2-create-array/solution.md index eec9055e7..f032b55f0 100644 --- a/1-js/05-data-types/04-array/2-create-array/solution.md +++ b/1-js/05-data-types/04-array/2-create-array/solution.md @@ -5,6 +5,6 @@ let styles = ["Jazz", "Blues"]; styles.push("Rock-n-Roll"); styles[Math.floor((styles.length - 1) / 2)] = "Classics"; alert( styles.shift() ); -styles.unshift("Rap", "Reggie"); +styles.unshift("Rap", "Reggae"); ``` diff --git a/1-js/05-data-types/04-array/array-pop.png b/1-js/05-data-types/04-array/array-pop.png index 9113c76be..4d6867b14 100644 Binary files a/1-js/05-data-types/04-array/array-pop.png and b/1-js/05-data-types/04-array/array-pop.png differ diff --git a/1-js/05-data-types/04-array/array-pop@2x.png b/1-js/05-data-types/04-array/array-pop@2x.png index e6ec8d8fb..c65ef9446 100644 Binary files a/1-js/05-data-types/04-array/array-pop@2x.png and b/1-js/05-data-types/04-array/array-pop@2x.png differ diff --git a/1-js/05-data-types/04-array/array-shift.png b/1-js/05-data-types/04-array/array-shift.png index 03b29d930..5798a6476 100644 Binary files a/1-js/05-data-types/04-array/array-shift.png and b/1-js/05-data-types/04-array/array-shift.png differ diff --git a/1-js/05-data-types/04-array/array-shift@2x.png b/1-js/05-data-types/04-array/array-shift@2x.png index c9888a44c..ba95f2651 100644 Binary files a/1-js/05-data-types/04-array/array-shift@2x.png and b/1-js/05-data-types/04-array/array-shift@2x.png differ diff --git a/1-js/05-data-types/04-array/array-speed.png b/1-js/05-data-types/04-array/array-speed.png index 3737e8248..a400d0c1d 100644 Binary files a/1-js/05-data-types/04-array/array-speed.png and b/1-js/05-data-types/04-array/array-speed.png differ diff --git a/1-js/05-data-types/04-array/array-speed@2x.png b/1-js/05-data-types/04-array/array-speed@2x.png index e45624b50..11a3e67c9 100644 Binary files a/1-js/05-data-types/04-array/array-speed@2x.png and b/1-js/05-data-types/04-array/array-speed@2x.png differ diff --git a/1-js/05-data-types/04-array/article.md b/1-js/05-data-types/04-array/article.md index 12d064264..2d6c225c9 100644 --- a/1-js/05-data-types/04-array/article.md +++ b/1-js/05-data-types/04-array/article.md @@ -1,12 +1,12 @@ -# Arrays +# Arrays Objects allow you to store keyed collections of values. That's fine. -But quite often we find that we need an *ordered collection*, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc. +But quite often we find that we need an *ordered collection*, where we have a 1st, a 2nd, a 3rd element and so on. For example, we need that to store a list of something: users, goods, HTML elements etc. It is not convenient to use an object here, because it provides no methods to manage the order of elements. We can’t insert a new property “between” the existing ones. Objects are just not meant for such use. -There exists a special data structure named `Array`, to store ordered collections. +There exists a special data structure named `Array`, to store ordered collections. ## Declaration @@ -81,10 +81,10 @@ arr[3](); // hello ````smart header="Trailing comma" An array, just like an object, may end with a comma: -```js +```js let fruits = [ - "Apple", - "Orange", + "Apple", + "Orange", "Plum"*!*,*/!* ]; ``` @@ -95,7 +95,7 @@ The "trailing comma" style makes it easier to insert/remove items, because all l ## Methods pop/push, shift/unshift -A [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) is one of most common uses of an array. In computer science, this means an ordered collection of elements which supports two operations: +A [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) is one of the most common uses of an array. In computer science, this means an ordered collection of elements which supports two operations: - `push` appends an element to the end. - `shift` get an element from the beginning, advancing the queue, so that the 2nd element becomes the 1st. @@ -104,9 +104,9 @@ A [queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) is one of mo Arrays support both operations. -In practice we meet it very often. For example, a queue of messages that need to be shown on-screen. +In practice we need it very often. For example, a queue of messages that need to be shown on-screen. -There's another use case for arrays -- the data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)). +There's another use case for arrays -- the data structure named [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)). It supports two operations: @@ -121,7 +121,7 @@ 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 it is called [deque](https://en.wikipedia.org/wiki/Double-ended_queue). @@ -189,11 +189,11 @@ alert( fruits ); ## Internals -An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. Numbers are used as keys. +An array is a special kind of object. The square brackets used to access a property `arr[0]` actually come from the object syntax. That's essentially the same as `obj[key]`, where `arr` is the object, while numbers are used as keys. They extend objects providing special methods to work with ordered collections of data and also the `length` property. But at the core it's still an object. -Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object. +Remember, there are only 7 basic types in JavaScript. Array is an object and thus behaves like an object. For instance, it is copied by reference: @@ -203,7 +203,7 @@ let fruits = ["Banana"] let arr = fruits; // copy by reference (two variables reference the same array) alert( arr === fruits ); // true - + arr.push("Pear"); // modify the array by reference alert( fruits ); // Banana, Pear - 2 items now @@ -229,7 +229,7 @@ But the engine will see that we're working with the array as with a regular obje The ways to misuse an array: -- Add a non-numeric property like `arr.test = 5`. +- Add a non-numeric property like `arr.test = 5`. - Make holes, like: add `arr[0]` and then `arr[1000]` (and nothing between them). - Fill the array in the reverse order, like `arr[1000]`, `arr[999]` and so on. @@ -296,7 +296,7 @@ let fruits = ["Apple", "Orange", "Plum"]; // iterates over array elements for (let fruit of fruits) { - alert( fruit ); + alert( fruit ); } ``` @@ -320,7 +320,7 @@ But that's actually a bad idea. There are potential problems with it: There are so-called "array-like" objects in the browser and in other environments, that *look like arrays*. That is, they have `length` and indexes properties, but they may also have other non-numeric properties and methods, which we usually don't need. The `for..in` loop will list them though. So if we need to work with array-like objects, then these "extra" properties can become a problem. -2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may matter only in bottlenecks or just irrelevant. But still we should be aware of the difference. +2. The `for..in` loop is optimized for generic objects, not arrays, and thus is 10-100 times slower. Of course, it's still very fast. The speedup may only matter in bottlenecks. But still we should be aware of the difference. Generally, we shouldn't use `for..in` for arrays. @@ -338,7 +338,7 @@ fruits[123] = "Apple"; alert( fruits.length ); // 124 ``` -Note that we usually don't use arrays like that. +Note that we usually don't use arrays like that. Another interesting thing about the `length` property is that it's writable. @@ -385,7 +385,7 @@ To evade such surprises, we usually use square brackets, unless we really know w ## Multidimensional arrays -Arrays can have items that are also arrays. We can use it for multidimensional arrays, to store matrices: +Arrays can have items that are also arrays. We can use it for multidimensional arrays, for example to store matrices: ```js run let matrix = [ @@ -445,7 +445,7 @@ Array is a special kind of object, suited to storing and managing ordered data i 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. +- 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. We can use an array as a deque with the following operations: @@ -461,4 +461,3 @@ To loop over the elements of the array: - `for (let i in arr)` -- never use. We will return to arrays and study more methods to add, remove, extract elements and sort arrays in the chapter . - diff --git a/1-js/05-data-types/04-array/queue.png b/1-js/05-data-types/04-array/queue.png index 5e1fb640c..39af3beda 100644 Binary files a/1-js/05-data-types/04-array/queue.png and b/1-js/05-data-types/04-array/queue.png differ diff --git a/1-js/05-data-types/04-array/queue@2x.png b/1-js/05-data-types/04-array/queue@2x.png index 6acfc83d1..75045a51d 100644 Binary files a/1-js/05-data-types/04-array/queue@2x.png and b/1-js/05-data-types/04-array/queue@2x.png differ diff --git a/1-js/05-data-types/04-array/stack.png b/1-js/05-data-types/04-array/stack.png index d1c9cb9af..7d2599355 100644 Binary files a/1-js/05-data-types/04-array/stack.png and b/1-js/05-data-types/04-array/stack.png differ diff --git a/1-js/05-data-types/04-array/stack@2x.png b/1-js/05-data-types/04-array/stack@2x.png index b3835fa44..16c622518 100644 Binary files a/1-js/05-data-types/04-array/stack@2x.png and b/1-js/05-data-types/04-array/stack@2x.png differ diff --git a/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js b/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js index 024d6d6c2..490f570ad 100644 --- a/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/1-camelcase/_js.view/solution.js @@ -1,8 +1,10 @@ function camelize(str) { return str - .split('-') // my-long-word -> ['my', 'long', 'word'] - .map( + .split('-') // splits 'my-long-word' into array ['my', 'long', 'word'] + .map( + // capitalizes first letters of all array items except the first one + // converts ['my', 'long', 'word'] into ['my', 'Long', 'Word'] (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1) - ) // ['my', 'long', 'word'] -> ['my', 'Long', 'Word'] - .join(''); // ['my', 'Long', 'Word'] -> myLongWord + ) + .join(''); // joins ['my', 'Long', 'Word'] into 'myLongWord' } diff --git a/1-js/05-data-types/05-array-methods/10-average-age/task.md b/1-js/05-data-types/05-array-methods/10-average-age/task.md index a991c156b..bf5f85df3 100644 --- a/1-js/05-data-types/05-array-methods/10-average-age/task.md +++ b/1-js/05-data-types/05-array-methods/10-average-age/task.md @@ -4,7 +4,7 @@ importance: 4 # Get average age -Write the function `getAverageAge(users)` that gets an array of objects with property `age` and gets the average. +Write the function `getAverageAge(users)` that gets an array of objects with property `age` and returns the average age. The formula for the average is `(age1 + age2 + ... + ageN) / N`. @@ -19,4 +19,3 @@ let arr = [ john, pete, mary ]; alert( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28 ``` - diff --git a/1-js/05-data-types/05-array-methods/11-array-unique/solution.md b/1-js/05-data-types/05-array-methods/11-array-unique/solution.md index 8f7fd9af4..32d3b2679 100644 --- a/1-js/05-data-types/05-array-methods/11-array-unique/solution.md +++ b/1-js/05-data-types/05-array-methods/11-array-unique/solution.md @@ -2,7 +2,7 @@ Let's walk the array items: - For each item we'll check if the resulting array already has that item. - If it is so, then ignore, otherwise add to results. -```js run +```js run demo function unique(arr) { let result = []; diff --git a/1-js/05-data-types/05-array-methods/2-filter-range/solution.md b/1-js/05-data-types/05-array-methods/2-filter-range/solution.md index e69de29bb..73993a07a 100644 --- a/1-js/05-data-types/05-array-methods/2-filter-range/solution.md +++ b/1-js/05-data-types/05-array-methods/2-filter-range/solution.md @@ -0,0 +1,14 @@ +```js run demo +function filterRange(arr, a, b) { + // added brackets around the expression for better readability + return arr.filter(item => (a <= item && item <= b)); +} + +let arr = [5, 3, 8, 1]; + +let filtered = filterRange(arr, 1, 4); + +alert( filtered ); // 3,1 (matching values) + +alert( arr ); // 5,3,8,1 (not modified) +``` diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js index 61cda126b..488db3755 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/_js.view/solution.js @@ -1,5 +1,4 @@ - function filterRangeInPlace(arr, a, b) { for (let i = 0; i < arr.length; i++) { @@ -12,4 +11,4 @@ function filterRangeInPlace(arr, a, b) { } } -} \ No newline at end of file +} diff --git a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md index e69de29bb..36e3130ff 100644 --- a/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md +++ b/1-js/05-data-types/05-array-methods/3-filter-range-in-place/solution.md @@ -0,0 +1,21 @@ +```js run demo +function filterRangeInPlace(arr, a, b) { + + for (let i = 0; i < arr.length; i++) { + let val = arr[i]; + + // remove if outside of the interval + if (val < a || val > b) { + arr.splice(i, 1); + i--; + } + } + +} + +let arr = [5, 3, 8, 1]; + +filterRangeInPlace(arr, 1, 4); // removed the numbers except from 1 to 4 + +alert( arr ); // [3, 1] +``` diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md index 8d56db9d6..9f1ade707 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/solution.md @@ -1,17 +1,18 @@ ```js run no-beautify -function sortByName(arr) { - arr.sort((a, b) => a.name > b.name); +function sortByAge(arr) { + arr.sort((a, b) => a.age > b.age ? 1 : -1); } let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; -let arr = [ john, pete, mary ]; +let arr = [ pete, john, mary ]; -sortByName(arr); +sortByAge(arr); // now sorted is: [john, mary, pete] +alert(arr[0].name); // John alert(arr[1].name); // Mary +alert(arr[2].name); // Pete ``` - diff --git a/1-js/05-data-types/05-array-methods/8-sort-objects/task.md b/1-js/05-data-types/05-array-methods/8-sort-objects/task.md index 8a3f5a97a..9a215c9f4 100644 --- a/1-js/05-data-types/05-array-methods/8-sort-objects/task.md +++ b/1-js/05-data-types/05-array-methods/8-sort-objects/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Sort objects +# Sort users by age -Write the function `sortByName(users)` that gets an array of objects with property `name` and sorts it. +Write the function `sortByAge(users)` that gets an array of objects with the `age` property and sorts them by `age`. For instance: @@ -13,11 +13,12 @@ let john = { name: "John", age: 25 }; let pete = { name: "Pete", age: 30 }; let mary = { name: "Mary", age: 28 }; -let arr = [ john, pete, mary ]; +let arr = [ pete, john, mary ]; -sortByName(arr); +sortByAge(arr); // now: [john, mary, pete] +alert(arr[0].name); // John alert(arr[1].name); // Mary +alert(arr[2].name); // Pete ``` - 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 a378bd0fb..1e2685580 100644 --- a/1-js/05-data-types/05-array-methods/article.md +++ b/1-js/05-data-types/05-array-methods/article.md @@ -36,7 +36,7 @@ That's natural, because `delete obj.key` removes a value by the `key`. It's all So, special methods should be used. -The [arr.splice(str)](mdn:js/Array/splice) method is a swiss army knife for arrays. It can do everything: add, remove and insert elements. +The [arr.splice(str)](mdn:js/Array/splice) method is a swiss army knife for arrays. It can do everything: insert, remove and replace elements. The syntax is: @@ -122,7 +122,7 @@ The syntax is: arr.slice(start, end) ``` -It returns a new array where it copies all items start index `"start"` to `"end"` (not including `"end"`). Both `start` and `end` can be negative, in that case position from array end is assumed. +It returns a new array containing all items from index `"start"` to `"end"` (not including `"end"`). Both `start` and `end` can be negative, in that case position from array end is assumed. It works like `str.slice`, but makes subarrays instead of substrings. @@ -201,6 +201,35 @@ let arrayLike = { alert( arr.concat(arrayLike) ); // 1,2,something,else ``` +## Iterate: forEach + +The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for every element of the array. + +The syntax: +```js +arr.forEach(function(item, index, array) { + // ... do something with item +}); +``` + +For instance, this shows each element of the array: + +```js run +// for each element call alert +["Bilbo", "Gandalf", "Nazgul"].forEach(alert); +``` + +And this code is more elaborate about their positions in the target array: + +```js run +["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { + alert(`${item} is at index ${index} in ${array}`); +}); +``` + +The result of the function (if it returns any) is thrown away and ignored. + + ## Searching in array These are methods to search for something in an array. @@ -210,7 +239,7 @@ These are methods to search for something in an array. 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: - `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 from right to left. +- `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. For instance: @@ -246,7 +275,8 @@ Here the [arr.find](mdn:js/Array/find) method comes in handy. The syntax is: ```js let result = arr.find(function(item, index, array) { - // should return true if the item is what we are looking for + // if true is returned, item is returned and iteration is stopped + // for falsy scenario returns undefined }); ``` @@ -274,9 +304,9 @@ alert(user.name); // John In real life arrays of objects is a common thing, so the `find` method is very useful. -Note that in the example we provide to `find` a single-argument function `item => item.id == 1`. Other parameters of `find` are rarely used. +Note that in the example we provide to `find` the function `item => item.id == 1` with one argument. Other arguments of this function are rarely used. -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. +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. ### filter @@ -284,11 +314,12 @@ The `find` method looks for a single (first) element that makes the function ret If there may be many, we can use [arr.filter(fn)](mdn:js/Array/filter). -The syntax is roughly the same as `find`, but it returns an array of matching elements: +The syntax is similar to `find`, but filter continues to iterate for all array elements even if `true` is already returned: ```js let results = arr.filter(function(item, index, array) { - // should return true if the item passes the filter + // if true item is pushed to results and iteration continues + // returns empty array for complete falsy scenario }); ``` @@ -329,7 +360,7 @@ It calls the function for each element of the array and returns the array of res For instance, here we transform each element into its length: ```js run -let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length) +let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length); alert(lengths); // 5,7,6 ``` @@ -477,7 +508,7 @@ alert( str.split('') ); // t,e,s,t ``` ```` -The call [arr.join(str)](mdn:js/Array/join) does the reverse to `split`. It creates a string of `arr` items glued by `str` between them. +The call [arr.join(separator)](mdn:js/Array/join) does the reverse to `split`. It creates a string of `arr` items glued by `separator` between them. For instance: @@ -491,7 +522,7 @@ alert( str ); // Bilbo;Gandalf;Nazgul ### reduce/reduceRight -When we need to iterate over an array -- we can use `forEach`. +When we need to iterate over an array -- we can use `forEach`, `for` or `for..of`. When we need to iterate and return the data for each element -- we can use `map`. @@ -500,7 +531,7 @@ The methods [arr.reduce](mdn:js/Array/reduce) and [arr.reduceRight](mdn:js/Array The syntax is: ```js -let value = arr.reduce(function(previousValue, item, index, arr) { +let value = arr.reduce(function(previousValue, item, index, array) { // ... }, initial); ``` @@ -509,7 +540,7 @@ The function is applied to the elements. You may notice the familiar arguments, - `item` -- is the current array item. - `index` -- is its position. -- `arr` -- is the array. +- `array` -- is the array. So far, like `forEach/map`. But there's one more argument: @@ -539,7 +570,7 @@ The calculation flow: ![](reduce.png) -Or in the form of a table, where each row represents is a function call on the next array element: +Or in the form of a table, where each row represents a function call on the next array element: | |`sum`|`current`|`result`| |---|-----|---------|---------| @@ -585,34 +616,6 @@ 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. -## Iterate: forEach - -The [arr.forEach](mdn:js/Array/forEach) method allows to run a function for every element of the array. - -The syntax: -```js -arr.forEach(function(item, index, array) { - // ... do something with item -}); -``` - -For instance, this shows each element of the array: - -```js run -// for each element call alert -["Bilbo", "Gandalf", "Nazgul"].forEach(alert); -``` - -And this code is more elaborate about their positions in the target array: - -```js run -["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => { - alert(`${item} is at index ${index} in ${array}`); -}); -``` - -The result of the function (if it returns any) is thrown away and ignored. - ## Array.isArray Arrays do not form a separate language type. They are based on objects. @@ -694,6 +697,9 @@ A cheatsheet of array methods: - `includes(value)` -- returns `true` if the array has `value`, otherwise `false`. - `find/filter(func)` -- filter elements through the function, return first/all values that make it return `true`. - `findIndex` is like `find`, but returns the index instead of a value. + +- To iterate over elements: + - `forEach(func)` -- calls `func` for every element, does not return anything. - To transform the array: - `map(func)` -- creates a new array from results of calling `func` for every element. @@ -702,9 +708,6 @@ A cheatsheet of array methods: - `split/join` -- convert a string to array and back. - `reduce(func, initial)` -- calculate a single value over the array by calling `func` for each element and passing an intermediate result between the calls. -- To iterate over elements: - - `forEach(func)` -- calls `func` for every element, does not return anything. - - Additionally: - `Array.isArray(arr)` checks `arr` for being an array. diff --git a/1-js/05-data-types/05-array-methods/reduce.png b/1-js/05-data-types/05-array-methods/reduce.png index 41476d2ae..7566c4d84 100644 Binary files a/1-js/05-data-types/05-array-methods/reduce.png and b/1-js/05-data-types/05-array-methods/reduce.png differ diff --git a/1-js/05-data-types/05-array-methods/reduce@2x.png b/1-js/05-data-types/05-array-methods/reduce@2x.png index f31647d17..7c9fd6692 100644 Binary files a/1-js/05-data-types/05-array-methods/reduce@2x.png and b/1-js/05-data-types/05-array-methods/reduce@2x.png differ diff --git a/1-js/05-data-types/06-iterable/article.md b/1-js/05-data-types/06-iterable/article.md index 7f72f5caf..474d0463d 100644 --- a/1-js/05-data-types/06-iterable/article.md +++ b/1-js/05-data-types/06-iterable/article.md @@ -105,7 +105,7 @@ for (let num of range) { Now `range[Symbol.iterator]()` returns the `range` object itself: it has the necessary `next()` method and remembers the current iteration progress in `this.current`. Shorter? Yes. And sometimes that's fine too. -The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself. But two parallel for-ofs is a rare thing, doable with some async scenarios. +The downside is that now it's impossible to have two `for..of` loops running over the object simultaneously: they'll share the iteration state, because there's only one iterator -- the object itself. But two parallel for-ofs is a rare thing, even in async scenarios. ```smart header="Infinite iterators" Infinite iterators are also possible. For instance, the `range` becomes infinite for `range.to = Infinity`. Or we can make an iterable object that generates an infinite sequence of pseudorandom numbers. Also can be useful. @@ -144,7 +144,7 @@ Normally, internals of iterables are hidden from the external code. There's a `f But to understand things a little bit deeper let's see how to create an iterator explicitly. -We'll iterate over a string the same way as `for..of`, but with direct calls. This code gets a string iterator and calls it "manually": +We'll iterate over a string in exactlly the same way as `for..of`, but with direct calls. This code creates a string iterator and gets values from it "manually": ```js run let str = "Hello"; @@ -170,7 +170,9 @@ There are two official terms that look similar, but are very different. Please m - *Iterables* are objects that implement the `Symbol.iterator` method, as described above. - *Array-likes* are objects that have indexes and `length`, so they look like arrays. -Naturally, these properties can combine. For instance, strings are both iterable (`for..of` works on them) and array-like (they have numeric indexes and `length`). +When we use JavaScript for practical tasks in browser or other environments, we may meet objects that are iterables or array-likes, or both. + +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. @@ -191,11 +193,11 @@ for (let item of arrayLike) {} */!* ``` -What do they have in common? Both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we have such an object and want to work with it as with an array. +Both iterables and array-likes are usually *not arrays*, they don't have `push`, `pop` etc. That's rather inconvenient if we have such an object and want to work with it as with an array. E.g. we would like to work with `range` using array methods. How to achieve that? ## Array.from -There's a universal method [Array.from](mdn:js/Array/from) that brings them together. It takes an iterable or array-like value and makes a "real" `Array` from it. Then we can call array methods on it. +There's a universal method [Array.from](mdn:js/Array/from) that takes an iterable or array-like value and makes a "real" `Array` from it. Then we can call array methods on it. For instance: @@ -227,7 +229,7 @@ The full syntax for `Array.from` allows to provide an optional "mapping" functio Array.from(obj[, mapFn, thisArg]) ``` -The second argument `mapFn` should be the function to apply to each element before adding to the array, and `thisArg` allows to set `this` for it. +The optional second argument `mapFn` can be a function that will be applied to each element before adding to the array, and `thisArg` allows to set `this` for it. For instance: diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md b/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md index dacb42856..4c8af1f24 100644 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md +++ b/1-js/05-data-types/07-map-set-weakmap-weakset/02-filter-anagrams/solution.md @@ -59,7 +59,7 @@ Here we could also use a plain object instead of the `Map`, because keys are str That's how the solution can look: -```js run +```js run demo function aclean(arr) { let obj = {}; @@ -68,7 +68,7 @@ function aclean(arr) { obj[sorted] = arr[i]; } - return Array.from(Object.values(obj)); + return Object.values(obj); } let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"]; diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md b/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md index 9a3e5d250..b1ccbd0ac 100644 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md +++ b/1-js/05-data-types/07-map-set-weakmap-weakset/03-iterable-keys/task.md @@ -16,7 +16,7 @@ map.set("name", "John"); let keys = map.keys(); *!* -// Error: numbers.push is not a function +// Error: keys.push is not a function keys.push("more"); */!* ``` diff --git a/1-js/05-data-types/07-map-set-weakmap-weakset/article.md b/1-js/05-data-types/07-map-set-weakmap-weakset/article.md index a7838efdd..034ad22c3 100644 --- a/1-js/05-data-types/07-map-set-weakmap-weakset/article.md +++ b/1-js/05-data-types/07-map-set-weakmap-weakset/article.md @@ -159,6 +159,7 @@ The iteration goes in the same order as the values were inserted. `Map` preserve Besides that, `Map` has a built-in `forEach` method, similar to `Array`: ```js +// runs the function for each (key, value) pair recipeMap.forEach( (value, key, map) => { alert(`${key}: ${value}`); // cucumber: 500 etc }); @@ -223,7 +224,7 @@ set.forEach((value, valueAgain, set) => { Note the funny thing. The `forEach` function in the `Set` has 3 arguments: a value, then *again a value*, and then the target object. Indeed, the same value appears in the arguments twice. -That's for compatibility with `Map` where `forEach` has three arguments. +That's for compatibility with `Map` where `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. The same methods `Map` has for iterators are also supported: @@ -253,9 +254,27 @@ john = null; Usually, properties of an object or elements of an array or another data structure are considered reachable and kept in memory while that data structure is in memory. -In a regular `Map`, it does not matter if we store an object as a key or as a value. It's kept in memory even if there are no more references to it. +For instance, if we put an object into an array, then while the array is alive, the object will be alive as well, even if there are no other references to it. + +Like this: + +```js +let john = { name: "John" }; + +let array = [ john ]; + +john = null; // overwrite the reference + +*!* +// john is stored inside the array, so it won't be garbage-collected +// we can get it as array[0] +*/!* +``` + +Or, if we use an object as the key in a regular `Map`, then while the `Map` exists, that object exists as well. It occupies memory and may not be garbage collected. For instance: + ```js let john = { name: "John" }; @@ -265,19 +284,16 @@ map.set(john, "..."); john = null; // overwrite the reference *!* -// john is stored inside the map +// john is stored inside the map, // we can get it by using map.keys() */!* ``` +`WeakMap/WeakSet` are fundamentally different in this aspect. They do not prevent garbage-collection of key objects. -With the exception of `WeakMap/WeakSet`. - -**`WeakMap/WeakSet` does not prevent the object removal from the memory.** +Let's explain it starting with `WeakMap`. -Let's start with `WeakMap`. - -The first difference from `Map` is that its keys must be objects, not primitive values: +The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values: ```js run let weakMap = new WeakMap(); @@ -287,7 +303,8 @@ let obj = {}; weakMap.set(obj, "ok"); // works fine (object key) *!* -weakMap.set("test", "Whoops"); // Error, because "test" is a primitive +// can't use a string as the key +weakMap.set("test", "Whoops"); // Error, because "test" is not an object */!* ``` @@ -306,35 +323,35 @@ john = null; // overwrite the reference Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it is to be automatically deleted. -...And `WeakMap` does not support methods `keys()`, `values()`, `entries()`, we can not iterate over it. So there's really no way to receive all keys or values from it. +`WeakMap` does not support iteration and methods `keys()`, `values()`, `entries()`, so there's no way to get all keys or values from it. `WeakMap` has only the following methods: - `weakMap.get(key)` - `weakMap.set(key, value)` -- `weakMap.delete(key, value)` +- `weakMap.delete(key)` - `weakMap.has(key)` -Why such a limitation? That's for technical reasons. If the object has lost all other references (like `john` in the code above), then it is to be deleted automatically. But technically it's not exactly specified *when the cleanup happens*. +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*. -The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of the `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access `WeakMap` as a whole are not supported. +The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access `WeakMap` as a whole are not supported. Now where do we need such thing? -The idea of `WeakMap` is that we can store something for an object that exists only while the object exists. But we do not force the object to live by the mere fact that we store something for it. +The idea of `WeakMap` is that we can store something for an object that should exist only while the object exists. But we do not force the object to live by the mere fact that we store something for it. ```js weakMap.set(john, "secret documents"); -// if john dies, secret documents will be destroyed +// if john dies, secret documents will be destroyed automatically ``` -That's useful for situations when we have a main storage for the objects somewhere and need to keep additional information that is only relevant while the object lives. +That's useful for situations when we have a main storage for the objects somewhere and need to keep additional information, that is only relevant while the object lives. Let's look at an example. For instance, we have code that keeps a visit count for each user. The information is stored in a map: a user is the key and the visit count is the value. When a user leaves, we don't want to store their visit count anymore. -One way would be to keep track of leaving users and clean up the storage manually: +One way would be to keep track of users, and when they leave -- clean up the map manually: ```js run let john = { name: "John" }; @@ -352,7 +369,7 @@ john = null; // but it's still in the map, we need to clean it! */!* alert( visitsCountMap.size ); // 1 -// it's also in the memory, because Map uses it as the key +// and john is also in the memory, because Map uses it as the key ``` Another way would be to use `WeakMap`: @@ -371,9 +388,11 @@ john = null; // so the object is removed both from the memory and from visitsCountMap automatically ``` -With a regular `Map`, cleaning up after a user has left becomes a tedious task: we not only need to remove the user from its main storage (be it a variable or an array), but also need to clean up the additional stores like `visitsCountMap`. And it can become cumbersome in more complex cases when users are managed in one place of the code and the additional structure is at another place and is getting no information about removals. +With a regular `Map`, cleaning up after a user has left becomes a tedious task: we not only need to remove the user from its main storage (be it a variable or an array), but also need to clean up the additional stores like `visitsCountMap`. And it can become cumbersome in more complex cases when users are managed in one place of the code and the additional structure is in another place and is getting no information about removals. +```summary `WeakMap` can make things simpler, because it is cleaned up automatically. The information in it like visits count in the example above lives only while the key object exists. +``` `WeakSet` behaves similarly: @@ -381,7 +400,7 @@ With a regular `Map`, cleaning up after a user has left becomes a tedious task: - 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. -For instance, we can use it to keep track of whether an item is checked: +For instance, we can use it to keep track of whether a message is read: ```js let messages = [ @@ -393,21 +412,26 @@ let messages = [ // fill it with array elements (3 items) let unreadSet = new WeakSet(messages); -// we can use unreadSet to see whether a message is unread +// use unreadSet to see whether a message is unread alert(unreadSet.has(messages[1])); // true + // remove it from the set after reading unreadSet.delete(messages[1]); // true // and when we shift our messages history, the set is cleaned up automatically messages.shift(); + +*!* // no need to clean unreadSet, it now has 2 items -// unfortunately, there's no method to get the exact count of items, so can't show it +*/!* +// (though technically we don't know for sure when the JS engine clears it) ``` -The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but actually does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. +The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. ## Summary +Regular collections: - `Map` -- is a collection of keyed values. The differences from a regular `Object`: @@ -421,6 +445,8 @@ The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterati - Unlike an array, does not allow to reorder elements. - Keeps the insertion order. +Collections that allow garbage-collection: + - `WeakMap` -- a variant of `Map` that allows only objects as keys and removes them once they become inaccessible by other means. - It does not support operations on the structure as a whole: no `size`, no `clear()`, no iterations. diff --git a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md b/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md index e69de29bb..27a7b418a 100644 --- a/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md +++ b/1-js/05-data-types/08-keys-values-entries/01-sum-salaries/solution.md @@ -0,0 +1,29 @@ +```js run demo +function sumSalaries(salaries) { + + let sum = 0; + for (let salary of Object.values(salaries)) { + sum += salary; + } + + return sum; // 650 +} + +let salaries = { + "John": 100, + "Pete": 300, + "Mary": 250 +}; + +alert( sumSalaries(salaries) ); // 650 +``` +Or, optionally, we could also get the sum using `Object.values` and `reduce`: + +```js +// reduce loops over array of salaries, +// adding them up +// and returns the result +function sumSalaries(salaries) { + return Object.values(salaries).reduce((a, b) => a + b, 0) // 650 +} +``` diff --git a/1-js/05-data-types/08-keys-values-entries/article.md b/1-js/05-data-types/08-keys-values-entries/article.md index 50ba91cef..66ca3ca92 100644 --- a/1-js/05-data-types/08-keys-values-entries/article.md +++ b/1-js/05-data-types/08-keys-values-entries/article.md @@ -45,7 +45,7 @@ let user = { }; ``` -- `Object.keys(user) = [name, age]` +- `Object.keys(user) = ["name", "age"]` - `Object.values(user) = ["John", 30]` - `Object.entries(user) = [ ["name","John"], ["age",30] ]` diff --git a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/solution.js b/1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/solution.js index d95cf1b1e..f4bd5c761 100644 --- a/1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/solution.js +++ b/1-js/05-data-types/09-destructuring-assignment/6-max-salary/_js.view/solution.js @@ -3,7 +3,7 @@ function topSalary(salaries) { let max = 0; let maxName = null; - for(let [name, salary] of Object.entries(salaries)) { + for(const [name, salary] of Object.entries(salaries)) { if (max < salary) { max = salary; maxName = name; diff --git a/1-js/05-data-types/09-destructuring-assignment/article.md b/1-js/05-data-types/09-destructuring-assignment/article.md index 2e1960655..7c60b274a 100644 --- a/1-js/05-data-types/09-destructuring-assignment/article.md +++ b/1-js/05-data-types/09-destructuring-assignment/article.md @@ -42,19 +42,19 @@ let surname = arr[1]; ``` ```` -````smart header="Ignore first elements" +````smart header="Ignore elements using commas" Unwanted elements of the array can also be thrown away via an extra comma: ```js run *!* -// first and second elements are not needed -let [, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; +// second element is not needed +let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"]; */!* alert( title ); // Consul ``` -In the code above, although the first and second elements of the array are skipped, the third one is assigned to `title`, and the rest are also skipped. +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 is also skipped. ```` ````smart header="Works with any iterable on the right-side" @@ -128,6 +128,7 @@ alert(name1); // Julius alert(name2); // Caesar *!* +// Note that type of `rest` is Array. alert(rest[0]); // Consul alert(rest[1]); // of the Roman Republic alert(rest.length); // 2 @@ -146,6 +147,7 @@ let [firstName, surname] = []; */!* alert(firstName); // undefined +alert(surname); // undefined ``` If we want a "default" value to replace the missing one, we can provide it using `=`: @@ -335,7 +337,7 @@ The problem is that JavaScript treats `{...}` in the main code flow (not inside } ``` -To show JavaScript that it's not a code block, we can wrap the whole assignment in brackets `(...)`: +To show JavaScript that it's not a code block, we can wrap the whole assignment in parentheses `(...)`: ```js run let title, width, height; @@ -383,6 +385,8 @@ alert(item2); // Donut The whole `options` object except `extra` that was not mentioned, is assigned to corresponding variables. +Note that `size` and `items` itself is not destructured. + ![](destructuring-complex.png) Finally, we have `width`, `height`, `item1`, `item2` and `title` from the default value. diff --git a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex.png b/1-js/05-data-types/09-destructuring-assignment/destructuring-complex.png index 50c4ffc93..c46bf6e45 100644 Binary files a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex.png and b/1-js/05-data-types/09-destructuring-assignment/destructuring-complex.png differ diff --git a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex@2x.png b/1-js/05-data-types/09-destructuring-assignment/destructuring-complex@2x.png index bb908281d..26032a3d6 100644 Binary files a/1-js/05-data-types/09-destructuring-assignment/destructuring-complex@2x.png and b/1-js/05-data-types/09-destructuring-assignment/destructuring-complex@2x.png differ diff --git a/1-js/05-data-types/10-date/2-get-week-day/solution.md b/1-js/05-data-types/10-date/2-get-week-day/solution.md index 2acaabf9c..58d75c1c3 100644 --- a/1-js/05-data-types/10-date/2-get-week-day/solution.md +++ b/1-js/05-data-types/10-date/2-get-week-day/solution.md @@ -2,7 +2,7 @@ The method `date.getDay()` returns the number of the weekday, starting from sund Let's make an array of weekdays, so that we can get the proper day name by its number: -```js run +```js run demo function getWeekDay(date) { let days = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; diff --git a/1-js/05-data-types/10-date/3-weekday/solution.md b/1-js/05-data-types/10-date/3-weekday/solution.md index bfe3f4cac..e69de29bb 100644 --- a/1-js/05-data-types/10-date/3-weekday/solution.md +++ b/1-js/05-data-types/10-date/3-weekday/solution.md @@ -1,14 +0,0 @@ -```js run -function getLocalDay(date) { - - let day = date.getDay(); - - if (day == 0) { // 0 becomes 7 - day = 7; - } - - return day; -} - -alert( getLocalDay(new Date(2012, 0, 3)) ); // 2 -``` diff --git a/1-js/05-data-types/10-date/4-get-date-ago/solution.md b/1-js/05-data-types/10-date/4-get-date-ago/solution.md index 7abc7ab9c..5c394c100 100644 --- a/1-js/05-data-types/10-date/4-get-date-ago/solution.md +++ b/1-js/05-data-types/10-date/4-get-date-ago/solution.md @@ -11,7 +11,7 @@ function getDateAgo(date, days) { To implement it let's clone the date, like this: -```js run +```js run demo function getDateAgo(date, days) { let dateCopy = new Date(date); diff --git a/1-js/05-data-types/10-date/5-last-day-of-month/solution.md b/1-js/05-data-types/10-date/5-last-day-of-month/solution.md index 65f61c5ba..4f642536e 100644 --- a/1-js/05-data-types/10-date/5-last-day-of-month/solution.md +++ b/1-js/05-data-types/10-date/5-last-day-of-month/solution.md @@ -1,5 +1,5 @@ Let's create a date using the next month, but pass zero as the day: -```js run +```js run demo function getLastDayOfMonth(year, month) { let date = new Date(year, month + 1, 0); return date.getDate(); diff --git a/1-js/05-data-types/10-date/6-get-seconds-today/solution.md b/1-js/05-data-types/10-date/6-get-seconds-today/solution.md index 91903d905..a483afe93 100644 --- a/1-js/05-data-types/10-date/6-get-seconds-today/solution.md +++ b/1-js/05-data-types/10-date/6-get-seconds-today/solution.md @@ -22,5 +22,5 @@ An alternative solution would be to get hours/minutes/seconds and convert them t function getSecondsToday() { let d = new Date(); return d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds(); -}; +} ``` diff --git a/1-js/05-data-types/10-date/8-format-date-relative/solution.md b/1-js/05-data-types/10-date/8-format-date-relative/solution.md index 24fec997d..2507c840c 100644 --- a/1-js/05-data-types/10-date/8-format-date-relative/solution.md +++ b/1-js/05-data-types/10-date/8-format-date-relative/solution.md @@ -1,6 +1,6 @@ To get the time from `date` till now -- let's substract the dates. -```js run +```js run demo function formatDate(date) { let diff = new Date() - date; // the difference in milliseconds @@ -57,12 +57,12 @@ function formatDate(date) { let diffSec = Math.round(diffMs / 1000); let diffMin = diffSec / 60; let diffHour = diffMin / 60; - + // formatting year = year.toString().slice(-2); month = month < 10 ? '0' + month : month; dayOfMonth = dayOfMonth < 10 ? '0' + dayOfMonth : dayOfMonth; - + if (diffSec < 1) { return 'right now'; } else if (diffMin < 1) { diff --git a/1-js/05-data-types/10-date/article.md b/1-js/05-data-types/10-date/article.md index 00390812f..8a75f1cbd 100644 --- a/1-js/05-data-types/10-date/article.md +++ b/1-js/05-data-types/10-date/article.md @@ -2,7 +2,7 @@ Let's meet a new built-in object: [Date](mdn:js/Date). It stores the date, time and provides methods for date/time management. -For instance, we can use it to store creation/modification times, or to measure time, or just to print out the current date. +For instance, we can use it to store creation/modification times, to measure time, or just to print out the current date. ## Creation @@ -39,7 +39,13 @@ To create a new `Date` object call `new Date()` with one of the following argume ```js run let date = new Date("2017-01-26"); - alert(date); // Thu Jan 26 2017 ... + alert(date); + // The time portion of the date is assumed to be midnight GMT and + // is adjusted according to the timezone the code is run in + // So the result could be + // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) + // or + // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time) ``` `new Date(year, month, date, hours, minutes, seconds, ms)` @@ -108,7 +114,7 @@ alert( date.getHours() ); alert( date.getUTCHours() ); ``` -Besides the given methods, there are two special ones, that do not have a UTC-variant: +Besides the given methods, there are two special ones that do not have a UTC-variant: [getTime()](mdn:js/Date/getTime) : Returns the timestamp for the date -- a number of milliseconds passed from the January 1st of 1970 UTC+0. @@ -418,4 +424,4 @@ alert(`Loading started ${performance.now()}ms ago`); // more than 3 digits after the decimal point are precision errors, but only the first 3 are correct ``` -Node.JS has `microtime` module and other ways. Technically, any device and environment allows to get more precision, it's just not in `Date`. +Node.js has `microtime` module and other ways. Technically, any device and environment allows to get more precision, it's just not in `Date`. diff --git a/1-js/05-data-types/11-json/article.md b/1-js/05-data-types/11-json/article.md index 9d5d6f0e6..cce3d1df9 100644 --- a/1-js/05-data-types/11-json/article.md +++ b/1-js/05-data-types/11-json/article.md @@ -66,7 +66,7 @@ alert(json); The method `JSON.stringify(student)` takes the object and converts it into a string. -The resulting `json` string is a called *JSON-encoded* or *serialized* or *stringified* or *marshalled* object. We are ready to send it over the wire or put into a plain data store. +The resulting `json` string is called a *JSON-encoded* or *serialized* or *stringified* or *marshalled* object. We are ready to send it over the wire or put into a plain data store. Please note that a JSON-encoded object has several important differences from the object literal: @@ -361,7 +361,7 @@ alert( JSON.stringify(meetup) ); Here we can see that `date` `(1)` became a string. That's because all dates have a built-in `toJSON` method which returns such kind of string. -Now let's add a custom `toJSON` for our object `room`: +Now let's add a custom `toJSON` for our object `room` `(2)`: ```js run let room = { diff --git a/1-js/05-data-types/11-json/json-meetup.png b/1-js/05-data-types/11-json/json-meetup.png index 0a26e0a67..268666424 100644 Binary files a/1-js/05-data-types/11-json/json-meetup.png and b/1-js/05-data-types/11-json/json-meetup.png differ diff --git a/1-js/05-data-types/11-json/json-meetup@2x.png b/1-js/05-data-types/11-json/json-meetup@2x.png index b5f6a4012..a8f2cd609 100644 Binary files a/1-js/05-data-types/11-json/json-meetup@2x.png and b/1-js/05-data-types/11-json/json-meetup@2x.png differ diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.png b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.png index c45418ff4..d0d37e35e 100644 Binary files a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.png and b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree.png differ diff --git a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree@2x.png b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree@2x.png index 6fc39ae13..3a937c64c 100644 Binary files a/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree@2x.png and b/1-js/06-advanced-functions/01-recursion/03-fibonacci-numbers/fibonacci-recursion-tree@2x.png differ diff --git a/1-js/06-advanced-functions/01-recursion/article.md b/1-js/06-advanced-functions/01-recursion/article.md index 036ae7f51..735c75bf3 100644 --- a/1-js/06-advanced-functions/01-recursion/article.md +++ b/1-js/06-advanced-functions/01-recursion/article.md @@ -319,7 +319,7 @@ let company = { In other words, a company has departments. - A department may have an array of staff. For instance, `sales` department has 2 employees: John and Alice. -- Or a department may split into subdepartments, like `development` has two branches: `sites` and `internals`. Each of them has the own staff. +- Or a department may split into subdepartments, like `development` has two branches: `sites` and `internals`. Each of them has their own staff. - It is also possible that when a subdepartment grows, it divides into subsubdepartments (or teams). For instance, the `sites` department in the future may be split into teams for `siteA` and `siteB`. And they, potentially, can split even more. That's not on the picture, just something to have in mind. diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-0.png b/1-js/06-advanced-functions/01-recursion/linked-list-0.png index 000a80da8..c694e7021 100644 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-0.png and b/1-js/06-advanced-functions/01-recursion/linked-list-0.png differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-0@2x.png b/1-js/06-advanced-functions/01-recursion/linked-list-0@2x.png index 5a2368694..1fe50ace4 100644 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-0@2x.png and b/1-js/06-advanced-functions/01-recursion/linked-list-0@2x.png differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.png b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.png index 477989ad8..b4c89ecdf 100644 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.png and b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1.png differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1@2x.png b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1@2x.png index 41de7661c..6b5b95a0f 100644 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-remove-1@2x.png and b/1-js/06-advanced-functions/01-recursion/linked-list-remove-1@2x.png differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-split.png b/1-js/06-advanced-functions/01-recursion/linked-list-split.png index ac2203490..310a36066 100644 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-split.png and b/1-js/06-advanced-functions/01-recursion/linked-list-split.png differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list-split@2x.png b/1-js/06-advanced-functions/01-recursion/linked-list-split@2x.png index 201c66f15..2de39ca4f 100644 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list-split@2x.png and b/1-js/06-advanced-functions/01-recursion/linked-list-split@2x.png differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list.png b/1-js/06-advanced-functions/01-recursion/linked-list.png index 64b6fb2b7..80fabffc6 100644 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list.png and b/1-js/06-advanced-functions/01-recursion/linked-list.png differ diff --git a/1-js/06-advanced-functions/01-recursion/linked-list@2x.png b/1-js/06-advanced-functions/01-recursion/linked-list@2x.png index c28fa8259..1e6dc1483 100644 Binary files a/1-js/06-advanced-functions/01-recursion/linked-list@2x.png and b/1-js/06-advanced-functions/01-recursion/linked-list@2x.png differ diff --git a/1-js/06-advanced-functions/01-recursion/recursion-pow.png b/1-js/06-advanced-functions/01-recursion/recursion-pow.png index 30577f89c..354f01271 100644 Binary files a/1-js/06-advanced-functions/01-recursion/recursion-pow.png and b/1-js/06-advanced-functions/01-recursion/recursion-pow.png differ diff --git a/1-js/06-advanced-functions/01-recursion/recursion-pow@2x.png b/1-js/06-advanced-functions/01-recursion/recursion-pow@2x.png index c19973420..22ae94410 100644 Binary files a/1-js/06-advanced-functions/01-recursion/recursion-pow@2x.png and b/1-js/06-advanced-functions/01-recursion/recursion-pow@2x.png differ diff --git a/1-js/06-advanced-functions/01-recursion/recursive-salaries.png b/1-js/06-advanced-functions/01-recursion/recursive-salaries.png index 2b9015409..b40783a9b 100644 Binary files a/1-js/06-advanced-functions/01-recursion/recursive-salaries.png and b/1-js/06-advanced-functions/01-recursion/recursive-salaries.png differ diff --git a/1-js/06-advanced-functions/01-recursion/recursive-salaries@2x.png b/1-js/06-advanced-functions/01-recursion/recursive-salaries@2x.png index 261ab144e..e37597128 100644 Binary files a/1-js/06-advanced-functions/01-recursion/recursive-salaries@2x.png and b/1-js/06-advanced-functions/01-recursion/recursive-salaries@2x.png differ diff --git a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md b/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md index 409c6dc1e..a98d8eddd 100644 --- a/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md +++ b/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md @@ -62,7 +62,7 @@ showName("Julius", "Caesar", "Consul", "Imperator"); ``` ````warn header="The rest parameters must be at the end" -The rest parameters gather all remaining arguments, so the following has no sense: +The rest parameters gather all remaining arguments, so the following does not make sense and causes an error: ```js function f(arg1, ...rest, arg2) { // arg2 after ...rest ?! diff --git a/1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md b/1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md index e8c8c465c..a6679cd20 100644 --- a/1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md +++ b/1-js/06-advanced-functions/03-closure/4-closure-sum/solution.md @@ -1,4 +1,4 @@ -For the second brackets to work, the first ones must return a function. +For the second parentheses to work, the first ones must return a function. Like this: diff --git a/1-js/06-advanced-functions/03-closure/4-closure-sum/task.md b/1-js/06-advanced-functions/03-closure/4-closure-sum/task.md index c2f3eabeb..b45758562 100644 --- a/1-js/06-advanced-functions/03-closure/4-closure-sum/task.md +++ b/1-js/06-advanced-functions/03-closure/4-closure-sum/task.md @@ -6,7 +6,7 @@ importance: 4 Write function `sum` that works like this: `sum(a)(b) = a+b`. -Yes, exactly this way, via double brackets (not a mistype). +Yes, exactly this way, using double parentheses (not a mistype). For instance: diff --git a/1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md b/1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md index 5bbc33b02..46c5514a8 100644 --- a/1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md +++ b/1-js/06-advanced-functions/03-closure/6-filter-through-function/solution.md @@ -14,7 +14,7 @@ alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6 # Filter inArray -```js run +```js run demo function inArray(arr) { return function(x) { return arr.includes(x); diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.png b/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.png index d51e8167f..8e39564ff 100644 Binary files a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.png and b/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy.png differ diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy@2x.png b/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy@2x.png index e70edbd6d..39cc13a42 100644 Binary files a/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy@2x.png and b/1-js/06-advanced-functions/03-closure/8-make-army/lexenv-makearmy@2x.png differ diff --git a/1-js/06-advanced-functions/03-closure/8-make-army/solution.md b/1-js/06-advanced-functions/03-closure/8-make-army/solution.md index 03c34b075..f5afd4262 100644 --- a/1-js/06-advanced-functions/03-closure/8-make-army/solution.md +++ b/1-js/06-advanced-functions/03-closure/8-make-army/solution.md @@ -55,9 +55,9 @@ function makeArmy() { As a result, all `shooter` functions get from the outer lexical envrironment the same, last value `i=10`. -The fix can be very simple: +We can fix it by moving the variable definition into the loop: -```js run +```js run demo function makeArmy() { let shooters = []; @@ -80,9 +80,9 @@ army[0](); // 0 army[5](); // 5 ``` -Now it works correctly, because every time the code block in `for (..) {...}` is executed, a new Lexical Environment is created for it, with the corresponding value of `i`. +Now it works correctly, because every time the code block in `for (let i=0...) {...}` is executed, a new Lexical Environment is created for it, with the corresponding variable `i`. -So, the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration. A `shooter` gets the value exactly from the one where it was created. +So, the value of `i` now lives a little bit closer. Not in `makeArmy()` Lexical Environment, but in the Lexical Environment that corresponds the current loop iteration. That's why now it works. ![](lexenv-makearmy.png) @@ -90,7 +90,6 @@ Here we rewrote `while` into `for`. Another trick could be possible, let's see it for better understanding of the subject: - ```js run function makeArmy() { let shooters = []; diff --git a/1-js/06-advanced-functions/03-closure/article.md b/1-js/06-advanced-functions/03-closure/article.md index b50d7ce0e..6f175a775 100644 --- a/1-js/06-advanced-functions/03-closure/article.md +++ b/1-js/06-advanced-functions/03-closure/article.md @@ -1,9 +1,9 @@ # Closure -JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created at one moment, then copied to another variable or passed as an argument to another function and called from a totally different place later. +JavaScript is a very function-oriented language. It gives us a lot of freedom. A function can be created dynamically, copied to another variable or passed as an argument to another function and called from a totally different place later. -We know that a function can access variables outside of it; this feature is used quite often. +We know that a function can access variables outside of it, this feature is used quite often. But what happens when an outer variable changes? Does a function get the most recent value or the one that existed when the function was created? @@ -63,24 +63,24 @@ Let's consider two situations to begin with, and then study the internal mechani To understand what's going on, let's first discuss what a "variable" actually is. -In JavaScript, every running function, code block, and the script as a whole have an associated object known as the *Lexical Environment*. +In JavaScript, every running function, code block `{...}`, and the script as a whole have an internal (hidden) associated object known as the *Lexical Environment*. The Lexical Environment object consists of two parts: -1. *Environment Record* -- an object that has all local variables as its properties (and some other information like the value of `this`). -2. A reference to the *outer lexical environment*, usually the one associated with the code lexically right outside of it (outside of the current curly brackets). +1. *Environment Record* -- an object that stores all local variables as its properties (and some other information like the value of `this`). +2. A reference to the *outer lexical environment*, the one associated with the outer code. -So, a "variable" is just a property of the special internal object, Environment Record. "To get or change a variable" means "to get or change a property of the Lexical Environment". +**So, a "variable" is just a property of the special internal object, `Environment Record`. "To get or change a variable" means "to get or change a property of that object".** For instance, in this simple code, there is only one Lexical Environment: ![lexical environment](lexical-environment-global.png) -This is a so-called global Lexical Environment, associated with the whole script. For browsers, all ` -Usually, it's not a good idea to use it, but here are some examples you can meet. + + ``` -1. To access exactly the global variable if the function has the local one with the same name. +4. And, a minor thing, but still: the value of `this` in the global scope is `window`. ```js untrusted run no-strict refresh - var user = "Global"; - - function sayHi() { - var user = "Local"; - - *!* - alert(window.user); // Global - */!* - } - - sayHi(); + alert(this); // window ``` - Such use is a workaround. Would be better to name variables differently, that won't require use to write the code it this way. And please note `"var"` before `user`. The trick doesn't work with `let` variables. +Why was it made like this? At the time of the language creation, the idea to merge multiple aspects into a single `window` object was to "make things simple". But since then many things changed. Tiny scripts became big applications that require proper architecture. -2. To check if a certain global variable or a builtin exists. +Is it good that different scripts (possibly from different sources) see variables of each other? - For instance, we want to check whether a global function `XMLHttpRequest` exists. +No, it's not, because it may lead to naming conflicts: the same variable name can be used in two scripts for different purposes, so they will conflict with each other. - We can't write `if (XMLHttpRequest)`, because if there's no `XMLHttpRequest`, there will be an error (variable not defined). +As of now, the multi-purpose `window` is considered a design mistake in the language. - But we can read it from `window.XMLHttpRequest`: +Luckily, there's a "road out of hell", called "JavaScript modules". - ```js run - if (window.XMLHttpRequest) { - alert('XMLHttpRequest exists!') - } - ``` +If we set `type="module"` attribute on a ` ``` - This doesn't use `window`, but is (theoretically) less reliable, because `typeof` may use a local XMLHttpRequest, and we want the global one. +- Two modules that do not see variables of each other: + ```html run + -3. To take the variable from the right window. That's probably the most valid use case. + + ``` - A browser may open multiple windows and tabs. A window may also embed another one in ` - - ``` - Here, first two alerts use the current window, and the latter two take variables from `iframe` window. Can be any variables if `iframe` originates from the same protocol/host/port. +**Using ` - - - - diff --git a/1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/source.view/index.html b/1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/source.view/index.html deleted file mode 100644 index fdee13d01..000000000 --- a/1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/source.view/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Console clock - - - - - - - - - - diff --git a/1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/task.md b/1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/task.md deleted file mode 100644 index 71131816b..000000000 --- a/1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/task.md +++ /dev/null @@ -1,9 +0,0 @@ -importance: 5 - ---- - -# Rewrite to prototypes - -The `Clock` class is written in functional style. Rewrite it using prototypes. - -P.S. The clock ticks in the console, open it to see. diff --git a/1-js/07-object-oriented-programming/08-class-patterns/article.md b/1-js/07-object-oriented-programming/08-class-patterns/article.md deleted file mode 100644 index 92b521c19..000000000 --- a/1-js/07-object-oriented-programming/08-class-patterns/article.md +++ /dev/null @@ -1,240 +0,0 @@ - -# Class patterns - -```quote author="Wikipedia" -In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods). -``` - -There's a special syntax construct and a keyword `class` in JavaScript. But before studying it, we should consider that the term "class" comes from the theory of object-oriented programming. The definition is cited above, and it's language-independent. - -In JavaScript there are several well-known programming patterns to make classes even without using the `class` keyword. And here we'll talk about them first. - -The `class` construct will be described in the next chapter, but in JavaScript it's a "syntax sugar" and an extension of one of the patterns that we'll study here. - - -## Functional class pattern - -The constructor function below can be considered a "class" according to the definition: - -```js run -function User(name) { - this.sayHi = function() { - alert(name); - }; -} - -let user = new User("John"); -user.sayHi(); // John -``` - -It follows all parts of the definition: - -1. It is a "program-code-template" for creating objects (callable with `new`). -2. It provides initial values for the state (`name` from parameters). -3. It provides methods (`sayHi`). - -This is called *functional class pattern*. - -In the functional class pattern, local variables and nested functions inside `User`, that are not assigned to `this`, are visible from inside, but not accessible by the outer code. - -So we can easily add internal functions and variables, like `calcAge()` here: - -```js run -function User(name, birthday) { -*!* - // only visible from other methods inside User - function calcAge() { - return new Date().getFullYear() - birthday.getFullYear(); - } -*/!* - - this.sayHi = function() { - alert(`${name}, age:${calcAge()}`); - }; -} - -let user = new User("John", new Date(2000, 0, 1)); -user.sayHi(); // John, age:17 -``` - -In this code variables `name`, `birthday` and the function `calcAge()` are internal, *private* to the object. They are only visible from inside of it. - -On the other hand, `sayHi` is the external, *public* method. The external code that creates `user` can access it. - -This way we can hide internal implementation details and helper methods from the outer code. Only what's assigned to `this` becomes visible outside. - -## Factory class pattern - -We can create a class without using `new` at all. - -Like this: - -```js run -function User(name, birthday) { - // only visible from other methods inside User - function calcAge() { - return new Date().getFullYear() - birthday.getFullYear(); - } - - return { - sayHi() { - alert(`${name}, age:${calcAge()}`); - } - }; -} - -*!* -let user = User("John", new Date(2000, 0, 1)); -*/!* -user.sayHi(); // John, age:17 -``` - -As we can see, the function `User` returns an object with public properties and methods. The only benefit of this method is that we can omit `new`: write `let user = User(...)` instead of `let user = new User(...)`. In other aspects it's almost the same as the functional pattern. - -## Prototype-based classes - -Prototype-based classes are the most important and generally the best. Functional and factory class patterns are rarely used in practice. - -Soon you'll see why. - -Here's the same class rewritten using prototypes: - -```js run -function User(name, birthday) { -*!* - this._name = name; - this._birthday = birthday; -*/!* -} - -*!* -User.prototype._calcAge = function() { -*/!* - return new Date().getFullYear() - this._birthday.getFullYear(); -}; - -User.prototype.sayHi = function() { - alert(`${this._name}, age:${this._calcAge()}`); -}; - -let user = new User("John", new Date(2000, 0, 1)); -user.sayHi(); // John, age:17 -``` - -The code structure: - -- The constructor `User` only initializes the current object state. -- Methods are added to `User.prototype`. - -As we can see, methods are lexically not inside `function User`, they do not share a common lexical environment. If we declare variables inside `function User`, then they won't be visible to methods. - -So, there is a widely known agreement that internal properties and methods are prepended with an underscore `"_"`. Like `_name` or `_calcAge()`. Technically, that's just an agreement, the outer code still can access them. But most developers recognize the meaning of `"_"` and try not to touch prefixed properties and methods in the external code. - -Here are the advantages over the functional pattern: - -- In the functional pattern, each object has its own copy of every method. We assign a separate copy of `this.sayHi = function() {...}` and other methods in the constructor. -- In the prototypal pattern, all methods are in `User.prototype` that is shared between all user objects. An object itself only stores the data. - -So the prototypal pattern is more memory-efficient. - -...But not only that. Prototypes allow us to setup the inheritance in a really efficient way. Built-in JavaScript objects all use prototypes. Also there's a special syntax construct: "class" that provides nice-looking syntax for them. And there's more, so let's go on with them. - -## Prototype-based inheritance for classes - -Let's say we have two prototype-based classes. - -`Rabbit`: - -```js -function Rabbit(name) { - this.name = name; -} - -Rabbit.prototype.jump = function() { - alert(`${this.name} jumps!`); -}; - -let rabbit = new Rabbit("My rabbit"); -``` - -![](rabbit-animal-independent-1.png) - -...And `Animal`: - -```js -function Animal(name) { - this.name = name; -} - -Animal.prototype.eat = function() { - alert(`${this.name} eats.`); -}; - -let animal = new Animal("My animal"); -``` - -![](rabbit-animal-independent-2.png) - -Right now they are fully independent. - -But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods. - -What does it mean in the language of prototypes? - -Right now methods for `rabbit` objects are in `Rabbit.prototype`. We'd like `rabbit` to use `Animal.prototype` as a "fallback", if the method is not found in `Rabbit.prototype`. - -So the prototype chain should be `rabbit` -> `Rabbit.prototype` -> `Animal.prototype`. - -Like this: - -![](class-inheritance-rabbit-animal.png) - -The code to implement that: - -```js run -// Same Animal as before -function Animal(name) { - this.name = name; -} - -// All animals can eat, right? -Animal.prototype.eat = function() { - alert(`${this.name} eats.`); -}; - -// Same Rabbit as before -function Rabbit(name) { - this.name = name; -} - -Rabbit.prototype.jump = function() { - alert(`${this.name} jumps!`); -}; - -*!* -// setup the inheritance chain -Rabbit.prototype.__proto__ = Animal.prototype; // (*) -*/!* - -let rabbit = new Rabbit("White Rabbit"); -*!* -rabbit.eat(); // rabbits can eat too -*/!* -rabbit.jump(); -``` - -The line `(*)` sets up the prototype chain. So that `rabbit` first searches methods in `Rabbit.prototype`, then `Animal.prototype`. And then, just for completeness, let's mention that if the method is not found in `Animal.prototype`, then the search continues in `Object.prototype`, because `Animal.prototype` is a regular plain object, so it inherits from it. - -So here's the full picture: - -![](class-inheritance-rabbit-animal-2.png) - -## Summary - -The term "class" comes from the object-oriented programming. In JavaScript it usually means the functional class pattern or the prototypal pattern. The prototypal pattern is more powerful and memory-efficient, so it's recommended to stick to it. - -According to the prototypal pattern: -1. Methods are stored in `Class.prototype`. -2. Prototypes inherit from each other. - -In the next chapter we'll study `class` keyword and construct. It allows to write prototypal classes shorter and provides some additional benefits. diff --git a/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal-2.png b/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal-2.png deleted file mode 100644 index ad4a40932..000000000 Binary files a/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal-2.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal-2@2x.png b/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal-2@2x.png deleted file mode 100644 index 199ed3ee6..000000000 Binary files a/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal-2@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal.png b/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal.png deleted file mode 100644 index 70708c284..000000000 Binary files a/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal@2x.png b/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal@2x.png deleted file mode 100644 index 0db130181..000000000 Binary files a/1-js/07-object-oriented-programming/08-class-patterns/class-inheritance-rabbit-animal@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-1.png b/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-1.png deleted file mode 100644 index e63d7d784..000000000 Binary files a/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-1.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-1@2x.png b/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-1@2x.png deleted file mode 100644 index 3d1be9cce..000000000 Binary files a/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-1@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-2.png b/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-2.png deleted file mode 100644 index 435ec5f89..000000000 Binary files a/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-2.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-2@2x.png b/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-2@2x.png deleted file mode 100644 index 5731da73a..000000000 Binary files a/1-js/07-object-oriented-programming/08-class-patterns/rabbit-animal-independent-2@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/solution.view/index.html b/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/solution.view/index.html deleted file mode 100644 index fdee13d01..000000000 --- a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/solution.view/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Console clock - - - - - - - - - - diff --git a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/source.view/clock.js b/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/source.view/clock.js deleted file mode 100644 index d312c93ea..000000000 --- a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/source.view/clock.js +++ /dev/null @@ -1,34 +0,0 @@ - - -function Clock({ template }) { - this._template = template; -} - -Clock.prototype._render = function() { - let date = new Date(); - - let hours = date.getHours(); - if (hours < 10) hours = '0' + hours; - - let mins = date.getMinutes(); - if (mins < 10) mins = '0' + mins; - - let secs = date.getSeconds(); - if (secs < 10) secs = '0' + secs; - - let output = this._template - .replace('h', hours) - .replace('m', mins) - .replace('s', secs); - - console.log(output); -}; - -Clock.prototype.stop = function() { - clearInterval(this._timer); -}; - -Clock.prototype.start = function() { - this._render(); - this._timer = setInterval(() => this._render(), 1000); -}; diff --git a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/source.view/index.html b/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/source.view/index.html deleted file mode 100644 index fdee13d01..000000000 --- a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/source.view/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Console clock - - - - - - - - - - diff --git a/1-js/07-object-oriented-programming/09-class/article.md b/1-js/07-object-oriented-programming/09-class/article.md deleted file mode 100644 index 71878ae35..000000000 --- a/1-js/07-object-oriented-programming/09-class/article.md +++ /dev/null @@ -1,355 +0,0 @@ - -# Classes - -The "class" construct allows to define prototype-based classes with a clean, nice-looking syntax. - -## The "class" syntax - -The `class` syntax is versatile, we'll start with a simple example first. - -Here's a prototype-based class `User`: - -```js run -function User(name) { - this.name = name; -} - -User.prototype.sayHi = function() { - alert(this.name); -} - -let user = new User("John"); -user.sayHi(); -``` - -...And that's the same using `class` syntax: - -```js run -class User { - - constructor(name) { - this.name = name; - } - - sayHi() { - alert(this.name); - } - -} - -let user = new User("John"); -user.sayHi(); -``` - -It's easy to see that the two examples are alike. Just please note that methods in a class do not have a comma between them. Novice developers sometimes forget it and put a comma between class methods, and things don't work. That's not a literal object, but a class syntax. - -So, what exactly does `class` do? We may think that it defines a new language-level entity, but that would be wrong. - -The `class User {...}` here actually does two things: - -1. Declares a variable `User` that references the function named `"constructor"`. -2. Puts methods listed in the definition into `User.prototype`. Here, it includes `sayHi` and the `constructor`. - -Here's the code to dig into the class and see that: - -```js run -class User { - constructor(name) { this.name = name; } - sayHi() { alert(this.name); } -} - -*!* -// proof: User is the "constructor" function -*/!* -alert(User === User.prototype.constructor); // true - -*!* -// proof: there are two methods in its "prototype" -*/!* -alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi -``` - -Here's the illustration of what `class User` creates: - -![](class-user.png) - - - -So `class` is a special syntax to define a constructor together with its prototype methods. - -...But not only that. There are minor tweaks here and there: - -Constructors require `new` -: Unlike a regular function, a class `constructor` can't be called without `new`: - -```js run -class User { - constructor() {} -} - -alert(typeof User); // function -User(); // Error: Class constructor User cannot be invoked without 'new' -``` - -Different string output -: If we output it like `alert(User)`, some engines show `"class User..."`, while others show `"function User..."`. - -Please don't be confused: the string representation may vary, but that's still a function, there is no separate "class" entity in JavaScript language. - -Class methods are non-enumerable -: A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. That's good, because if we `for..in` over an object, we usually don't want its class methods. - -Classes have a default `constructor() {}` -: If there's no `constructor` in the `class` construct, then an empty function is generated, same as if we had written `constructor() {}`. - -Classes always `use strict` -: All code inside the class construct is automatically in strict mode. - -### Getters/setters - -Classes may also include getters/setters. Here's an example with `user.name` implemented using them: - -```js run -class User { - - constructor(name) { - // invokes the setter - this.name = name; - } - -*!* - get name() { -*/!* - return this._name; - } - -*!* - set name(value) { -*/!* - if (value.length < 4) { - alert("Name is too short."); - return; - } - this._name = value; - } - -} - -let user = new User("John"); -alert(user.name); // John - -user = new User(""); // Name too short. -``` - -Internally, getters and setters are also created on the `User` prototype, like this: - -```js -Object.defineProperties(User.prototype, { - name: { - get() { - return this._name - }, - set(name) { - // ... - } - } -}); -``` - -### Only methods - -Unlike object literals, no `property:value` assignments are allowed inside `class`. There may be only methods and getters/setters. There is some work going on in the specification to lift that limitation, but it's not yet there. - -If we really need to put a non-function value into the prototype, then we can alter `prototype` manually, like this: - -```js run -class User { } - -User.prototype.test = 5; - -alert( new User().test ); // 5 -``` - -So, technically that's possible, but we should know why we're doing it. Such properties will be shared among all objects of the class. - -An "in-class" alternative is to use a getter: - -```js run -class User { - get test() { - return 5; - } -} - -alert( new User().test ); // 5 -``` - -From the external code, the usage is the same. But the getter variant is a bit slower. - -## Class Expression - -Just like functions, classes can be defined inside another expression, passed around, returned etc. - -Here's a class-returning function ("class factory"): - -```js run -function makeClass(phrase) { -*!* - // declare a class and return it - return class { - sayHi() { - alert(phrase); - }; - }; -*/!* -} - -let User = makeClass("Hello"); - -new User().sayHi(); // Hello -``` - -That's quite normal if we recall that `class` is just a special form of a function-with-prototype definition. - -And, like Named Function Expressions, such classes also may have a name, that is visible inside that class only: - -```js run -// "Named Class Expression" (alas, no such term, but that's what's going on) -let User = class *!*MyClass*/!* { - sayHi() { - alert(MyClass); // MyClass is visible only inside the class - } -}; - -new User().sayHi(); // works, shows MyClass definition - -alert(MyClass); // error, MyClass not visible outside of the class -``` - -## Static methods - -We can also assign methods to the class function, not to its `"prototype"`. Such methods are called *static*. - -An example: - -```js run -class User { -*!* - static staticMethod() { -*/!* - alert(this === User); - } -} - -User.staticMethod(); // true -``` - -That actually does the same as assigning it as a function property: - -```js -function User() { } - -User.staticMethod = function() { - alert(this === User); -}; -``` - -The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule). - -Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it. - -For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this: - -```js run -class Article { - constructor(title, date) { - this.title = title; - this.date = date; - } - -*!* - static compare(articleA, articleB) { - return articleA.date - articleB.date; - } -*/!* -} - -// usage -let articles = [ - new Article("Mind", new Date(2016, 1, 1)), - new Article("Body", new Date(2016, 0, 1)), - new Article("JavaScript", new Date(2016, 11, 1)) -]; - -*!* -articles.sort(Article.compare); -*/!* - -alert( articles[0].title ); // Body -``` - -Here `Article.compare` stands "over" the articles, as a means to compare them. It's not a method of an article, but rather of the whole class. - -Another example would be a so-called "factory" method. Imagine, we need few ways to create an article: - -1. Create by given parameters (`title`, `date` etc). -2. Create an empty article with today's date. -3. ... - -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: - -```js run -class Article { - constructor(title, date) { - this.title = title; - this.date = date; - } - -*!* - static createTodays() { - // remember, this = Article - return new this("Today's digest", new Date()); - } -*/!* -} - -let article = Article.createTodays(); - -alert( article.title ); // Todays digest -``` - -Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class. - -Static methods are also used in database-related classes to search/save/remove entries from the database, like this: - -```js -// assuming Article is a special class for managing articles -// static method to remove the article: -Article.remove({id: 12345}); -``` - -## Summary - -The basic class syntax looks like this: - -```js -class MyClass { - constructor(...) { - // ... - } - method1(...) {} - method2(...) {} - get something(...) {} - set something(...) {} - static staticMethod(..) {} - // ... -} -``` - -The value of `MyClass` is a function provided as `constructor`. If there's no `constructor`, then an empty function. - -In any case, methods listed in the class declaration become members of its `prototype`, with the exception of static methods that are written into the function itself and callable as `MyClass.staticMethod()`. Static methods are used when we need a function bound to a class, but not to any object of that class. - -In the next chapter we'll learn more about classes, including inheritance. diff --git a/1-js/07-object-oriented-programming/09-class/class-user.png b/1-js/07-object-oriented-programming/09-class/class-user.png deleted file mode 100644 index 5579e6bbd..000000000 Binary files a/1-js/07-object-oriented-programming/09-class/class-user.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/09-class/class-user@2x.png b/1-js/07-object-oriented-programming/09-class/class-user@2x.png deleted file mode 100644 index 5a85e6589..000000000 Binary files a/1-js/07-object-oriented-programming/09-class/class-user@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/index.html b/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/index.html deleted file mode 100644 index 7ac1db714..000000000 --- a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - Console clock - - - - - - - - - - - diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/source.view/index.html b/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/source.view/index.html deleted file mode 100644 index b48a2a007..000000000 --- a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/source.view/index.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - Console clock - - - - - - - - - - - diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/rabbit-extends-object.png b/1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/rabbit-extends-object.png deleted file mode 100644 index d4ff37e56..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/rabbit-extends-object.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png b/1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png deleted file mode 100644 index a54a9d2f8..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-extends.png b/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-extends.png deleted file mode 100644 index 2db88f366..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-extends.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-extends@2x.png b/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-extends@2x.png deleted file mode 100644 index 9539fe9ec..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-extends@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-static.png b/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-static.png deleted file mode 100644 index 998c82330..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-static.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-static@2x.png b/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-static@2x.png deleted file mode 100644 index 98a80d38f..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/animal-rabbit-static@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-array-object.png b/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-array-object.png deleted file mode 100644 index c5d712632..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-array-object.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-array-object@2x.png b/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-array-object@2x.png deleted file mode 100644 index edc4e841e..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-array-object@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-animal.png b/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-animal.png deleted file mode 100644 index 70708c284..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-animal.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-animal@2x.png b/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-animal@2x.png deleted file mode 100644 index 0db130181..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-animal@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/object-date-inheritance.png b/1-js/07-object-oriented-programming/10-class-inheritance/object-date-inheritance.png deleted file mode 100644 index 542a0c9fa..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/object-date-inheritance.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/object-date-inheritance@2x.png b/1-js/07-object-oriented-programming/10-class-inheritance/object-date-inheritance@2x.png deleted file mode 100644 index 21485062a..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/object-date-inheritance@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/this-super-loop.png b/1-js/07-object-oriented-programming/10-class-inheritance/this-super-loop.png deleted file mode 100644 index 637d17939..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/this-super-loop.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/this-super-loop@2x.png b/1-js/07-object-oriented-programming/10-class-inheritance/this-super-loop@2x.png deleted file mode 100644 index af7b443bc..000000000 Binary files a/1-js/07-object-oriented-programming/10-class-inheritance/this-super-loop@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/11-instanceof/instanceof.png b/1-js/07-object-oriented-programming/11-instanceof/instanceof.png deleted file mode 100644 index 85aa9a55f..000000000 Binary files a/1-js/07-object-oriented-programming/11-instanceof/instanceof.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/11-instanceof/instanceof@2x.png b/1-js/07-object-oriented-programming/11-instanceof/instanceof@2x.png deleted file mode 100644 index fba771220..000000000 Binary files a/1-js/07-object-oriented-programming/11-instanceof/instanceof@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/13-mixins/mixin-inheritance.png b/1-js/07-object-oriented-programming/13-mixins/mixin-inheritance.png deleted file mode 100644 index 7cc655036..000000000 Binary files a/1-js/07-object-oriented-programming/13-mixins/mixin-inheritance.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/13-mixins/mixin-inheritance@2x.png b/1-js/07-object-oriented-programming/13-mixins/mixin-inheritance@2x.png deleted file mode 100644 index f53ecf68f..000000000 Binary files a/1-js/07-object-oriented-programming/13-mixins/mixin-inheritance@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/index.md b/1-js/07-object-oriented-programming/index.md deleted file mode 100644 index 7053ada81..000000000 --- a/1-js/07-object-oriented-programming/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Objects, classes, inheritance - -In this section we return to objects and learn them even more in-depth. diff --git a/1-js/07-object-oriented-programming/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md similarity index 94% rename from 1-js/07-object-oriented-programming/01-property-descriptors/article.md rename to 1-js/07-object-properties/01-property-descriptors/article.md index 14d91800f..7768b3557 100644 --- a/1-js/07-object-oriented-programming/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -3,7 +3,9 @@ As we know, objects can store properties. -Till now, a property was a simple "key-value" pair to us. But an object property is actually a more complex and tunable thing. +Till now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing. + +In this chapter we'll study additional configuration options, and in the next we'll see how to invisibly turn them into getter/setter functions. ## Property flags @@ -296,14 +298,13 @@ 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) -: Forbids to add properties to the object. +: Forbids the addition of new properties to the object. [Object.seal(obj)](mdn:js/Object/seal) -: Forbids to add/remove properties, sets for all existing properties `configurable: false`. +: Forbids adding/removing of properties. Sets `configurable: false` for all existing properties. [Object.freeze(obj)](mdn:js/Object/freeze) -: Forbids to add/remove/change properties, sets for all existing properties `configurable: false, writable: false`. - +: Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties. And also there are tests for them: [Object.isExtensible(obj)](mdn:js/Object/isExtensible) diff --git a/1-js/07-object-oriented-programming/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md similarity index 94% rename from 1-js/07-object-oriented-programming/02-property-accessors/article.md rename to 1-js/07-object-properties/02-property-accessors/article.md index fa4d2216d..5b58b584b 100644 --- a/1-js/07-object-oriented-programming/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -85,11 +85,12 @@ alert(user.surname); // Cooper Now we have a "virtual" property. It is readable and writable, but in fact does not exist. ```smart header="Accessor properties are only accessible with get/set" -A property can either be a "data property" or an "accessor property", but not both. +Once a property is defined with `get prop()` or `set prop()`, it's an accessor property, not a data property any more. -Once a property is defined with `get prop()` or `set prop()`, it's an accessor property. So there must be a getter to read it, and must be a setter if we want to assign it. +- If there's a getter -- we can read `object.prop`, otherwise we can't. +- If there's a setter -- we can set `object.prop=...`, otherwise we can't. -Sometimes it's normal that there's only a setter or only a getter. But the property won't be readable or writable in that case. +And in either case we can't `delete` an accessor property. ``` diff --git a/1-js/07-object-properties/index.md b/1-js/07-object-properties/index.md new file mode 100644 index 000000000..67fcccaff --- /dev/null +++ b/1-js/07-object-properties/index.md @@ -0,0 +1,3 @@ +# Object properties configuration + +In this section we return to objects and study their properties even more in-depth. diff --git a/1-js/08-error-handling/1-try-catch/try-catch-flow.png b/1-js/08-error-handling/1-try-catch/try-catch-flow.png deleted file mode 100644 index 6a91b6329..000000000 Binary files a/1-js/08-error-handling/1-try-catch/try-catch-flow.png and /dev/null differ diff --git a/1-js/08-error-handling/1-try-catch/try-catch-flow@2x.png b/1-js/08-error-handling/1-try-catch/try-catch-flow@2x.png deleted file mode 100644 index 8bf9680fd..000000000 Binary files a/1-js/08-error-handling/1-try-catch/try-catch-flow@2x.png and /dev/null differ diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/1-property-after-delete/solution.md b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/1-property-after-delete/solution.md rename to 1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/solution.md diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/1-property-after-delete/task.md b/1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/1-property-after-delete/task.md rename to 1-js/08-prototypes/01-prototype-inheritance/1-property-after-delete/task.md diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/2-search-algorithm/solution.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/2-search-algorithm/solution.md rename to 1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/solution.md diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/2-search-algorithm/task.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/2-search-algorithm/task.md rename to 1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/3-proto-and-this/solution.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/3-proto-and-this/solution.md rename to 1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/solution.md diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/3-proto-and-this/task.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/3-proto-and-this/task.md rename to 1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/4-hamster-proto/solution.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/4-hamster-proto/solution.md rename to 1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/solution.md diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/4-hamster-proto/task.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/4-hamster-proto/task.md rename to 1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md diff --git a/1-js/07-object-oriented-programming/03-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md similarity index 58% rename from 1-js/07-object-oriented-programming/03-prototype-inheritance/article.md rename to 1-js/08-prototypes/01-prototype-inheritance/article.md index fa61cbc89..c4dd1aacb 100644 --- a/1-js/07-object-oriented-programming/03-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -12,7 +12,7 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named ![prototype](object-prototype-empty.png) -That `[[Prototype]]` has a "magical" meaning. When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it. +The prototype is a little bit "magical". When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it. The property `[[Prototype]]` is internal and hidden, but there are many ways to set it. @@ -31,7 +31,13 @@ rabbit.__proto__ = animal; */!* ``` -Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. We'll talk about other ways of setting it later, but for now `__proto__` will do just fine. +```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" +Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. + +It exists for historical reasons, in modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later. + +By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples. +``` If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`. @@ -62,7 +68,7 @@ Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rab ![](proto-animal-rabbit.png) -Here we can say that "`animal` is the prototype of `rabbit`" or "`rabbit` prototypally inherits from `animal`". +Here we can say that "`animal` is the prototype of `rabbit`" or "`rabbit` prototypically inherits from `animal`". So if `animal` has a lot of useful properties and methods, then they become automatically available in `rabbit`. Such properties are called "inherited". @@ -106,13 +112,17 @@ let animal = { let rabbit = { jumps: true, +*!* __proto__: animal +*/!* }; let longEar = { earLength: 10, +*!* __proto__: rabbit -} +*/!* +}; // walk is taken from the prototype chain longEar.walk(); // Animal walk @@ -124,15 +134,15 @@ alert(longEar.jumps); // true (from rabbit) There are actually only two limitations: 1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle. -2. The value of `__proto__` can be either an object or `null`. All other values (like primitives) are ignored. +2. The value of `__proto__` can be either an object or `null`, other types (like primitives) are ignored. Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others. -## Read/write rules +## Writing doesn't use prototype The prototype is only used for reading properties. -For data properties (not getters/setters) write/delete operations work directly with the object. +Write/delete operations work directly with the object. In the example below, we assign its own `walk` method to `rabbit`: @@ -146,7 +156,7 @@ let animal = { let rabbit = { __proto__: animal -} +}; *!* rabbit.walk = function() { @@ -161,9 +171,9 @@ From now on, `rabbit.walk()` call finds the method immediately in the object and ![](proto-animal-rabbit-walk-2.png) -For getters/setters -- if we read/write a property, they are looked up in the prototype and invoked. +That's for data properties only, not for accessors. If a property is a getter/setter, then it behaves like a function: getters/setters are looked up in the prototype. -For instance, check out `admin.fullName` property in the code below: +For that reason `admin.fullName` works correctly in the code below: ```js run let user = { @@ -194,15 +204,15 @@ Here in the line `(*)` the property `admin.fullName` has a getter in the prototy ## The value of "this" -An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where the properties `this.name` and `this.surname` are written: `user` or `admin`? +An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where the properties `this.name` and `this.surname` are written: into `user` or `admin`? The answer is simple: `this` is not affected by prototypes at all. **No matter where the method is found: in an object or its prototype. In a method call, `this` is always the object before the dot.** -So, the setter actually uses `admin` as `this`, not `user`. +So, the setter call `admin.fullName=` uses `admin` as `this`, not `user`. -That is actually a super-important thing, because we may have a big object with many methods and inherit from it. Then we can run its methods on inherited objects and they will modify the state of these objects, not the big one. +That is actually a super-important thing, because we may have a big object with many methods and inherit from it. Then inherited objects can run its methods, and they will modify the state of these objects, not the big one. For instance, here `animal` represents a "method storage", and `rabbit` makes use of it. @@ -241,10 +251,80 @@ If we had other objects like `bird`, `snake` etc inheriting from `animal`, they As a result, methods are shared, but the object state is not. +## for..in loop + +The `for..in` loops over inherited properties too. + +For instance: + +```js run +let animal = { + eats: true +}; + +let rabbit = { + jumps: true, + __proto__: animal +}; + +*!* +// Object.keys only return own keys +alert(Object.keys(rabbit)); // jumps +*/!* + +*!* +// for..in loops over both own and inherited keys +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`. + +So we can filter out inherited properties (or do something else with them): + +```js run +let animal = { + eats: true +}; + +let rabbit = { + jumps: true, + __proto__: animal +}; + +for(let prop in rabbit) { + let isOwn = rabbit.hasOwnProperty(prop); + + if (isOwn) { + alert(`Our: ${prop}`); // Our: jumps + } else { + alert(`Inherited: ${prop}`); // Inherited: eats + } +} +``` + +Here we have the following inheritance chain: `rabbit` inherits from `animal`, that inherits from `Object.prototype` (because `animal` is a literal object `{...}`, so it's by default), and then `null` above it: + +![](rabbit-animal-object.png) + +Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? We did not define it. Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited. + +...But why `hasOwnProperty` does not appear in `for..in` loop, like `eats` and `jumps`, if it lists all inherited properties. + +The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`, it has `enumerable:false` flag. That's why they are not listed. + +```smart header="All other iteration methods ignore inherited properties" +All other key/value-getting methods, such as `Object.keys`, `Object.values` and so on ignore inherited properties. + +They only operate on the object itself. Properties from the prototype are taken into account. +``` + ## Summary - In JavaScript, all objects have a hidden `[[Prototype]]` property that's either another object or `null`. -- We can use `obj.__proto__` to access it (there are other ways too, to be covered soon). +- We can use `obj.__proto__` to access it (a historical getter/setter, there are other ways, to be covered soon). - The object referenced by `[[Prototype]]` is called a "prototype". -- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype. Write/delete operations work directly on the object, they don't use the prototype (unless the property is actually a setter). +- If we want to read a property of `obj` or call a method, and it doesn't exist, then JavaScript tries to find it in the prototype. +- Write/delete operations for act directly on the object, they don't use the prototype (assuming it's a data property, not is a setter). - If we call `obj.method()`, and the `method` is taken from the prototype, `this` still references `obj`. So methods always work with the current object even if they are inherited. +- The `for..in` loop iterates over both own and inherited properties. All other key/value-getting methods only operate on the object itself. diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png new file mode 100644 index 000000000..3ecff3fdd Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png new file mode 100644 index 000000000..f80e61e68 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty@2x.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png new file mode 100644 index 000000000..48e02ad18 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png new file mode 100644 index 000000000..1a04db885 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain@2x.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png new file mode 100644 index 000000000..22b867fc8 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png new file mode 100644 index 000000000..8d9f9f1ae Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2@2x.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png new file mode 100644 index 000000000..bc76d64ba Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png new file mode 100644 index 000000000..725a4c150 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3@2x.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png new file mode 100644 index 000000000..29fc6d503 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png new file mode 100644 index 000000000..724d6111b Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk@2x.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png new file mode 100644 index 000000000..d50327f0e Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png new file mode 100644 index 000000000..0506b92b6 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit@2x.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png new file mode 100644 index 000000000..c37d00780 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png new file mode 100644 index 000000000..8c3f546eb Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin@2x.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.png b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.png new file mode 100644 index 000000000..3924233dd Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.png differ diff --git a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object@2x.png b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object@2x.png new file mode 100644 index 000000000..5350a1935 Binary files /dev/null and b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object@2x.png differ diff --git a/1-js/07-object-oriented-programming/04-function-prototype/1-changing-prototype/solution.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/04-function-prototype/1-changing-prototype/solution.md rename to 1-js/08-prototypes/02-function-prototype/1-changing-prototype/solution.md diff --git a/1-js/07-object-oriented-programming/04-function-prototype/1-changing-prototype/task.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/04-function-prototype/1-changing-prototype/task.md rename to 1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md diff --git a/1-js/07-object-oriented-programming/04-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/04-function-prototype/4-new-object-same-constructor/solution.md rename to 1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md diff --git a/1-js/07-object-oriented-programming/04-function-prototype/4-new-object-same-constructor/task.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/04-function-prototype/4-new-object-same-constructor/task.md rename to 1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/task.md diff --git a/1-js/07-object-oriented-programming/04-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md similarity index 75% rename from 1-js/07-object-oriented-programming/04-function-prototype/article.md rename to 1-js/08-prototypes/02-function-prototype/article.md index d4b453850..2089e96cf 100644 --- a/1-js/07-object-oriented-programming/04-function-prototype/article.md +++ b/1-js/08-prototypes/02-function-prototype/article.md @@ -1,18 +1,14 @@ # F.prototype -In modern JavaScript we can set a prototype using `__proto__`, as described in the previous article. But it wasn't like that all the time. +Remember, new objects can be created with a constructor function, like `new F()`. -JavaScript has had prototypal inheritance from the beginning. It was one of the core features of the language. +If `F.prototype` is an object, then `new` operator uses it to set `[[Prototype]]` for the new object. -But in the old times, there was another (and the only) way to set it: to use a `"prototype"` property of the constructor function. And there are still many scripts that use it. +```smart +JavaScript had prototypal inheritance from the beginning. It was one of the core features of the language. -## The "prototype" property - -As we know already, `new F()` creates a new object. - -When a new object is created with `new F()`, the object's `[[Prototype]]` is set to `F.prototype`. - -In other words, if `F` has a `prototype` property with a value of the object type, then `new` operator uses it to set `[[Prototype]]` for the new object. +But in the old times, there was no direct access to it. The only thing that worked reliably was a `"prototype"` property of the constructor function, described in this chapter. So there are many scripts that still use it. +``` Please note that `F.prototype` here means a regular property named `"prototype"` on `F`. It sounds something similar to the term "prototype", but here we really mean a regular property with this name. @@ -42,8 +38,13 @@ That's the resulting picture: ![](proto-constructor-animal-rabbit.png) -On the picture, `"prototype"` is a horizontal arrow, it's a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`. +On the picture, `"prototype"` is a horizontal arrow, meaning a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`. + +```smart header="`F.prototype` only used at `new F` time" +`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. After that, there's no connection between `F.prototype` and the new object. Think of it as a "one-time gift". +If, after the creation, `F.prototype` property changes (`F.prototype = `), then new objects created by `new F` will have another object as `[[Prototype]]`, but already existing objects keep the old one. +``` ## Default F.prototype, constructor property @@ -139,7 +140,7 @@ Rabbit.prototype.jumps = true // the default Rabbit.prototype.constructor is preserved ``` -Or, alternatively, recreate the `constructor` property it manually: +Or, alternatively, recreate the `constructor` property manually: ```js Rabbit.prototype = { @@ -161,7 +162,7 @@ Everything is quite simple, just few notes to make things clear: - The `F.prototype` property is not the same as `[[Prototype]]`. The only thing `F.prototype` does: it sets `[[Prototype]]` of new objects when `new F()` is called. - The value of `F.prototype` should be either an object or null: other values won't work. -- The `"prototype"` property only has such a special effect when is set to a constructor function, and invoked with `new`. +- The `"prototype"` property only has such a special effect when set on a constructor function, and invoked with `new`. On regular objects the `prototype` is nothing special: ```js diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png new file mode 100644 index 000000000..6ab9c6934 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.png differ diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png new file mode 100644 index 000000000..3beb0a573 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor@2x.png differ diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png new file mode 100644 index 000000000..c47938674 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring.png differ diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png new file mode 100644 index 000000000..293c88bf9 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/native-prototypes-array-tostring@2x.png differ diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png new file mode 100644 index 000000000..c4658a929 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes.png differ diff --git a/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png new file mode 100644 index 000000000..79445fac9 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/native-prototypes-classes@2x.png differ diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype-1.png b/1-js/08-prototypes/02-function-prototype/object-prototype-1.png new file mode 100644 index 000000000..a69cad4a1 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/object-prototype-1.png differ diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png b/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png new file mode 100644 index 000000000..9d661ac17 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/object-prototype-1@2x.png differ diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype.png b/1-js/08-prototypes/02-function-prototype/object-prototype.png new file mode 100644 index 000000000..820ffcc73 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/object-prototype.png differ diff --git a/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png b/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png new file mode 100644 index 000000000..1eb7e8749 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/object-prototype@2x.png differ diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png new file mode 100644 index 000000000..4b381460a Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.png differ diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png new file mode 100644 index 000000000..c6e6ab5f6 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit@2x.png differ diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png new file mode 100644 index 000000000..3924233dd Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object.png differ diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png new file mode 100644 index 000000000..5350a1935 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/rabbit-animal-object@2x.png differ diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png new file mode 100644 index 000000000..8f3519a74 Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.png differ diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png new file mode 100644 index 000000000..07acb8f4a Binary files /dev/null and b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor@2x.png differ diff --git a/1-js/07-object-oriented-programming/05-native-prototypes/1-defer-to-prototype/solution.md b/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/05-native-prototypes/1-defer-to-prototype/solution.md rename to 1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/solution.md diff --git a/1-js/07-object-oriented-programming/05-native-prototypes/1-defer-to-prototype/task.md b/1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/05-native-prototypes/1-defer-to-prototype/task.md rename to 1-js/08-prototypes/03-native-prototypes/1-defer-to-prototype/task.md diff --git a/1-js/07-object-oriented-programming/05-native-prototypes/2-defer-to-prototype-extended/solution.md b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/05-native-prototypes/2-defer-to-prototype-extended/solution.md rename to 1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/solution.md diff --git a/1-js/07-object-oriented-programming/05-native-prototypes/2-defer-to-prototype-extended/task.md b/1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/05-native-prototypes/2-defer-to-prototype-extended/task.md rename to 1-js/08-prototypes/03-native-prototypes/2-defer-to-prototype-extended/task.md diff --git a/1-js/07-object-oriented-programming/05-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md similarity index 61% rename from 1-js/07-object-oriented-programming/05-native-prototypes/article.md rename to 1-js/08-prototypes/03-native-prototypes/article.md index 4777f356f..d540be5f2 100644 --- a/1-js/07-object-oriented-programming/05-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -15,17 +15,17 @@ alert( obj ); // "[object Object]" ? Where's the code that generates the string `"[object Object]"`? That's a built-in `toString` method, but where is it? The `obj` is empty! -...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` -- is a built-in object constructor function. And that function has `Object.prototype` that references a huge object with `toString` and other functions. +...But the short notation `obj = {}` is the same as `obj = new Object()`, where `Object` is a built-in object constructor function, with its own `prototype` referencing a huge object with `toString` and other methods. -Like this (all that is built-in): +Here's what's going on: ![](object-prototype.png) -When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` by the rule that we've discussed in the previous chapter: +When `new Object()` is called (or a literal object `{...}` is created), the `[[Prototype]]` of it is set to `Object.prototype` according to the rule that we discussed in the previous chapter: ![](object-prototype-1.png) -Afterwards when `obj.toString()` is called -- the method is taken from `Object.prototype`. +So then when `obj.toString()` is called the method is taken from `Object.prototype`. We can check it like this: @@ -48,7 +48,7 @@ Other built-in objects such as `Array`, `Date`, `Function` and others also keep For instance, when we create an array `[1, 2, 3]`, the default `new Array()` constructor is used internally. So the array data is written into the new object, and `Array.prototype` becomes its prototype and provides methods. That's very memory-efficient. -By specification, all built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects". +By specification, all of the built-in prototypes have `Object.prototype` on the top. Sometimes people say that "everything inherits from objects". Here's the overall picture (for 3 built-ins to fit): @@ -82,11 +82,11 @@ As we've seen before, `Object.prototype` has `toString` as well, but `Array.prot ![](native-prototypes-array-tostring.png) -In-browser tools like Chrome developer console also show inheritance (may need to use `console.dir` for built-in objects): +In-browser tools like Chrome developer console also show inheritance (`console.dir` may need to be used for built-in objects): ![](console_dir_array.png) -Other built-in objects also work the same way. Even functions. They are objects of a built-in `Function` constructor, and their methods: `call/apply` and others are taken from `Function.prototype`. Functions have their own `toString` too. +Other built-in objects also work the same way. Even functions -- they are objects of a built-in `Function` constructor, and their methods (`call`/`apply` and others) are taken from `Function.prototype`. Functions have their own `toString` too. ```js run function f() {} @@ -119,11 +119,19 @@ String.prototype.show = function() { "BOOM!".show(); // BOOM! ``` -During the process of development we may have ideas which new built-in methods we'd like to have. And there may be a slight temptation to add them to native prototypes. But that is generally a bad idea. +During the process of development, we may have ideas for new built-in methods we'd like to have, and we may be tempted to add them to native prototypes. But that is generally a bad idea. -Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them overwrites the other one. +```warn +Prototypes are global, so it's easy to get a conflict. If two libraries add a method `String.prototype.show`, then one of them will be overwriting the other. -In modern programming, there is only one case when modifying native prototypes is approved. That's polyfills. In other words, if there's a method in JavaScript specification that is not yet supported by our JavaScript engine (or any of those that we want to support), then may implement it manually and populate the built-in prototype with it. +So, generally, modifying a native prototype is considered a bad idea. +``` + +**In modern programming, there is only one case where modifying native prototypes is approved. That's polyfilling.** + +Polyfilling is a term for making a substitute for a method that exists in JavaScript specification, but not yet supported by current JavaScript engine. + +Then we may implement it manually and populate the built-in prototype with it. For instance: @@ -134,9 +142,9 @@ if (!String.prototype.repeat) { // if there's no such method String.prototype.repeat = function(n) { // repeat the string n times - // actually, the code should be more complex than that, - // throw errors for negative values of "n" - // the full algorithm is in the specification + // actually, the code should be a little bit more complex than that + // (the full algorithm is in the specification) + // but even an imperfect polyfill is often considered good enough return new Array(n + 1).join(this); }; } @@ -144,37 +152,45 @@ if (!String.prototype.repeat) { // if there's no such method alert( "La".repeat(3) ); // LaLaLa ``` + ## Borrowing from prototypes -In the chapter we talked about method borrowing: +In the chapter we talked about method borrowing. + +That's when we take a method from one object and copy it into another. + +Some methods of native prototypes are often borrowed. + +For instance, if we're making an array-like object, we may want to copy some array methods to it. + +E.g. ```js run -function showArgs() { +let obj = { + 0: "Hello", + 1: "world!", + length: 2, +}; + *!* - // borrow join from array and call in the context of arguments - alert( [].join.call(arguments, " - ") ); +obj.join = Array.prototype.join; */!* -} -showArgs("John", "Pete", "Alice"); // John - Pete - Alice +alert( obj.join(',') ); // Hello,world! ``` -Because `join` resides in `Array.prototype`, we can call it from there directly and rewrite it as: +It works, because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property, it doesn't check that the object is indeed the array. And many built-in methods are like that. -```js -function showArgs() { -*!* - alert( Array.prototype.join.call(arguments, " - ") ); -*/!* -} -``` +Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, so all `Array` methods are automatically available in `obj`. + +But that's impossible if `obj` already inherits from another object. Remember, we only can inherit from one object at a time. -That's more efficient, because it avoids the creation of an extra array object `[]`. On the other hand, it is longer to write. +Borrowing methods is flexible, it allows to mix functionality from different objects if needed. ## Summary - All built-in objects follow the same pattern: - The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype` etc). - The object itself stores only the data (array items, object properties, the date). -- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype`, `Boolean.prototype`. There are no wrapper objects only for `undefined` and `null`. +- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype`, `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects. - Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. Probably the only allowable cause is when we add-in a new standard, but not yet supported by the engine JavaScript method. diff --git a/1-js/07-object-oriented-programming/05-native-prototypes/console_dir_array.png b/1-js/08-prototypes/03-native-prototypes/console_dir_array.png similarity index 100% rename from 1-js/07-object-oriented-programming/05-native-prototypes/console_dir_array.png rename to 1-js/08-prototypes/03-native-prototypes/console_dir_array.png diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png new file mode 100644 index 000000000..6ab9c6934 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png new file mode 100644 index 000000000..3beb0a573 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor@2x.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png new file mode 100644 index 000000000..c47938674 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png new file mode 100644 index 000000000..293c88bf9 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring@2x.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png new file mode 100644 index 000000000..c4658a929 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png new file mode 100644 index 000000000..79445fac9 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes@2x.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png new file mode 100644 index 000000000..a69cad4a1 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png new file mode 100644 index 000000000..9d661ac17 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/object-prototype-1@2x.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png new file mode 100644 index 000000000..792c4f423 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png new file mode 100644 index 000000000..d97efa884 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/object-prototype-null@2x.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.png b/1-js/08-prototypes/03-native-prototypes/object-prototype.png new file mode 100644 index 000000000..820ffcc73 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/object-prototype.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png b/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png new file mode 100644 index 000000000..1eb7e8749 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/object-prototype@2x.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png new file mode 100644 index 000000000..4b381460a Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png new file mode 100644 index 000000000..c6e6ab5f6 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit@2x.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png new file mode 100644 index 000000000..8f3519a74 Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.png differ diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png new file mode 100644 index 000000000..07acb8f4a Binary files /dev/null and b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor@2x.png differ diff --git a/1-js/07-object-oriented-programming/06-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md similarity index 79% rename from 1-js/07-object-oriented-programming/06-prototype-methods/2-dictionary-tostring/solution.md rename to 1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md index debaecd6a..a92e17900 100644 --- a/1-js/07-object-oriented-programming/06-prototype-methods/2-dictionary-tostring/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md @@ -1,7 +1,7 @@ The method can take all enumerable keys using `Object.keys` and output their list. -To make `toString` non-enumerable, let's define it using a property descriptor. The syntax of `Object.create` allows to provide an object with property descriptors as the second argument. +To make `toString` non-enumerable, let's define it using a property descriptor. The syntax of `Object.create` allows us to provide an object with property descriptors as the second argument. ```js run *!* @@ -27,3 +27,5 @@ 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. diff --git a/1-js/07-object-oriented-programming/06-prototype-methods/2-dictionary-tostring/task.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/06-prototype-methods/2-dictionary-tostring/task.md rename to 1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/task.md diff --git a/1-js/07-object-oriented-programming/06-prototype-methods/3-compare-calls/solution.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/06-prototype-methods/3-compare-calls/solution.md rename to 1-js/08-prototypes/04-prototype-methods/3-compare-calls/solution.md diff --git a/1-js/07-object-oriented-programming/06-prototype-methods/3-compare-calls/task.md b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md similarity index 92% rename from 1-js/07-object-oriented-programming/06-prototype-methods/3-compare-calls/task.md rename to 1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md index 92653bd8c..09bb7f1ed 100644 --- a/1-js/07-object-oriented-programming/06-prototype-methods/3-compare-calls/task.md +++ b/1-js/08-prototypes/04-prototype-methods/3-compare-calls/task.md @@ -2,7 +2,7 @@ importance: 5 --- -# The difference beteeen calls +# The difference between calls Let's create a new `rabbit` object: diff --git a/1-js/07-object-oriented-programming/06-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md similarity index 59% rename from 1-js/07-object-oriented-programming/06-prototype-methods/article.md rename to 1-js/08-prototypes/04-prototype-methods/article.md index e253e8af7..a9c21faaf 100644 --- a/1-js/07-object-oriented-programming/06-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -1,14 +1,18 @@ -# Methods for prototypes +# Prototype methods, objects without __proto__ -In this chapter we cover additional methods to work with a prototype. +In the first chapter of this section, we mentioned that there are modern methods to setup a prototype. -There are also other ways to get/set a prototype, besides those that we already know: +The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard). + +The modern methods 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__`. + For instance: ```js run @@ -68,11 +72,17 @@ That's for historical reasons. - The `"prototype"` property of a constructor function works since very ancient times. - Later in the year 2012: `Object.create` appeared in the standard. It allowed to create objects with the given prototype, but did not allow to get/set it. So browsers implemented non-standard `__proto__` accessor that allowed to get/set a prototype at any time. -- Later in the year 2015: `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard. The `__proto__` was de-facto implemented everywhere, so it made its way to the Annex B of the standard, that is optional for non-browser environments. +- Later in the year 2015: `Object.setPrototypeOf` and `Object.getPrototypeOf` were added to the standard, to perform the same functionality as `__proto__`. As `__proto__` was de-facto implemented everywhere, it was kind-of deprecated and made its way to the Annex B of the standard, that is optional for non-browser environments. As of now we have all these ways at our disposal. -Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time, and then do not modify: `rabbit` inherits from `animal`, and that is not going to change. And JavaScript engines are highly optimized to that. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation. But it is possible. +Why was `__proto__` replaced by the functions `getPrototypeOf/setPrototypeOf`? That's an interesting question, requiring us to understand why `__proto__` is bad. Read on to get the answer. + +```warn header="Don't reset `[[Prototype]]` unless the speed doesn't matter" +Technically, we can get/set `[[Prototype]]` at any time. But usually we only set it once at the object creation time, and then do not modify: `rabbit` inherits from `animal`, and that is not going to change. + +And JavaScript engines are highly optimized to that. Changing a prototype "on-the-fly" with `Object.setPrototypeOf` or `obj.__proto__=` is a very slow operation, it breaks internal optimizations for object property access operations. So evade it unless you know what you're doing, or JavaScript speed totally doesn't matter for you. +``` ## "Very plain" objects @@ -95,11 +105,13 @@ Here if the user types in `__proto__`, the assignment is ignored! That shouldn't surprise us. The `__proto__` property is special: it must be either an object or `null`, a string can not become a prototype. -But we did not intend to implement such behavior, right? We want to store key/value pairs, and the key named `"__proto__"` was not properly saved. So that's a bug. Here the consequences are not terrible. But in other cases the prototype may indeed be changed, so the execution may go wrong in totally unexpected ways. +But we didn't *intend* to implement such behavior, right? We want to store key/value pairs, and the key named `"__proto__"` was not properly saved. So that's a bug! + +Here the consequences are not terrible. But in other cases, we may be assigning object values, then the prototype may indeed be changed. As the result, the execution will go wrong in totally unexpected ways. What's worst -- usually developers do not think about such possibility at all. That makes such bugs hard to notice and even turn them into vulnerabilities, especially when JavaScript is used on server-side. -Such thing happens only with `__proto__`. All other properties are "assignable" normally. +Unexpected things also may happen when accessing `toString` property -- that's a function by default, and other built-in properties. How to evade the problem? @@ -111,9 +123,9 @@ The `__proto__` is not a property of an object, but an accessor property of `Obj ![](object-prototype-2.png) -So, if `obj.__proto__` is read or assigned, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`. +So, if `obj.__proto__` is read or set, the corresponding getter/setter is called from its prototype, and it gets/sets `[[Prototype]]`. -As it was said in the beginning: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself. +As it was said in the beginning of this tutorial section: `__proto__` is a way to access `[[Prototype]]`, it is not `[[Prototype]]` itself. Now, if we want to use an object as an associative array, we can do it with a little trick: @@ -153,98 +165,36 @@ Please note that most object-related methods are `Object.something(...)`, like ` ```js run let chineseDictionary = Object.create(null); -chineseDictionary.hello = "ni hao"; -chineseDictionary.bye = "zai jian"; +chineseDictionary.hello = "你好"; +chineseDictionary.bye = "再见"; alert(Object.keys(chineseDictionary)); // hello,bye ``` -## Getting all properties - -There are many ways to get keys/values from an object. - -We already know these ones: - -- [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of enumerable own string property names/values/key-value pairs. These methods only list *enumerable* properties, and those that have *strings as keys*. - -If we want symbolic properties: - -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names. - -If we want non-enumerable properties: - -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names. - -If we want *all* properties: - -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names. - -These methods are a bit different about which properties they return, but all of them operate on the object itself. Properties from the prototype are not listed. - -The `for..in` loop is different: it loops over inherited properties too. - -For instance: - -```js run -let animal = { - eats: true -}; - -let rabbit = { - jumps: true, - __proto__: animal -}; +## Summary -*!* -// only own keys -alert(Object.keys(rabbit)); // jumps -*/!* +Modern methods to setup and directly access the prototype are: -*!* -// inherited keys too -for(let prop in rabbit) alert(prop); // jumps, then eats -*/!* -``` +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors. +- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter). +- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter). -If we want to distinguish 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`. +The built-in `__proto__` getter/setter is unsafe if we'd want to put user-generated keys in to an object. Just because a user may enter "__proto__" as the key, and there'll be an error with hopefully easy, but generally unpredictable consequences. -So we can filter out inherited properties (or do something else with them): +So we can either use `Object.create(null)` to create a "very plain" object without `__proto__`, or stick to `Map` objects for that. -```js run -let animal = { - eats: true -}; +Also, `Object.create` provides an easy way to shallow-copy an object with all descriptors: -let rabbit = { - jumps: true, - __proto__: animal -}; - -for(let prop in rabbit) { - let isOwn = rabbit.hasOwnProperty(prop); - alert(`${prop}: ${isOwn}`); // jumps:true, then eats:false -} +```js +let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); ``` -Here we have the following inheritance chain: `rabbit`, then `animal`, then `Object.prototype` (because `animal` is a literal object `{...}`, so it's by default), and then `null` above it: - -![](rabbit-animal-object.png) - -Note, there's one funny thing. Where is the method `rabbit.hasOwnProperty` coming from? Looking at the chain we can see that the method is provided by `Object.prototype.hasOwnProperty`. In other words, it's inherited. -...But why `hasOwnProperty` does not appear in `for..in` loop, if it lists all inherited properties? The answer is simple: it's not enumerable. Just like all other properties of `Object.prototype`. That's why they are not listed. -## Summary - -Here's a brief list of methods we discussed in this chapter -- as a recap: - -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` (can be `null`) and optional property descriptors. -- [Object.getPrototypeOf(obj)](mdn:js/Object.getPrototypeOf) -- returns the `[[Prototype]]` of `obj` (same as `__proto__` getter). -- [Object.setPrototypeOf(obj, proto)](mdn:js/Object.setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto` (same as `__proto__` setter). - [Object.keys(obj)](mdn:js/Object/keys) / [Object.values(obj)](mdn:js/Object/values) / [Object.entries(obj)](mdn:js/Object/entries) -- returns an array of enumerable own string property names/values/key-value pairs. -- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic property names. -- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string property names. -- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own property names. -- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. +- [Object.getOwnPropertySymbols(obj)](mdn:js/Object/getOwnPropertySymbols) -- returns an array of all own symbolic keys. +- [Object.getOwnPropertyNames(obj)](mdn:js/Object/getOwnPropertyNames) -- returns an array of all own string keys. +- [Reflect.ownKeys(obj)](mdn:js/Reflect/ownKeys) -- returns an array of all own keys. +- [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) keytt named `key`. We also made it clear that `__proto__` is a getter/setter for `[[Prototype]]` and resides in `Object.prototype`, just as other methods. diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png new file mode 100644 index 000000000..343435af6 Binary files /dev/null and b/1-js/08-prototypes/04-prototype-methods/object-prototype-2.png differ diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png new file mode 100644 index 000000000..86b8a678e Binary files /dev/null and b/1-js/08-prototypes/04-prototype-methods/object-prototype-2@2x.png differ diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png new file mode 100644 index 000000000..792c4f423 Binary files /dev/null and b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.png differ diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png b/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png new file mode 100644 index 000000000..d97efa884 Binary files /dev/null and b/1-js/08-prototypes/04-prototype-methods/object-prototype-null@2x.png differ diff --git a/1-js/08-prototypes/index.md b/1-js/08-prototypes/index.md new file mode 100644 index 000000000..8554a0e30 --- /dev/null +++ b/1-js/08-prototypes/index.md @@ -0,0 +1 @@ +# Prototypes, inheritance diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/source.view/clock.js b/1-js/09-classes/01-class/1-rewrite-to-class/_js.view/solution.js similarity index 64% rename from 1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/source.view/clock.js rename to 1-js/09-classes/01-class/1-rewrite-to-class/_js.view/solution.js index 8009273e3..0b31cf334 100644 --- a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/source.view/clock.js +++ b/1-js/09-classes/01-class/1-rewrite-to-class/_js.view/solution.js @@ -1,9 +1,9 @@ class Clock { constructor({ template }) { - this._template = template; + this.template = template; } - _render() { + render() { let date = new Date(); let hours = date.getHours(); @@ -15,7 +15,7 @@ class Clock { let secs = date.getSeconds(); if (secs < 10) secs = '0' + secs; - let output = this._template + let output = this.template .replace('h', hours) .replace('m', mins) .replace('s', secs); @@ -24,11 +24,15 @@ class Clock { } stop() { - clearInterval(this._timer); + clearInterval(this.timer); } start() { - this._render(); - this._timer = setInterval(() => this._render(), 1000); + this.render(); + this.timer = setInterval(() => this.render(), 1000); } } + + +let clock = new Clock({template: 'h:m:s'}); +clock.start(); diff --git a/1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/source.view/clock.js b/1-js/09-classes/01-class/1-rewrite-to-class/_js.view/source.js similarity index 90% rename from 1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/source.view/clock.js rename to 1-js/09-classes/01-class/1-rewrite-to-class/_js.view/source.js index c4bfaa0ff..f1749c8ba 100644 --- a/1-js/07-object-oriented-programming/08-class-patterns/2-rewrite-to-prototypes/source.view/clock.js +++ b/1-js/09-classes/01-class/1-rewrite-to-class/_js.view/source.js @@ -32,3 +32,6 @@ function Clock({ template }) { }; } + +let clock = new Clock({template: 'h:m:s'}); +clock.start(); diff --git a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/solution.md b/1-js/09-classes/01-class/1-rewrite-to-class/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/solution.md rename to 1-js/09-classes/01-class/1-rewrite-to-class/solution.md diff --git a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/task.md b/1-js/09-classes/01-class/1-rewrite-to-class/task.md similarity index 53% rename from 1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/task.md rename to 1-js/09-classes/01-class/1-rewrite-to-class/task.md index a29d347f5..05365e410 100644 --- a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/task.md +++ b/1-js/09-classes/01-class/1-rewrite-to-class/task.md @@ -4,6 +4,6 @@ importance: 5 # Rewrite to class -Rewrite the `Clock` class from prototypes to the modern "class" syntax. +The `Clock` class is written in functional style. Rewrite it the "class" syntax. P.S. The clock ticks in the console, open it to see. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md new file mode 100644 index 000000000..30a958df7 --- /dev/null +++ b/1-js/09-classes/01-class/article.md @@ -0,0 +1,351 @@ + +# Class basic syntax + +```quote author="Wikipedia" +In object-oriented programming, a *class* is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods). +``` + +In practice, we often need to create many objects of the same kind, like users, or goods or whatever. + +As we already know from the chapter , `new function` can help with that. + +But in the modern JavaScript, there's a more advanced "class" construct, that introduces great new features which are useful for object-oriented programming. + +## The "class" syntax + +The basic syntax is: +```js +class MyClass { + // class methods + constructor() { ... } + method1() { ... } + method2() { ... } + method3() { ... } + ... +} +``` + +Then `new MyClass()` creates a new object with all the listed methods. + +The `constructor()` method is called automatically by `new`, so we can initialize the object there. + +For example: + +```js run +class User { + + constructor(name) { + this.name = name; + } + + sayHi() { + alert(this.name); + } + +} + +// Usage: +let user = new User("John"); +user.sayHi(); +``` + +When `new User("John")` is called: +1. A new object is created. +2. The `constructor` runs with the given argument and assigns `this.name` to it. + +...Then we can call methods, such as `user.sayHi`. + + +```warn header="No comma between class methods" +A common pitfall for novice developers is to put a comma between class methods, which would result in a syntax error. + +The notation here is not to be confused with object literals. Within the class, no commas are required. +``` + +## What is a class? + +So, what exactly is a `class`? That's not an entirely new language-level entity, as one might think. + +Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects. + +In JavaScript, a class is a kind of a function. + +Here, take a look: + +```js run +class User { + constructor(name) { this.name = name; } + sayHi() { alert(this.name); } +} + +// proof: User is a function +*!* +alert(typeof User); // function +*/!* +``` + +What `class User {...}` construct really does is: +1. Creates a function named `User`, that becomes the result of the class declaration. + - The function code is taken from the `constructor` method (assumed empty if we don't write such method). +3. Stores all methods, such as `sayHi`, in `User.prototype`. + +Afterwards, for new objects, when we call a method, it's taken from the prototype, just as described in the chapter . So `new User` object has access to class methods. + +We can illustrate the result of `class User` declaration as: + +![](class-user.png) + +Here's the code to introspect it: + + +```js run +class User { + constructor(name) { this.name = name; } + sayHi() { alert(this.name); } +} + +// class is a function +alert(typeof User); // function + +// ...or, more precisely, the constructor method +alert(User === User.prototype.constructor); // true + +// The methods are in User.prototype, e.g: +alert(User.prototype.sayHi); // alert(this.name); + +// there are exactly two methods in the prototype +alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi +``` + +## Not just a syntax sugar + +Sometimes people say that `class` is a "syntax sugar" in JavaScript, because we could actually declare the same without `class` keyword at all: + +```js run +// rewriting class User in pure functions + +// 1. Create constructor function +function User(name) { + this.name = name; +} +// any function prototype has constructor property by default, +// so we don't need to create it + +// 2. Add the method to prototype +User.prototype.sayHi = function() { + alert(this.name); +}; + +// Usage: +let user = new User("John"); +user.sayHi(); +``` + +The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntax sugar to define a constructor together with its prototype methods. + +Although, there are important differences. + +1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually. + + Unlike a regular function, a class constructor can't be called without `new`: + + ```js run + class User { + constructor() {} + } + + alert(typeof User); // function + User(); // Error: Class constructor User cannot be invoked without 'new' + ``` + + Also, a string representation of a class constructor in most JavaScript engines starts with the "class..." + + ```js run + class User { + constructor() {} + } + + alert(User); // class User { ... } + ``` + +2. Class methods are non-enumerable. + A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. + + That's good, because if we `for..in` over an object, we usually don't want its class methods. + +3. Classes always `use strict`. + All code inside the class construct is automatically in strict mode. + + +Also, in addition to its basic operation, the `class` syntax brings many other features with it which we'll explore later. + +## Class Expression + +Just like functions, classes can be defined inside another expression, passed around, returned, assigned etc. + +Here's an example of a class expression: + +```js +let User = class { + sayHi() { + alert("Hello"); + } +}; +``` + +Similar to Named Function Expressions, class expressions may or may not have a name. + +If a class expression has a name, it's visible inside the class only: + +```js run +// "Named Class Expression" +// (no such term in the spec, but that's similar to Named Function Expression) +let User = class *!*MyClass*/!* { + sayHi() { + alert(MyClass); // MyClass is visible only inside the class + } +}; + +new User().sayHi(); // works, shows MyClass definition + +alert(MyClass); // error, MyClass not visible outside of the class +``` + + +We can even make classes dynamically "on-demand", like this: + +```js run +function makeClass(phrase) { + // declare a class and return it + return class { + sayHi() { + alert(phrase); + }; + }; +} + +// Create a new class +let User = makeClass("Hello"); + +new User().sayHi(); // Hello +``` + + +## Getters/setters, other shorthands + +Just like literal objects, classes may include getters/setters, generators, computed properties etc. + +Here's an example for `user.name` implemented using `get/set`: + +```js run +class User { + + constructor(name) { + // invokes the setter + this.name = name; + } + +*!* + get name() { +*/!* + return this._name; + } + +*!* + set name(value) { +*/!* + if (value.length < 4) { + alert("Name is too short."); + return; + } + this._name = value; + } + +} + +let user = new User("John"); +alert(user.name); // John + +user = new User(""); // Name too short. +``` + +The class declaration creates getters and setters in `User.prototype`, like this: + +```js +Object.defineProperties(User.prototype, { + name: { + get() { + return this._name + }, + set(name) { + // ... + } + } +}); +``` + +Here's an example with computed properties: + +```js run +function f() { return "sayHi"; } + +class User { + [f()]() { + alert("Hello"); + } + +} + +new User().sayHi(); +``` + +For a generator method, similarly, prepend it with `*`. + +## Class properties + +```warn header="Old browsers may need a polyfill" +Class-level properties are a recent addition to the language. +``` + +In the example above, `User` only had methods. Let's add a property: + +```js run +class User { + name = "Anonymous"; + + sayHi() { + alert(`Hello, ${this.name}!`); + } +} + +new User().sayHi(); +``` + +The property is not placed into `User.prototype`. Instead, it is created by `new`, separately for every object. So, the property will never be shared between different objects of the same class. + + +## Summary + +The basic class syntax looks like this: + +```js +class MyClass { + prop = value; // field + + constructor(...) { // constructor + // ... + } + + method(...) {} // method + + get something(...) {} // getter method + set something(...) {} // setter method + + [Symbol.iterator]() {} // method with computed name/symbol name + // ... +} +``` + +`MyClass` is technically a function (the one that we provide as `constructor`), while methods, getters and settors are written to `MyClass.prototype`. + +In the next chapters we'll learn more about classes, including inheritance and other features. diff --git a/1-js/09-classes/01-class/class-user.png b/1-js/09-classes/01-class/class-user.png new file mode 100644 index 000000000..dc8b75679 Binary files /dev/null and b/1-js/09-classes/01-class/class-user.png differ diff --git a/1-js/09-classes/01-class/class-user@2x.png b/1-js/09-classes/01-class/class-user@2x.png new file mode 100644 index 000000000..aaa53708f Binary files /dev/null and b/1-js/09-classes/01-class/class-user@2x.png differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/1-class-constructor-error/solution.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/10-class-inheritance/1-class-constructor-error/solution.md rename to 1-js/09-classes/02-class-inheritance/1-class-constructor-error/solution.md diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/1-class-constructor-error/task.md b/1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/10-class-inheritance/1-class-constructor-error/task.md rename to 1-js/09-classes/02-class-inheritance/1-class-constructor-error/task.md diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md new file mode 100644 index 000000000..dcb4ffe59 --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.md @@ -0,0 +1 @@ +[js src="solution.view/extended-clock.js"] diff --git a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/solution.view/clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/clock.js similarity index 70% rename from 1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/solution.view/clock.js rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/clock.js index 8009273e3..d701c0cae 100644 --- a/1-js/07-object-oriented-programming/09-class/1-rewrite-to-class/solution.view/clock.js +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/clock.js @@ -1,9 +1,9 @@ class Clock { constructor({ template }) { - this._template = template; + this.template = template; } - _render() { + render() { let date = new Date(); let hours = date.getHours(); @@ -15,7 +15,7 @@ class Clock { let secs = date.getSeconds(); if (secs < 10) secs = '0' + secs; - let output = this._template + let output = this.template .replace('h', hours) .replace('m', mins) .replace('s', secs); @@ -24,11 +24,11 @@ class Clock { } stop() { - clearInterval(this._timer); + clearInterval(this.timer); } start() { - this._render(); - this._timer = setInterval(() => this._render(), 1000); + this.render(); + this.timer = setInterval(() => this.render(), 1000); } } diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js similarity index 53% rename from 1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js index 4eb12381f..ca613ca5e 100644 --- a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js @@ -2,11 +2,11 @@ class ExtendedClock extends Clock { constructor(options) { super(options); let { precision=1000 } = options; - this._precision = precision; + this.precision = precision; } start() { - this._render(); - this._timer = setInterval(() => this._render(), this._precision); + this.render(); + this.timer = setInterval(() => this.render(), this.precision); } }; diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/index.html b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/index.html new file mode 100644 index 000000000..f76a43623 --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/index.html @@ -0,0 +1,12 @@ + + + + + diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/clock.js similarity index 70% rename from 1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/clock.js rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/clock.js index 8009273e3..d701c0cae 100644 --- a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.view/clock.js +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/clock.js @@ -1,9 +1,9 @@ class Clock { constructor({ template }) { - this._template = template; + this.template = template; } - _render() { + render() { let date = new Date(); let hours = date.getHours(); @@ -15,7 +15,7 @@ class Clock { let secs = date.getSeconds(); if (secs < 10) secs = '0' + secs; - let output = this._template + let output = this.template .replace('h', hours) .replace('m', mins) .replace('s', secs); @@ -24,11 +24,11 @@ class Clock { } stop() { - clearInterval(this._timer); + clearInterval(this.timer); } start() { - this._render(); - this._timer = setInterval(() => this._render(), 1000); + this.render(); + this.timer = setInterval(() => this.render(), 1000); } } diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html new file mode 100644 index 000000000..c0609858b --- /dev/null +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/source.view/index.html @@ -0,0 +1,21 @@ + + + diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/task.md b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md similarity index 92% rename from 1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/task.md rename to 1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md index 05da45387..bbc2c6a43 100644 --- a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/task.md +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/task.md @@ -6,6 +6,9 @@ importance: 5 We've got a `Clock` class. As of now, it prints the time every second. + +[js src="source.view/clock.js"] + Create a new class `ExtendedClock` that inherits from `Clock` and adds the parameter `precision` -- the number of `ms` between "ticks". Should be `1000` (1 second) by default. - Your code should be in the file `extended-clock.js` diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.png b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.png new file mode 100644 index 000000000..c610e28c3 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.png differ diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png new file mode 100644 index 000000000..4819c7f79 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object@2x.png differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/solution.md rename to 1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/10-class-inheritance/3-class-extend-object/task.md rename to 1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.png b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.png new file mode 100644 index 000000000..0d887dbc0 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.png differ diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends@2x.png b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends@2x.png new file mode 100644 index 000000000..af09271a9 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends@2x.png differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md similarity index 62% rename from 1-js/07-object-oriented-programming/10-class-inheritance/article.md rename to 1-js/09-classes/02-class-inheritance/article.md index 2c44e5ead..abb7d1e33 100644 --- a/1-js/07-object-oriented-programming/10-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -1,40 +1,82 @@ -# Class inheritance, super +# Class inheritance -Classes can extend one another. There's a nice syntax, technically based on the prototypal inheritance. +Let's say we have two classes. -To inherit from another class, we should specify `"extends"` and the parent class before the brackets `{..}`. +`Animal`: + +```js +class Animal { + constructor(name) { + this.speed = 0; + this.name = name; + } + run(speed) { + this.speed += speed; + alert(`${this.name} runs with speed ${this.speed}.`); + } + stop() { + this.speed = 0; + alert(`${this.name} stopped.`); + } +} + +let animal = new Animal("My animal"); +``` + +![](rabbit-animal-independent-animal.png) + + +...And `Rabbit`: + +```js +class Rabbit { + constructor(name) { + this.name = name; + } + hide() { + alert(`${this.name} hides!`); + } +} + +let rabbit = new Rabbit("My rabbit"); +``` + +![](rabbit-animal-independent-rabbit.png) + + +Right now they are fully independent. + +But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods. + +To inherit from another class, we should specify `"extends"` and the parent class before the braces `{..}`. Here `Rabbit` inherits from `Animal`: ```js run class Animal { - constructor(name) { this.speed = 0; this.name = name; } - run(speed) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } - stop() { this.speed = 0; alert(`${this.name} stopped.`); } - } +// Inherit from Animal by specifying "extends Animal" *!* -// Inherit from Animal class Rabbit extends Animal { +*/!* hide() { alert(`${this.name} hides!`); } } -*/!* let rabbit = new Rabbit("White Rabbit"); @@ -42,11 +84,15 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides! ``` -The `extends` keyword actually adds a `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`, just as you expect it to be, and as we've seen before. +Now the `Rabbit` code became a bit shorter, as it uses `Animal` constructor by default, and it also can `run`, as animals do. + +Internally, `extends` keyword adds `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`: ![](animal-rabbit-extends.png) -So now `rabbit` has access both to its own methods and to methods of `Animal`. +So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. + +As we can recall from the chapter , JavaScript uses the same prototypal inheritance for build-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`, so dates have generic object methods. ````smart header="Any expression is allowed after `extends`" Class syntax allows to specify not just a class, but any expression after `extends`. @@ -163,7 +209,7 @@ With constructors it gets a little bit tricky. Till now, `Rabbit` did not have its own `constructor`. -According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following `constructor` is generated: +According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated: ```js class Rabbit extends Animal { @@ -263,15 +309,15 @@ alert(rabbit.earLength); // 10 Let's get a little deeper under the hood of `super`. We'll see some interesting things by the way. -First to say, from all that we've learned till now, it's impossible for `super` to work. +First to say, from all that we've learned till now, it's impossible for `super` to work at all! -Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, how to retrieve the `method`? Naturally, we need to take the `method` from the prototype of the current object. How, technically, we (or a JavaScript engine) can do it? +Yeah, indeed, let's ask ourselves, how it could technically work? When an object method runs, it gets the current object as `this`. If we call `super.method()` then, it needs to retrieve the `method` from the prototype of the current object. -Maybe we can get the method from `[[Prototype]]` of `this`, as `this.__proto__.method`? Unfortunately, that doesn't work. +The task may seem simple, but it isn't. The engine knows the current object `this`, so it could get the parent `method` as `this.__proto__.method`. Unfortunately, such a "naive" solution won't work. -Let's try to do it. Without classes, using plain objects for the sake of simplicity. +Let's demonstrate the problem. Without classes, using plain objects for the sake of simplicity. -Here, `rabbit.eat()` should call `animal.eat()` method of the parent object: +In the example below, `rabbit.__proto__ = animal`. Now let's try: in `rabbit.eat()` we'll call `animal.eat()`, using `this.__proto__`: ```js run let animal = { @@ -368,18 +414,16 @@ The problem can't be solved by using `this` alone. To provide the solution, JavaScript adds one more special internal property for functions: `[[HomeObject]]`. -**When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object.** - -This actually violates the idea of "unbound" functions, because methods remember their objects. And `[[HomeObject]]` can't be changed, so this bound is forever. So that's a very important change in the language. +When a function is specified as a class or object method, its `[[HomeObject]]` property becomes that object. -But this change is safe. `[[HomeObject]]` is used only for calling parent methods in `super`, to resolve the prototype. So it doesn't break compatibility. +Then `super` uses it to resolve the parent prototype and its methods. -Let's see how it works for `super` -- again, using plain objects: +Let's see how it works, first with plain objects: ```js run let animal = { name: "Animal", - eat() { // [[HomeObject]] == animal + eat() { // animal.eat.[[HomeObject]] == animal alert(`${this.name} eats.`); } }; @@ -387,7 +431,7 @@ let animal = { let rabbit = { __proto__: animal, name: "Rabbit", - eat() { // [[HomeObject]] == rabbit + eat() { // rabbit.eat.[[HomeObject]] == rabbit super.eat(); } }; @@ -395,181 +439,108 @@ let rabbit = { let longEar = { __proto__: rabbit, name: "Long Ear", - eat() { // [[HomeObject]] == longEar + eat() { // longEar.eat.[[HomeObject]] == longEar super.eat(); } }; *!* +// works correctly longEar.eat(); // Long Ear eats. */!* ``` -Every method remembers its object in the internal `[[HomeObject]]` property. Then `super` uses it to resolve the parent prototype. +It works as intended, due to `[[HomeObject]]` mechanics. A method, such as `longEar.eat`, knows its `[[HomeObject]]` and takes the parent method from its prototype. Without any use of `this`. -`[[HomeObject]]` is defined for methods defined both in classes and in plain objects. But for objects, methods must be specified exactly the given way: as `method()`, not as `"method: function()"`. +### Methods are not "free" -In the example below a non-method syntax is used for comparison. `[[HomeObject]]` property is not set and the inheritance doesn't work: +As we've known before, generally functions are "free", not bound to objects in JavaScript. So they can be copied between objects and called with another `this`. + +The very existance of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever. + +The only place in the language where `[[HomeObject]]` is used -- is `super`. So, if a method does not use `super`, then we can still consider it free and copy between objects. But with `super` things may go wrong. + +Here's the demo of a wrong `super` call: ```js run let animal = { - eat: function() { // should be the short syntax: eat() {...} - // ... + sayHi() { + console.log(`I'm an animal`); } }; let rabbit = { __proto__: animal, - eat: function() { - super.eat(); + sayHi() { + super.sayHi(); } }; -*!* -rabbit.eat(); // Error calling super (because there's no [[HomeObject]]) -*/!* -``` - -## Static methods and inheritance - -The `class` syntax supports inheritance for static properties too. - -For instance: - -```js run -class Animal { - - constructor(name, speed) { - this.speed = speed; - this.name = name; - } - - run(speed = 0) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); +let plant = { + sayHi() { + console.log("I'm a plant"); } +}; - static compare(animalA, animalB) { - return animalA.speed - animalB.speed; - } - -} - -// Inherit from Animal -class Rabbit extends Animal { - hide() { - alert(`${this.name} hides!`); - } -} - -let rabbits = [ - new Rabbit("White Rabbit", 10), - new Rabbit("Black Rabbit", 5) -]; - -rabbits.sort(Rabbit.compare); - -rabbits[0].run(); // Black Rabbit runs with speed 5. -``` - -Now we can call `Rabbit.compare` assuming that the inherited `Animal.compare` will be called. - -How does it work? Again, using prototypes. As you might have already guessed, extends also gives `Rabbit` the `[[Prototype]]` reference to `Animal`. - - -![](animal-rabbit-static.png) - -So, `Rabbit` function now inherits from `Animal` function. And `Animal` function normally has `[[Prototype]]` referencing `Function.prototype`, because it doesn't `extend` anything. - -Here, let's check that: - -```js run -class Animal {} -class Rabbit extends Animal {} - -// for static propertites and methods -alert(Rabbit.__proto__ === Animal); // true - -// and the next step is Function.prototype -alert(Animal.__proto__ === Function.prototype); // true +let tree = { + __proto__: plant, +*!* + sayHi: rabbit.sayHi // (*) +*/!* +}; -// that's in addition to the "normal" prototype chain for object methods -alert(Rabbit.prototype.__proto__ === Animal.prototype); +*!* +tree.sayHi(); // I'm an animal (?!?) +*/!* ``` -This way `Rabbit` has access to all static methods of `Animal`. +A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong. -### No static inheritance in built-ins +The reason is simple: +- In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication? +- So its `[[HomeObject]]` is `rabbit`, as it was created in `rabbit`. There's no way to change `[[HomeObject]]`. +- The code of `tree.sayHi()` has `super.sayHi()` inside. It goes up from `rabbit` and takes the method from `animal`. -Please note that built-in classes don't have such static `[[Prototype]]` reference. For instance, `Object` has `Object.defineProperty`, `Object.keys` and so on, but `Array`, `Date` etc do not inherit them. +![](super-homeobject-wrong.png) -Here's the picture structure for `Date` and `Object`: +### Methods, not function properties -![](object-date-inheritance.png) +`[[HomeObject]]` is defined for methods both in classes and in plain objects. But for objects, methods must be specified exactly as `method()`, not as `"method: function()"`. -Note, there's no link between `Date` and `Object`. Both `Object` and `Date` exist independently. `Date.prototype` inherits from `Object.prototype`, but that's all. +The difference may be non-essential for us, but it's important for JavaScript. -Such difference exists for historical reasons: there was no thought about class syntax and inheriting static methods at the dawn of JavaScript language. - -## Natives are extendable - -Built-in classes like Array, Map and others are extendable also. - -For instance, here `PowerArray` inherits from the native `Array`: +In the example below a non-method syntax is used for comparison. `[[HomeObject]]` property is not set and the inheritance doesn't work: ```js run -// add one more method to it (can do more) -class PowerArray extends Array { - isEmpty() { - return this.length === 0; +let animal = { + eat: function() { // should be the short syntax: eat() {...} + // ... } -} - -let arr = new PowerArray(1, 2, 5, 10, 50); -alert(arr.isEmpty()); // false - -let filteredArr = arr.filter(item => item >= 10); -alert(filteredArr); // 10, 50 -alert(filteredArr.isEmpty()); // false -``` - -Please note one very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type. They rely on the `constructor` property to do so. - -In the example above, -```js -arr.constructor === PowerArray -``` - -So when `arr.filter()` is called, it internally creates the new array of results exactly as `new PowerArray`. And we can keep using its methods further down the chain. - -Even more, we can customize that behavior. The static getter `Symbol.species`, if exists, returns the constructor to use in such cases. - -For example, here due to `Symbol.species` built-in methods like `map`, `filter` will return "normal" arrays: +}; -```js run -class PowerArray extends Array { - isEmpty() { - return this.length === 0; +let rabbit = { + __proto__: animal, + eat: function() { + super.eat(); } +}; *!* - // built-in methods will use this as the constructor - static get [Symbol.species]() { - return Array; - } +rabbit.eat(); // Error calling super (because there's no [[HomeObject]]) */!* -} - -let arr = new PowerArray(1, 2, 5, 10, 50); -alert(arr.isEmpty()); // false +``` -// filter creates new array using arr.constructor[Symbol.species] as constructor -let filteredArr = arr.filter(item => item >= 10); +## Summary -*!* -// filteredArr is not PowerArray, but Array -*/!* -alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function -``` +1. To extend a class: `class Child extends Parent`: + - That means `Child.prototype.__proto__` will be `Parent.prototype`, so methods are inherited. +2. When overriding a constructor: + - We must call parent constructor as `super()` in `Child` constructor before using `this`. +3. When overriding another method: + - We can use `super.method()` in a `Child` method to call `Parent` method. +4. Internals: + - Methods remember their class/object in the internal `[[HomeObject]]` property. That's how `super` resolves parent methods. + - So it's not safe to copy a method with `super` from one object to another. -We can use it in more advanced keys to strip extended functionality from resulting values if not needed. Or, maybe, to extend it even further. +Also: +- Arrow functions don't have own `this` or `super`, so they transparently fit into the surrounding context. diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.png b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.png new file mode 100644 index 000000000..8d30622ce Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.png differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object@2x.png b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object@2x.png new file mode 100644 index 000000000..00f6bd80d Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object@2x.png differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.png new file mode 100644 index 000000000..f8afbbcd6 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.png differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2@2x.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2@2x.png new file mode 100644 index 000000000..cf5aa6554 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2@2x.png differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.png new file mode 100644 index 000000000..a6f8964e6 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.png differ diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal@2x.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal@2x.png new file mode 100644 index 000000000..2e3f4d7ff Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal@2x.png differ diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-run-animal.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal.png similarity index 100% rename from 1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-run-animal.png rename to 1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal.png diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-run-animal@2x.png b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal@2x.png similarity index 100% rename from 1-js/07-object-oriented-programming/10-class-inheritance/class-inheritance-rabbit-run-animal@2x.png rename to 1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-run-animal@2x.png diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.png b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.png new file mode 100644 index 000000000..79351a754 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.png differ diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal@2x.png b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal@2x.png new file mode 100644 index 000000000..346574e0c Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal@2x.png differ diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.png b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.png new file mode 100644 index 000000000..3d3b78cca Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.png differ diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit@2x.png b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit@2x.png new file mode 100644 index 000000000..a923d10e5 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit@2x.png differ diff --git a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.png b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.png new file mode 100644 index 000000000..b1871dc72 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.png differ diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop.png b/1-js/09-classes/02-class-inheritance/this-super-loop.png new file mode 100644 index 000000000..e68ed70e7 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/this-super-loop.png differ diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop@2x.png b/1-js/09-classes/02-class-inheritance/this-super-loop@2x.png new file mode 100644 index 000000000..037d07587 Binary files /dev/null and b/1-js/09-classes/02-class-inheritance/this-super-loop@2x.png differ diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.png b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.png new file mode 100644 index 000000000..c5e7e3e49 Binary files /dev/null and b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.png differ diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static@2x.png b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static@2x.png new file mode 100644 index 000000000..de434af2c Binary files /dev/null and b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static@2x.png differ diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md new file mode 100644 index 000000000..b940e04c8 --- /dev/null +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -0,0 +1,226 @@ + +# Static properties and methods + +We can also assign a method to the class function, not to its `"prototype"`. Such methods are called *static*. + +An example: + +```js run +class User { +*!* + static staticMethod() { +*/!* + alert(this === User); + } +} + +User.staticMethod(); // true +``` + +That actually does the same as assigning it as a function property: + +```js +function User() { } + +User.staticMethod = function() { + alert(this === User); +}; +``` + +The value of `this` inside `User.staticMethod()` is the class constructor `User` itself (the "object before dot" rule). + +Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it. + +For instance, we have `Article` objects and need a function to compare them. The natural choice would be `Article.compare`, like this: + +```js run +class Article { + constructor(title, date) { + this.title = title; + this.date = date; + } + +*!* + static compare(articleA, articleB) { + return articleA.date - articleB.date; + } +*/!* +} + +// usage +let articles = [ + new Article("HTML", new Date(2019, 1, 1)), + new Article("CSS", new Date(2019, 0, 1)), + new Article("JavaScript", new Date(2019, 11, 1)) +]; + +*!* +articles.sort(Article.compare); +*/!* + +alert( articles[0].title ); // CSS +``` + +Here `Article.compare` stands "over" the articles, as a means to compare them. It's not a method of an article, but rather of the whole class. + +Another example would be a so-called "factory" method. Imagine, we need few ways to create an article: + +1. Create by given parameters (`title`, `date` etc). +2. Create an empty article with today's date. +3. ... + +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: + +```js run +class Article { + constructor(title, date) { + this.title = title; + this.date = date; + } + +*!* + static createTodays() { + // remember, this = Article + return new this("Today's digest", new Date()); + } +*/!* +} + +let article = Article.createTodays(); + +alert( article.title ); // Todays digest +``` + +Now every time we need to create a today's digest, we can call `Article.createTodays()`. Once again, that's not a method of an article, but a method of the whole class. + +Static methods are also used in database-related classes to search/save/remove entries from the database, like this: + +```js +// assuming Article is a special class for managing articles +// static method to remove the article: +Article.remove({id: 12345}); +``` + +## Static properties + +[recent browser=Chrome] + +Static properties are also possible, just like regular class properties: + +```js run +class Article { + static publisher = "Ilya Kantor"; +} + +alert( Article.publisher ); // Ilya Kantor +``` + +That is the same as a direct assignment to `Article`: + +```js +Article.publisher = "Ilya Kantor"; +``` + +## Statics and inheritance + +Statics are inherited, we can access `Parent.method` as `Child.method`. + +For instance, `Animal.compare` in the code below is inherited and accessible as `Rabbit.compare`: + +```js run +class Animal { + + constructor(name, speed) { + this.speed = speed; + this.name = name; + } + + run(speed = 0) { + this.speed += speed; + alert(`${this.name} runs with speed ${this.speed}.`); + } + +*!* + static compare(animalA, animalB) { + return animalA.speed - animalB.speed; + } +*/!* + +} + +// Inherit from Animal +class Rabbit extends Animal { + hide() { + alert(`${this.name} hides!`); + } +} + +let rabbits = [ + new Rabbit("White Rabbit", 10), + new Rabbit("Black Rabbit", 5) +]; + +*!* +rabbits.sort(Rabbit.compare); +*/!* + +rabbits[0].run(); // Black Rabbit runs with speed 5. +``` + +Now we can call `Rabbit.compare` assuming that the inherited `Animal.compare` will be called. + +How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`. + + +![](animal-rabbit-static.png) + +So, `Rabbit` function now inherits from `Animal` function. And `Animal` function normally has `[[Prototype]]` referencing `Function.prototype`, because it doesn't `extend` anything. + +Here, let's check that: + +```js run +class Animal {} +class Rabbit extends Animal {} + +// for static properties and methods +alert(Rabbit.__proto__ === Animal); // true + +// the next step up leads to Function.prototype +alert(Animal.__proto__ === Function.prototype); // true + +// the "normal" prototype chain for object methods +alert(Rabbit.prototype.__proto__ === Animal.prototype); +``` + +This way `Rabbit` has access to all static methods of `Animal`. + +## Summary + +Static methods are used for the functionality that doesn't relate to a concrete class instance, doesn't require an instance to exist, but rather belongs to the class as a whole, like `Article.compare` -- a generic method to compare two articles. + +Static properties are used when we'd like to store class-level data, also not bound to an instance. + +The syntax is: + +```js +class MyClass { + static property = ...; + + static method() { + ... + } +} +``` + +That's technically the same as assigning to the class itself: + +```js +MyClass.property = ... +MyClass.method = ... +``` + +Static properties are inherited. + +Technically, for `class B extends A` the prototype of the class `B` itself points to `A`: `B.[[Prototype]] = A`. So if a field is not found in `B`, the search continues in `A`. 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 new file mode 100644 index 000000000..78d6c78f5 --- /dev/null +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -0,0 +1,330 @@ + +# Private and protected properties and methods + +One of the most important principles of object oriented programming -- delimiting internal interface from the external one. + +That is "a must" practice in developing anything more complex than a "hello world" app. + +To understand this, let's break away from development and turn our eyes into the real world. + +Usually, devices that we're using are quite complex. But delimiting the internal interface from the external one allows to use them without problems. + +## A real-life example + +For instance, a coffee machine. Simple from outside: a button, a display, a few holes...And, surely, the result -- great coffee! :) + +![](coffee.jpg) + +But inside... (a picture from the repair manual) + +![](coffee-inside.jpg) + +A lot of details. But we can use it without knowing anything. + +Coffee machines are quite reliable, aren't they? We can use one for years, and only if something goes wrong -- bring it for repairs. + +The secret of reliability and simplicity of a coffee machine -- all details are well-tuned and *hidden* inside. + +If we remove the protective cover from the coffee machine, then using it will be much more complex (where to press?), and dangerous (it can electrocute). + +As we'll see, in programming objects are like coffee machines. + +But in order to hide inner details, we'll use not a protective cover, but rather special syntax of the language and conventions. + +## Internal and external interface + +In object-oriented programming, properties and methods are split into two groups: + +- *Internal interface* -- methods and properties, accessible from other methods of the class, but not from the outside. +- *External interface* -- methods and properties, accessible also from outside the class. + +If we continue the analogy with the coffee machine -- what's hidden inside: a boiler tube, heating element, and so on -- is its internal interface. + +An internal interface is used for the object to work, its details use each other. For instance, a boiler tube is attached to the heating element. + +But from the outside a coffee machine is closed by the protective cover, so that no one can reach those. Details are hidden and inaccessible. We can use its features via the external interface. + +So, all we need to use an object is to know its external interface. We may be completely unaware how it works inside, and that's great. + +That was a general introduction. + +In JavaScript, there are three types of properties and members: + +- Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods. +- Private: accessible only from inside the class. These are for the internal interface. + +In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it. They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to properly do the extension. + +Protected fields are not implemented in JavaScript on the language level, but in practice they are very convenient, so they are emulated. + +In the next step we'll make a coffee machine in JavaScript with all these types of properties. A coffee machine has a lot of details, we won't model them to stay simple (though we could). + +## Protecting "waterAmount" + +Let's make a simple coffee machine class first: + +```js run +class CoffeeMachine { + waterAmount = 0; // the amount of water inside + + constructor(power) { + this.power = power; + alert( `Created a coffee-machine, power: ${power}` ); + } + +} + +// create the coffee machine +let coffeeMachine = new CoffeeMachine(100); + +// add water +coffeeMachine.waterAmount = 200; +``` + +Right now the properties `waterAmount` and `power` are public. We can easily get/set them from the outside to any value. + +Let's change `waterAmount` property to protected to have more control over it. For instance, we don't want anyone to set it below zero. + +**Protected properties are usually prefixed with an underscore `_`.** + +That is not enforced on the language level, but there's a convention that such properties and methods should not be accessed from the outside. Most programmers follow it. + +So our property will be called `_waterAmount`: + +```js run +class CoffeeMachine { + _waterAmount = 0; + + set waterAmount(value) { + if (value < 0) throw new Error("Negative water"); + this._waterAmount = value; + } + + get waterAmount() { + return this._waterAmount; + } + + constructor(power) { + this._power = power; + } + +} + +// create the coffee machine +let coffeeMachine = new CoffeeMachine(100); + +// add water +coffeeMachine.waterAmount = -10; // Error: Negative water +``` + +Now the access is under control, so setting the water below zero fails. + +## Read-only "power" + +For `power` property, let's make it read-only. It sometimes happens that a property must be set at creation time only, and then never modified. + +That's exactly the case for a coffee machine: power never changes. + +To do so, we only need to make getter, but not the setter: + +```js run +class CoffeeMachine { + // ... + + constructor(power) { + this._power = power; + } + + get power() { + return this._power; + } + +} + +// create the coffee machine +let coffeeMachine = new CoffeeMachine(100); + +alert(`Power is: ${coffeeMachine.power}W`); // Power is: 100W + +coffeeMachine.power = 25; // Error (no setter) +``` + +````smart header="Getter/setter functions" +Here we used getter/setter syntax. + +But most of the time `get.../set...` functions are preferred, like this: + +```js +class CoffeeMachine { + _waterAmount = 0; + + *!*setWaterAmount(value)*/!* { + if (value < 0) throw new Error("Negative water"); + this._waterAmount = value; + } + + *!*getWaterAmount()*/!* { + return this._waterAmount; + } +} + +new CoffeeMachine().setWaterAmount(100); +``` + +That looks a bit longer, but functions are more flexible. They can accept multiple arguments (even if we don't need them right now). So, for the future, just in case we need to refactor something, functions are a safer choice. + +Surely, there's a tradeoff. On the other hand, get/set syntax is shorter, so ultimately there's no strict rule, it's up to you to decide. +```` + +```smart header="Protected fields are inherited" +If we inherit `class MegaMachine extends CoffeeMachine`, then nothing prevents us from accessing `this._waterAmount` or `this._power` from the methods of the new class. + +So protected fields are naturally inheritable. Unlike private ones that we'll see below. +``` + +## Private "#waterLimit" + +[recent browser=none] + +There's a finished JavaScript proposal, almost in the standard, that provides language-level support for private properties and methods. + +Privates should start with `#`. They are only accessible from inside the class. + +For instance, here we add a private `#waterLimit` property and extract the water-checking logic into a separate method: + +```js +class CoffeeMachine { +*!* + #waterLimit = 200; +*/!* + +*!* + #checkWater(value) { + if (value < 0) throw new Error("Negative water"); + if (value > this.#waterLimit) throw new Error("Too much water"); + } +*/!* + + _waterAmount = 0; + + set waterAmount(value) { +*!* + this.#checkWater(value); +*/!* + this._waterAmount = value; + } + + get waterAmount() { + return this._waterAmount; + } + +} + +let coffeeMachine = new CoffeeMachine(); + +*!* +coffeeMachine.#checkWater(); // Error +coffeeMachine.#waterLimit = 1000; // Error +*/!* + +coffeeMachine.waterAmount = 100; // Works +``` + +On the language level, `#` is a special sign that the field is private. We can't access it from outside or from inheriting classes. + +Private fields do not conflict with public ones. We can have both private `#waterAmount` and public `waterAmount` fields at the same time. + +For instance, let's make `waterAmount` an accessor for `#waterAmount`: + +```js run +class CoffeeMachine { + + #waterAmount = 0; + + get waterAmount() { + return this.#waterAmount; + } + + set waterAmount(value) { + if (value < 0) throw new Error("Negative water"); + this.#waterAmount = value; + } +} + +let machine = new CoffeeMachine(); + +machine.waterAmount = 100; +alert(machine.#waterAmount); // Error +``` + +Unlike protected ones, private fields are enforced by the language itself. That's a good thing. + +But if we inherit from `CoffeeMachine`, then we'll have no direct access to `#waterAmount`. We'll need to rely on `waterAmount` getter/setter: + +```js +class MegaCoffeeMachine extends CoffeeMachine() { + method() { +*!* + alert( this.#waterAmount ); // Error: can only access from CoffeeMachine +*/!* + } +} +``` + +In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reason to access its internals. That's why protected fields are used most of the time, even though they are not supported by the language syntax. + +````warn +Private fields are special. + +Remember, usually we can access fields by this[name]: + +```js +class User { + ... + sayHi() { + let fieldName = "name"; + alert(`Hello, ${this[fieldName]}`); + } +} +``` + +With private fields that's impossible: `this['#name']` doesn't work. That's a syntax limitation to ensure privacy. +```` + +## Summary + +In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)"). + +It gives the following benefits: + +Protection for users, so that they don't shoot themselves in the feet +: Imagine, there's a team of developers using a coffee machine. It was made by the "Best CoffeeMachine" company, and works fine, but a protective cover was removed. So the internal interface is exposed. + + All developers are civilized -- they use the coffee machine as intended. But one of them, John, decided that he's the smartest one, and made some tweaks in the coffee machine internals. So the coffee machine failed two days later. + + That's surely not John's fault, but rather the person who removed the protective cover and let John do his manipulations. + + The same in programming. If a user of a class will change things not intended to be changed from the outside -- the consequences are unpredictable. + +Supportable +: The situation in programming is more complex than with a real-life coffee machine, because we don't just buy it once. The code constantly undergoes development and improvement. + + **If we strictly delimit the internal interface, then the developer of the class can freely change its internal properties and methods, even without informing the users..** + + It's much easier to develop, if you know that certain methods can be renamed, their parameters can be changed, and even removed, because no external code depends on them. + + For users, when a new version comes out, it may be a total overhaul, but still simple to upgrade if the external interface is the same. + +Hiding complexity +: People adore to use things that are simple. At least from outside. What's inside is a different thing. + + Programmers are not an exception. + + **It's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.** + +To hide internal interface we use either protected or public properties: + +- Protected fields start with `_`. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with `_` from its class and classes inheriting from it. +- Private fields start with `#`. JavaScript makes sure we only can access those from inside the class. + +Right now, private fields are not well-supported among browsers, but can be polyfilled. diff --git a/1-js/09-classes/04-private-protected-properties-methods/coffee-inside.jpg b/1-js/09-classes/04-private-protected-properties-methods/coffee-inside.jpg new file mode 100644 index 000000000..60f84664d Binary files /dev/null and b/1-js/09-classes/04-private-protected-properties-methods/coffee-inside.jpg differ diff --git a/1-js/09-classes/04-private-protected-properties-methods/coffee.jpg b/1-js/09-classes/04-private-protected-properties-methods/coffee.jpg new file mode 100644 index 000000000..ee26e1c06 Binary files /dev/null and b/1-js/09-classes/04-private-protected-properties-methods/coffee.jpg differ diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md new file mode 100644 index 000000000..f85aa0242 --- /dev/null +++ b/1-js/09-classes/05-extend-natives/article.md @@ -0,0 +1,90 @@ + +# Extending built-in classes + +Built-in classes like Array, Map and others are extendable also. + +For instance, here `PowerArray` inherits from the native `Array`: + +```js run +// add one more method to it (can do more) +class PowerArray extends Array { + isEmpty() { + return this.length === 0; + } +} + +let arr = new PowerArray(1, 2, 5, 10, 50); +alert(arr.isEmpty()); // false + +let filteredArr = arr.filter(item => item >= 10); +alert(filteredArr); // 10, 50 +alert(filteredArr.isEmpty()); // false +``` + +Please note a very interesting thing. Built-in methods like `filter`, `map` and others -- return new objects of exactly the inherited type. They rely on the `constructor` property to do so. + +In the example above, +```js +arr.constructor === PowerArray +``` + +So when `arr.filter()` is called, it internally creates the new array of results using exactly `new PowerArray`, not basic `Array`. That's actually very cool, because we can keep using `PowerArray` methods further on the result. + +Even more, we can customize that behavior. + +We can add a special static getter `Symbol.species` to the class. If exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on. + +If we'd like built-in methods like `map`, `filter` will return regular arrays, we can return `Array` in `Symbol.species`, like here: + +```js run +class PowerArray extends Array { + isEmpty() { + return this.length === 0; + } + +*!* + // built-in methods will use this as the constructor + static get [Symbol.species]() { + return Array; + } +*/!* +} + +let arr = new PowerArray(1, 2, 5, 10, 50); +alert(arr.isEmpty()); // false + +// filter creates new array using arr.constructor[Symbol.species] as constructor +let filteredArr = arr.filter(item => item >= 10); + +*!* +// filteredArr is not PowerArray, but Array +*/!* +alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function +``` + +As you can see, now `.filter` returns `Array`. So the extended functionality is not passed any further. + +## No static inheritance in built-ins + +Built-in objects have their own static methods, for instance `Object.keys`, `Array.isArray` etc. + +As we already know, native classes extend each other. For instance, `Array` extends `Object`. + +Normally, when one class extends another, both static and non-static methods are inherited. + +So, if `Rabbit extends Animal`, then: + +1. `Rabbit.methods` are callable for `Animal.methods`, because `Rabbit.[[Prototype]] = Animal`. +2. `new Rabbit().methods` are also available, because `Rabbit.prototype.[[Prototype]] = Animal.prototype`. + +That's thoroughly explained in the chapter [](info:static-properties-methods#statics-and-inheritance). + +But built-in classes are an exception. They don't inherit statics `(1)` from each other. + +For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not point to `Object`. So there's `Object.keys()`, but not `Array.keys()` and `Date.keys()`. + +Here's the picture structure for `Date` and `Object`: + +![](object-date-inheritance.png) + +Note, there's no link between `Date` and `Object`. Both `Object` and `Date` exist independently. `Date.prototype` inherits from `Object.prototype`, but that's all. diff --git a/1-js/09-classes/05-extend-natives/object-date-inheritance.png b/1-js/09-classes/05-extend-natives/object-date-inheritance.png new file mode 100644 index 000000000..73020a49e Binary files /dev/null and b/1-js/09-classes/05-extend-natives/object-date-inheritance.png differ diff --git a/1-js/09-classes/05-extend-natives/object-date-inheritance@2x.png b/1-js/09-classes/05-extend-natives/object-date-inheritance@2x.png new file mode 100644 index 000000000..6520f9e26 Binary files /dev/null and b/1-js/09-classes/05-extend-natives/object-date-inheritance@2x.png differ diff --git a/1-js/07-object-oriented-programming/11-instanceof/1-strange-instanceof/solution.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/11-instanceof/1-strange-instanceof/solution.md rename to 1-js/09-classes/06-instanceof/1-strange-instanceof/solution.md diff --git a/1-js/07-object-oriented-programming/11-instanceof/1-strange-instanceof/task.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md similarity index 100% rename from 1-js/07-object-oriented-programming/11-instanceof/1-strange-instanceof/task.md rename to 1-js/09-classes/06-instanceof/1-strange-instanceof/task.md diff --git a/1-js/07-object-oriented-programming/11-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md similarity index 91% rename from 1-js/07-object-oriented-programming/11-instanceof/article.md rename to 1-js/09-classes/06-instanceof/article.md index 702c9e6b2..dca6ac627 100644 --- a/1-js/07-object-oriented-programming/11-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -46,14 +46,15 @@ alert( arr instanceof Object ); // true Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypally inherits from `Object`. -The `instanceof` operator examines the prototype chain for the check, and is also fine-tunable using the static method `Symbol.hasInstance`. +The `instanceof` operator examines the prototype chain for the check, but we can set a custom logic the static method `Symbol.hasInstance`. The algorithm of `obj instanceof Class` works roughly as follows: -1. If there's a static method `Symbol.hasInstance`, then use it. Like this: +1. If there's a static method `Symbol.hasInstance`, then just call it: `Class[Symbol.hasInstance](obj)`. It should return either `true` or `false`. We're done. + For example: ```js run - // assume anything that canEat is an animal + // setup instanceOf check that assumes that anything that canEat is an animal class Animal { static [Symbol.hasInstance](obj) { if (obj.canEat) return true; @@ -61,10 +62,11 @@ The algorithm of `obj instanceof Class` works roughly as follows: } let obj = { canEat: true }; + alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called ``` -2. Most classes do not have `Symbol.hasInstance`. In that case, check if `Class.prototype` equals to one of prototypes in the `obj` prototype chain. +2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Classs` checks whether `Class.prototype` equals to one of prototypes in the `obj` prototype chain. In other words, compare: ```js @@ -117,7 +119,7 @@ alert( rabbit instanceof Rabbit ); // false That's one of the reasons to avoid changing `prototype`. Just to keep safe. -## Bonus: Object toString for the type +## Bonus: Object.prototype.toString for the type We already know that plain objects are converted to string as `[object Object]`: @@ -182,7 +184,7 @@ alert( {}.toString.call(user) ); // [object User] For most environment-specific objects, there is such a property. Here are few browser specific examples: ```js run -// toStringTag for the envinronment-specific object and class: +// toStringTag for the environment-specific object and class: alert( window[Symbol.toStringTag]); // window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest diff --git a/1-js/09-classes/06-instanceof/instanceof.png b/1-js/09-classes/06-instanceof/instanceof.png new file mode 100644 index 000000000..ad0fc77ab Binary files /dev/null and b/1-js/09-classes/06-instanceof/instanceof.png differ diff --git a/1-js/09-classes/06-instanceof/instanceof@2x.png b/1-js/09-classes/06-instanceof/instanceof@2x.png new file mode 100644 index 000000000..c25b166b3 Binary files /dev/null and b/1-js/09-classes/06-instanceof/instanceof@2x.png differ diff --git a/1-js/07-object-oriented-programming/13-mixins/article.md b/1-js/09-classes/07-mixins/article.md similarity index 100% rename from 1-js/07-object-oriented-programming/13-mixins/article.md rename to 1-js/09-classes/07-mixins/article.md diff --git a/1-js/07-object-oriented-programming/13-mixins/head.html b/1-js/09-classes/07-mixins/head.html similarity index 100% rename from 1-js/07-object-oriented-programming/13-mixins/head.html rename to 1-js/09-classes/07-mixins/head.html diff --git a/1-js/09-classes/07-mixins/mixin-inheritance.png b/1-js/09-classes/07-mixins/mixin-inheritance.png new file mode 100644 index 000000000..6142ce7fc Binary files /dev/null and b/1-js/09-classes/07-mixins/mixin-inheritance.png differ diff --git a/1-js/09-classes/07-mixins/mixin-inheritance@2x.png b/1-js/09-classes/07-mixins/mixin-inheritance@2x.png new file mode 100644 index 000000000..ccbd74300 Binary files /dev/null and b/1-js/09-classes/07-mixins/mixin-inheritance@2x.png differ diff --git a/1-js/09-classes/index.md b/1-js/09-classes/index.md new file mode 100644 index 000000000..87846ef6b --- /dev/null +++ b/1-js/09-classes/index.md @@ -0,0 +1 @@ +# Classes diff --git a/1-js/08-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md similarity index 100% rename from 1-js/08-error-handling/1-try-catch/1-finally-or-code-after/solution.md rename to 1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md diff --git a/1-js/08-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md similarity index 86% rename from 1-js/08-error-handling/1-try-catch/1-finally-or-code-after/task.md rename to 1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md index e84687343..c573cc232 100644 --- a/1-js/08-error-handling/1-try-catch/1-finally-or-code-after/task.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md @@ -33,6 +33,6 @@ Compare the two code fragments. */!* ``` -We definitely need the cleanup after the work has started, doesn't matter if there was an error or not. +We definitely need the cleanup after the work, doesn't matter if there was an error or not. Is there an advantage here in using `finally` or both code fragments are equal? If there is such an advantage, then give an example when it matters. diff --git a/1-js/08-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md similarity index 94% rename from 1-js/08-error-handling/1-try-catch/article.md rename to 1-js/10-error-handling/1-try-catch/article.md index ac3e0031b..4a1adba0c 100644 --- a/1-js/08-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -68,7 +68,7 @@ Let's see more examples. } catch(err) { - alert(`Error has occured!`); // *!*(3) <--*/!* + alert(`Error has occurred!`); // *!*(3) <--*/!* } @@ -108,14 +108,14 @@ try { } ``` -That's because `try..catch` actually wraps the `setTimeout` call that schedules the function. But the function itself is executed later, when the engine has already left the `try..catch` construct. +That's because the function itself is executed later, when the engine has already left the `try..catch` construct. To catch an exception inside a scheduled function, `try..catch` must be inside that function: ```js run setTimeout(function() { try { noSuchVariable; // try..catch handles the error! - } catch (e) { + } catch { alert( "error is caught here!" ); } }, 1000); @@ -165,6 +165,19 @@ try { } ``` +## Optional "catch" binding + +[recent browser=new] + +If we don't need error details, `catch` may omit it: + +```js +try { + // ... +} catch { + // error object omitted +} +``` ## Using "try..catch" @@ -325,7 +338,7 @@ Now `catch` became a single place for all error handling: both for `JSON.parse` ## Rethrowing -In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a variable is undefined or something else, not just that "incorrect data" thing. +In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just that "incorrect data" thing. Like this: @@ -342,7 +355,7 @@ try { } ``` -Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a crazy bug may be discovered that leads to terrible hacks (like it happened with the `ssh` tool). +Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a bug may be discovered that leads to terrible hacks. In our case, `try..catch` is meant to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. @@ -476,7 +489,7 @@ The code has two ways of execution: 1. If you answer "Yes" to "Make an error?", then `try -> catch -> finally`. 2. If you say "No", then `try -> finally`. -The `finally` clause is often used when we start doing something before `try..catch` and want to finalize it in any case of outcome. +The `finally` clause is often used when we start doing something and want to finalize it in any case of outcome. For instance, we want to measure the time that a Fibonacci numbers function `fib(n)` takes. Naturally, we can start measuring before it runs and finish afterwards. But what if there's an error during the function call? In particular, the implementation of `fib(n)` in the code below returns an error for negative or non-integer numbers. @@ -508,14 +521,14 @@ try { } */!* -alert(result || "error occured"); +alert(result || "error occurred"); alert( `execution took ${diff}ms` ); ``` You can check by running the code with entering `35` into `prompt` -- it executes normally, `finally` after `try`. And then enter `-1` -- there will be an immediate error, an the execution will take `0ms`. Both measurements are done correctly. -In other words, there may be two ways to exit a function: either a `return` or `throw`. The `finally` clause handles them both. +In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases. ```smart header="Variables are local inside `try..catch..finally`" @@ -577,7 +590,7 @@ Let's imagine we've got a fatal error outside of `try..catch`, and the script di Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages) etc. -There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.JS has [process.on('uncaughtException')](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property. It will run in case of an uncaught error. +There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [process.on('uncaughtException')](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property. It will run in case of an uncaught error. The syntax: @@ -630,7 +643,7 @@ They work like this: ## Summary -The `try..catch` construct allows to handle runtime errors. It literally allows to try running the code and catch errors that may occur in it. +The `try..catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. The syntax is: @@ -653,6 +666,8 @@ Error objects have following properties: - `name` -- the string with error name (error constructor name). - `stack` (non-standard) -- the stack at the moment of error creation. +If an error object is not needed, we can omit it by using `catch {` instead of `catch(err) {`. + We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter. Rethrowing is a basic pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know. diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow.png b/1-js/10-error-handling/1-try-catch/try-catch-flow.png new file mode 100644 index 000000000..8b3775381 Binary files /dev/null and b/1-js/10-error-handling/1-try-catch/try-catch-flow.png differ diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png b/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png new file mode 100644 index 000000000..f85eb6321 Binary files /dev/null and b/1-js/10-error-handling/1-try-catch/try-catch-flow@2x.png differ diff --git a/1-js/08-error-handling/2-custom-errors/1-format-error/solution.md b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md similarity index 100% rename from 1-js/08-error-handling/2-custom-errors/1-format-error/solution.md rename to 1-js/10-error-handling/2-custom-errors/1-format-error/solution.md diff --git a/1-js/08-error-handling/2-custom-errors/1-format-error/task.md b/1-js/10-error-handling/2-custom-errors/1-format-error/task.md similarity index 100% rename from 1-js/08-error-handling/2-custom-errors/1-format-error/task.md rename to 1-js/10-error-handling/2-custom-errors/1-format-error/task.md diff --git a/1-js/08-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md similarity index 87% rename from 1-js/08-error-handling/2-custom-errors/article.md rename to 1-js/10-error-handling/2-custom-errors/article.md index d7d61166d..70ff1dec1 100644 --- a/1-js/08-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -6,7 +6,7 @@ Our errors should support basic error properties like `message`, `name` and, pre JavaScript allows to use `throw` with any argument, so technically our custom error classes don't need to inherit from `Error`. But if we inherit, then it becomes possible to use `obj instanceof Error` to identify error objects. So it's better to inherit from it. -As we build our application, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`, and so on. +As the application grows, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`, and so on. ## Extending Error @@ -19,7 +19,7 @@ let json = `{ "name": "John", "age": 30 }`; Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it throws `SyntaxError`. -But even if `json` is syntactically correct, that doesn't mean that it's a valid user, right? It may miss the necessary data. For instance, if may not have `name` and `age` properties that are essential for our users. +But even if `json` is syntactically correct, that doesn't mean that it's a valid user, right? It may miss the necessary data. For instance, it may not have `name` and `age` properties that are essential for our users. 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. @@ -126,7 +126,7 @@ We could also look at `err.name`, like this: 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` only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or such) 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 (due to a typo in the code or other unknown ones) should fall through. ## Further inheritance @@ -185,7 +185,7 @@ try { The new class `PropertyRequiredError` is easy to use: we only need to pass the property name: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor. -Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedius -- to assign `this.name = ` when creating each custom error. But there's a way out. We can make our own "basic error" class that removes this burden from our shoulders by using `this.constructor.name` for `this.name` in the constructor. And then inherit from it. +Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. But there's a way out. We can make our own "basic error" class that removes this burden from our shoulders by using `this.constructor.name` for `this.name` in its constructor. And then inherit all ours custom errors from it. Let's call it `MyError`. @@ -218,7 +218,7 @@ Now custom errors are much shorter, especially `ValidationError`, as we got rid ## Wrapping exceptions -The purpose of the function `readUser` in the code above is "to read the user data", right? There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow: the new code will probably generate other kinds of errors. +The purpose of the function `readUser` in the code above is "to read the user data", right? There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow and probably generate other kinds of errors. The code which calls `readUser` should handle these errors. Right now it uses multiple `if` in the `catch` block to check for different error types and rethrow the unknown ones. But if `readUser` function generates several kinds of errors -- then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`? @@ -303,5 +303,5 @@ The approach is called "wrapping exceptions", because we take "low level excepti ## Summary - We can inherit from `Error` and other built-in error classes normally, just need to take care of `name` property and don't forget to call `super`. -- Most of the time, we should use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from the 3rd-party library and there's no easy way to get the class. Then `name` property can be used for such checks. -- Wrapping exceptions is a widespread technique when a function handles low-level exceptions and makes a higher-level object to report about the errors. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required. +- We can use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from the 3rd-party library and there's no easy way to get the class. Then `name` property can be used for such checks. +- Wrapping exceptions is a widespread technique: a function handles low-level exceptions and creates higher-level errors instead of various low-level ones. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required. diff --git a/1-js/08-error-handling/index.md b/1-js/10-error-handling/index.md similarity index 100% rename from 1-js/08-error-handling/index.md rename to 1-js/10-error-handling/index.md diff --git a/1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.md b/1-js/11-async/01-callbacks/01-animate-circle-callback/solution.md similarity index 100% rename from 1-js/07-object-oriented-programming/10-class-inheritance/2-clock-class-extended/solution.md rename to 1-js/11-async/01-callbacks/01-animate-circle-callback/solution.md diff --git a/6-async/01-callbacks/01-animate-circle-callback/solution.view/index.html b/1-js/11-async/01-callbacks/01-animate-circle-callback/solution.view/index.html similarity index 100% rename from 6-async/01-callbacks/01-animate-circle-callback/solution.view/index.html rename to 1-js/11-async/01-callbacks/01-animate-circle-callback/solution.view/index.html diff --git a/6-async/01-callbacks/01-animate-circle-callback/task.md b/1-js/11-async/01-callbacks/01-animate-circle-callback/task.md similarity index 100% rename from 6-async/01-callbacks/01-animate-circle-callback/task.md rename to 1-js/11-async/01-callbacks/01-animate-circle-callback/task.md diff --git a/6-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md similarity index 94% rename from 6-async/01-callbacks/article.md rename to 1-js/11-async/01-callbacks/article.md index a5a91793e..ee0bb3caa 100644 --- a/6-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -98,7 +98,7 @@ Here we did it in `loadScript`, but of course, it's a general approach. ## Callback in callback -How to load two scripts sequentially: the first one, and then the second one after it? +How can we load two scripts sequentially: the first one, and then the second one after it? The natural solution would be to put the second `loadScript` call inside the callback, like this: @@ -140,7 +140,7 @@ So, every new action is inside a callback. That's fine for few actions, but not ## Handling errors -In examples above we didn't consider errors. What if the script loading fails? Our callback should be able to react on that. +In the above examples we didn't consider errors. What if the script loading fails? Our callback should be able to react on that. Here's an improved version of `loadScript` that tracks loading errors: @@ -262,7 +262,7 @@ function step3(error, script) { See? It does the same, and there's no deep nesting now because we made every action a separate top-level function. -It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that. One needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump. +It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that one needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump. Also, the functions named `step*` are all of single use, they are created only to avoid the "pyramid of doom." No one is going to reuse them outside of the action chain. So there's a bit of a namespace cluttering here. diff --git a/1-js/11-async/01-callbacks/callback-hell.png b/1-js/11-async/01-callbacks/callback-hell.png new file mode 100644 index 000000000..567ec284e Binary files /dev/null and b/1-js/11-async/01-callbacks/callback-hell.png differ diff --git a/1-js/11-async/01-callbacks/callback-hell@2x.png b/1-js/11-async/01-callbacks/callback-hell@2x.png new file mode 100644 index 000000000..4d6b0ad70 Binary files /dev/null and b/1-js/11-async/01-callbacks/callback-hell@2x.png differ diff --git a/6-async/01-callbacks/one.js b/1-js/11-async/01-callbacks/one.js similarity index 100% rename from 6-async/01-callbacks/one.js rename to 1-js/11-async/01-callbacks/one.js diff --git a/6-async/02-promise-basics/01-re-resolve/solution.md b/1-js/11-async/02-promise-basics/01-re-resolve/solution.md similarity index 100% rename from 6-async/02-promise-basics/01-re-resolve/solution.md rename to 1-js/11-async/02-promise-basics/01-re-resolve/solution.md diff --git a/6-async/02-promise-basics/01-re-resolve/task.md b/1-js/11-async/02-promise-basics/01-re-resolve/task.md similarity index 100% rename from 6-async/02-promise-basics/01-re-resolve/task.md rename to 1-js/11-async/02-promise-basics/01-re-resolve/task.md diff --git a/6-async/02-promise-basics/02-delay-promise/solution.md b/1-js/11-async/02-promise-basics/02-delay-promise/solution.md similarity index 100% rename from 6-async/02-promise-basics/02-delay-promise/solution.md rename to 1-js/11-async/02-promise-basics/02-delay-promise/solution.md diff --git a/6-async/02-promise-basics/02-delay-promise/task.md b/1-js/11-async/02-promise-basics/02-delay-promise/task.md similarity index 100% rename from 6-async/02-promise-basics/02-delay-promise/task.md rename to 1-js/11-async/02-promise-basics/02-delay-promise/task.md diff --git a/3-animation/2-css-animations/3-animate-circle/solution.md b/1-js/11-async/02-promise-basics/03-animate-circle-promise/solution.md similarity index 100% rename from 3-animation/2-css-animations/3-animate-circle/solution.md rename to 1-js/11-async/02-promise-basics/03-animate-circle-promise/solution.md diff --git a/6-async/02-promise-basics/03-animate-circle-promise/solution.view/index.html b/1-js/11-async/02-promise-basics/03-animate-circle-promise/solution.view/index.html similarity index 100% rename from 6-async/02-promise-basics/03-animate-circle-promise/solution.view/index.html rename to 1-js/11-async/02-promise-basics/03-animate-circle-promise/solution.view/index.html diff --git a/6-async/02-promise-basics/03-animate-circle-promise/task.md b/1-js/11-async/02-promise-basics/03-animate-circle-promise/task.md similarity index 100% rename from 6-async/02-promise-basics/03-animate-circle-promise/task.md rename to 1-js/11-async/02-promise-basics/03-animate-circle-promise/task.md diff --git a/6-async/02-promise-basics/article.md b/1-js/11-async/02-promise-basics/article.md similarity index 65% rename from 6-async/02-promise-basics/article.md rename to 1-js/11-async/02-promise-basics/article.md index 6f0ebe704..bec6851af 100644 --- a/6-async/02-promise-basics/article.md +++ b/1-js/11-async/02-promise-basics/article.md @@ -8,7 +8,7 @@ Everyone is happy, because the people don't crowd you any more, and fans, becaus This is a real-life analogy for things we often have in programming: -1. A "producing code" that does something and takes time. For instance, the code loads a remote script. That's a "singer". +1. A "producing code" that does something and takes time. For instance, the code loads data over a network. That's a "singer". 2. A "consuming code" that wants the result of the "producing code" once it's ready. Many functions may need that result. These are the "fans". 3. A *promise* is a special JavaScript object that links the "producing code" and the "consuming code" together. In terms of our analogy: this is the "subscription list". The "producing code" takes whatever time it needs to produce the promised result, and the "promise" makes that result available to all of the subscribed code when it's ready. @@ -27,7 +27,7 @@ The function passed to `new Promise` is called the *executor*. When the promise The resulting `promise` object has internal properties: - `state` — initially "pending", then changes to either "fulfilled" or "rejected", -- `result` — an arbitrary value of your choosing, initially `undefined`. +- `result` — an arbitrary value, initially `undefined`. When the executor finishes the job, it should call one of the functions that it gets as arguments: @@ -48,15 +48,15 @@ Here's an example of a Promise constructor and a simple executor function with i let promise = new Promise(function(resolve, reject) { // the function is executed automatically when the promise is constructed - // after 1 second signal that the job is done with the result "done!" - setTimeout(() => *!*resolve("done!")*/!*, 1000); + // after 1 second signal that the job is done with the result "done" + setTimeout(() => *!*resolve("done")*/!*, 1000); }); ``` We can see two things by running the code above: 1. The executor is called automatically and immediately (by the `new Promise`). -2. The executor receives two arguments: `resolve` and `reject` — these functions are pre-defined by the JavaScript engine. So we don't need to create them. Instead, we should write the executor to call them when ready. +2. The executor receives two arguments: `resolve` and `reject` — these functions are pre-defined by the JavaScript engine. So we don't need to create them. We only should call one of them when ready. After one second of "processing" the executor calls `resolve("done")` to produce the result: @@ -77,10 +77,10 @@ let promise = new Promise(function(resolve, reject) { To summarize, the executor should do a job (something that takes time usually) and then call `resolve` or `reject` to change the state of the corresponding Promise object. -The Promise that is either resolved or rejected is called "settled", as opposed to a "pending" Promise. +The Promise that is either resolved or rejected is called "settled", as opposed to a initially "pending" Promise. ````smart header="There can be only a single result or an error" -The executor should call only one `resolve` or `reject`. The promise's state change is final. +The executor should call only one `resolve` or one `reject`. The promise's state change is final. All further calls of `resolve` and `reject` are ignored: @@ -95,11 +95,11 @@ let promise = new Promise(function(resolve, reject) { The idea is that a job done by the executor may have only one result or an error. -Further, `resolve`/`reject` expect only one argument and will ignore additional arguments. +Also, `resolve`/`reject` expect only one argument (or none) and will ignore additional arguments. ```` ```smart header="Reject with `Error` objects" -In case if something goes wrong, we can call `reject` with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent. +In case something goes wrong, we can call `reject` with any type of argument (just like `resolve`). But it is recommended to use `Error` objects (or objects that inherit from `Error`). The reasoning for that will soon become apparent. ``` ````smart header="Immediately calling `resolve`/`reject`" @@ -112,20 +112,24 @@ let promise = new Promise(function(resolve, reject) { }); ``` -For instance, this might happen when we start to do a job but then see that everything has already been completed. +For instance, this might happen when we start to do a job but then see that everything has already been completed and cached. -That's fine. We immediately have a resolved Promise, nothing wrong with that. +That's fine. We immediately have a resolved promise. ```` ```smart header="The `state` and `result` are internal" -The properties `state` and `result` of the Promise object are internal. We can't directly access them from our "consuming code". We can use the methods `.then`/`.catch` for that. They are described below. +The properties `state` and `result` of the Promise object are internal. We can't directly access them from our "consuming code". We can use the methods `.then`/`.catch`/`.finally` for that. They are described below. ``` -## Consumers: "then" and "catch" +## Consumers: then, catch, finally -A Promise object serves as a link between the executor (the "producing code" or "singer") and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using the methods `.then` and `.catch`. +A Promise object serves as a link between the executor (the "producing code" or "singer") and the consuming functions (the "fans"), which will receive the result or error. Consuming functions can be registered (subscribed) using methods `.then`, `.catch` and `.finally`. -The syntax of `.then` is: +### then + +The most important, fundamental one is `.then`. + +The syntax is: ```js promise.then( @@ -136,15 +140,15 @@ promise.then( The first argument of `.then` is a function that: -1. runs when the Promise is resolved, and +1. runs when the promise is resolved, and 2. receives the result. The second argument of `.then` is a function that: -1. runs when the Promise is rejected, and +1. runs when the promise is rejected, and 2. receives the error. -For instance, here's the reaction to a successfuly resolved promise: +For instance, here's a reaction to a successfully resolved promise: ```js run let promise = new Promise(function(resolve, reject) { @@ -190,6 +194,8 @@ promise.then(alert); // shows "done!" after 1 second */!* ``` +### catch + If we're interested only in errors, then we can use `null` as the first argument: `.then(null, errorHandlingFunction)`. Or we can use `.catch(errorHandlingFunction)`, which is exactly the same: @@ -206,45 +212,68 @@ promise.catch(alert); // shows "Error: Whoops!" after 1 second The call `.catch(f)` is a complete analog of `.then(null, f)`, it's just a shorthand. -````smart header="On settled promises `then` runs immediately" -If a promise is pending, `.then/catch` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately: +### finally -```js run -// an immediately resolved promise -let promise = new Promise(resolve => resolve("done!")); +Just like there's a `finally` clause in a regular `try {...} catch {...}`, there's `finally` in promises. -promise.then(alert); // done! (shows up right now) +The call `.finally(f)` is similar to `.then(f, f)` in the sense that it always runs when the promise is settled: be it resolve or reject. + +`finally` is a good handler for performing cleanup, e.g. stopping our loading indicators, as they are not needed any more, no matter what the outcome is. + +Like this: + +```js +new Promise((resolve, reject) => { + /* do something that takes time, and then call resolve/reject */ +}) +*!* + // runs when the promise is settled, doesn't matter successfully or not + .finally(() => stop loading indicator) +*/!* + .then(result => show result, err => show error) ``` -Some tasks may sometimes require time and sometimes finish immediately. The good thing is: the `.then` handler is guaranteed to run in both cases. -```` +It's not exactly an alias of `then(f,f)` though. There are several important differences: -````smart header="Handlers of `.then`/`.catch` are always asynchronous" -Even when the Promise is immediately resolved, code which occurs on lines *below* your `.then`/`.catch` may still execute first. +1. A `finally` handler has no arguments. In `finally` we don't know whether the promise is successful or not. That's all right, as our task is usually to perform "general" finalizing procedures. +2. A `finally` handler passes through results and errors to the next handler. -The JavaScript engine has an internal execution queue which gets all `.then/catch` handlers. + For instance, here the result is passed through `finally` to `then`: + ```js run + new Promise((resolve, reject) => { + setTimeout(() => resolve("result"), 2000) + }) + .finally(() => alert("Promise ready")) + .then(result => alert(result)); // <-- .then handles the result + ``` -But it only looks into that queue when the current execution is finished. + And here there's an error in the promise, passed through `finally` to `catch`: -In other words, `.then/catch` handlers are pending execution until the engine is done with the current code. + ```js run + new Promise((resolve, reject) => { + throw new Error("error"); + }) + .finally(() => alert("Promise ready")) + .catch(err => alert(err)); // <-- .catch handles the error object + ``` -For instance, here: + That's very convenient, because `finally` is not meant to process a promise result. So it passes it through. -```js run -// an "immediately" resolved Promise -const executor = resolve => resolve("done!"); -const promise = new Promise(executor); + We'll talk more about promise chaining and result-passing between handlers in the next chapter. -promise.then(alert); // this alert shows last (*) +3. Last, but not least, `.finally(f)` is a more convenient syntax than `.then(f, f)`: no need to duplicate the function `f`. -alert("code finished"); // this alert shows first -``` +````smart header="On settled promises handlers runs immediately" +If a promise is pending, `.then/catch/finally` handlers wait for the result. Otherwise, if a promise has already settled, they execute immediately: -The promise becomes settled immediately, but the engine first finishes the current code, calls `alert`, and only *afterwards* looks into the queue to run `.then` handler. +```js run +// an immediately resolved promise +let promise = new Promise(resolve => resolve("done!")); -So the code *after* `.then` ends up always running *before* the Promise's subscribers, even in the case of an immediately-resolved Promise. +promise.then(alert); // done! (shows up right now) +``` -Usually that's unimportant, but in some scenarios the order may matter a great deal. +The good thing is: a `.then` handler is guaranteed to run whether the promise takes time or settles it immediately. ```` Next, let's see more practical examples of how promises can help us to write asynchronous code. @@ -261,7 +290,7 @@ function loadScript(src, callback) { script.src = src; script.onload = () => callback(null, script); - script.onerror = () => callback(new Error(`Script load error ` + src)); + script.onerror = () => callback(new Error(`Script load error for ${src}`)); document.head.append(script); } @@ -278,7 +307,7 @@ function loadScript(src) { script.src = src; script.onload = () => resolve(script); - script.onerror = () => reject(new Error("Script load error: " + src)); + script.onerror = () => reject(new Error(`Script load error for ${src}`)); document.head.append(script); }); @@ -288,7 +317,7 @@ function loadScript(src) { Usage: ```js run -let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js"); +let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"); promise.then( script => alert(`${script.src} is loaded!`), @@ -300,11 +329,10 @@ promise.then(script => alert('One more handler to do something else!')); We can immediately see a few benefits over the callback-based pattern: -```compare minus="Callbacks" plus="Promises" -- We must have a ready `callback` function when calling `loadScript`. In other words, we must know what to do with the result *before* `loadScript` is called. -- There can be only one callback. -+ Promises allow us to do things in the natural order. First, we run `loadScript`, and `.then` we write what to do with the result. -+ We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next section: [Promise Chaining](/promise-chaining). -``` -So Promises already give us better code flow and flexibility. But there's more. We'll see that in the next chapters. +| Promises | Callbacks | +|----------|-----------| +| Promises allow us to do things in the natural order. First, we run `loadScript(script)`, and `.then` we write what to do with the result. | We must have a `callback` function at our disposal when calling `loadScript(script, callback)`. In other words, we must know what to do with the result *before* `loadScript` is called. | +| We can call `.then` on a Promise as many times as we want. Each time, we're adding a new "fan", a new subscribing function, to the "subscription list". More about this in the next chapter: [](info:promise-chaining). | There can be only one callback. | + +So Promises give us better code flow and flexibility. But there's more. We'll see that in the next chapters. diff --git a/6-async/02-promise-basics/head.html b/1-js/11-async/02-promise-basics/head.html similarity index 100% rename from 6-async/02-promise-basics/head.html rename to 1-js/11-async/02-promise-basics/head.html diff --git a/6-async/02-promise-basics/promise-init.png b/1-js/11-async/02-promise-basics/promise-init.png similarity index 100% rename from 6-async/02-promise-basics/promise-init.png rename to 1-js/11-async/02-promise-basics/promise-init.png diff --git a/6-async/02-promise-basics/promise-init@2x.png b/1-js/11-async/02-promise-basics/promise-init@2x.png similarity index 100% rename from 6-async/02-promise-basics/promise-init@2x.png rename to 1-js/11-async/02-promise-basics/promise-init@2x.png diff --git a/1-js/11-async/02-promise-basics/promise-reject-1.png b/1-js/11-async/02-promise-basics/promise-reject-1.png new file mode 100644 index 000000000..3ae74879c Binary files /dev/null and b/1-js/11-async/02-promise-basics/promise-reject-1.png differ diff --git a/1-js/11-async/02-promise-basics/promise-reject-1@2x.png b/1-js/11-async/02-promise-basics/promise-reject-1@2x.png new file mode 100644 index 000000000..9eff3793c Binary files /dev/null and b/1-js/11-async/02-promise-basics/promise-reject-1@2x.png differ diff --git a/1-js/11-async/02-promise-basics/promise-resolve-1.png b/1-js/11-async/02-promise-basics/promise-resolve-1.png new file mode 100644 index 000000000..b4bb51826 Binary files /dev/null and b/1-js/11-async/02-promise-basics/promise-resolve-1.png differ diff --git a/1-js/11-async/02-promise-basics/promise-resolve-1@2x.png b/1-js/11-async/02-promise-basics/promise-resolve-1@2x.png new file mode 100644 index 000000000..ecb4af35d Binary files /dev/null and b/1-js/11-async/02-promise-basics/promise-resolve-1@2x.png differ diff --git a/1-js/11-async/02-promise-basics/promise-resolve-reject.png b/1-js/11-async/02-promise-basics/promise-resolve-reject.png new file mode 100644 index 000000000..6f0294f01 Binary files /dev/null and b/1-js/11-async/02-promise-basics/promise-resolve-reject.png differ diff --git a/1-js/11-async/02-promise-basics/promise-resolve-reject@2x.png b/1-js/11-async/02-promise-basics/promise-resolve-reject@2x.png new file mode 100644 index 000000000..b59301ff7 Binary files /dev/null and b/1-js/11-async/02-promise-basics/promise-resolve-reject@2x.png differ diff --git a/6-async/03-promise-chaining/01-then-vs-catch/solution.md b/1-js/11-async/03-promise-chaining/01-then-vs-catch/solution.md similarity index 100% rename from 6-async/03-promise-chaining/01-then-vs-catch/solution.md rename to 1-js/11-async/03-promise-chaining/01-then-vs-catch/solution.md diff --git a/6-async/03-promise-chaining/01-then-vs-catch/task.md b/1-js/11-async/03-promise-chaining/01-then-vs-catch/task.md similarity index 96% rename from 6-async/03-promise-chaining/01-then-vs-catch/task.md rename to 1-js/11-async/03-promise-chaining/01-then-vs-catch/task.md index 51c08d7c9..cefca60aa 100644 --- a/6-async/03-promise-chaining/01-then-vs-catch/task.md +++ b/1-js/11-async/03-promise-chaining/01-then-vs-catch/task.md @@ -3,10 +3,11 @@ Are these code fragments equal? In other words, do they behave the same way in any circumstances, for any handler functions? ```js -promise.then(f1, f2); +promise.then(f1).catch(f2); ``` -Versus; +Versus: + ```js -promise.then(f1).catch(f2); +promise.then(f1, f2); ``` diff --git a/1-js/11-async/03-promise-chaining/article.md b/1-js/11-async/03-promise-chaining/article.md new file mode 100644 index 000000000..b2ba231ec --- /dev/null +++ b/1-js/11-async/03-promise-chaining/article.md @@ -0,0 +1,387 @@ + +# Promises chaining + +Let's return to the problem mentioned in the chapter : we have a sequence of asynchronous tasks to be done one after another. For instance, loading scripts. How can we code it well? + +Promises provide a couple of recipes to do that. + +In this chapter we cover promise chaining. + +It looks like this: + +```js run +new Promise(function(resolve, reject) { + + setTimeout(() => resolve(1), 1000); // (*) + +}).then(function(result) { // (**) + + alert(result); // 1 + return result * 2; + +}).then(function(result) { // (***) + + alert(result); // 2 + return result * 2; + +}).then(function(result) { + + alert(result); // 4 + return result * 2; + +}); +``` + +The idea is that the result is passed through the chain of `.then` handlers. + +Here the flow is: +1. The initial promise resolves in 1 second `(*)`, +2. Then the `.then` handler is called `(**)`. +3. The value that it returns is passed to the next `.then` handler `(***)` +4. ...and so on. + +As the result is passed along the chain of handlers, we can see a sequence of `alert` calls: `1` -> `2` -> `4`. + +![](promise-then-chain.png) + +The whole thing works, because a call to `promise.then` returns a promise, so that we can call the next `.then` on it. + +When a handler returns a value, it becomes the result of that promise, so the next `.then` is called with it. + +To make these words more clear, here's the start of the chain: + +```js run +new Promise(function(resolve, reject) { + + setTimeout(() => resolve(1), 1000); + +}).then(function(result) { + + alert(result); + return result * 2; // <-- (1) + +}) // <-- (2) +// .then… +``` + +The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value. + +**A classic newbie error: technically we can also add many `.then` to a single promise. This is not chaining.** + +For example: +```js run +let promise = new Promise(function(resolve, reject) { + setTimeout(() => resolve(1), 1000); +}); + +promise.then(function(result) { + alert(result); // 1 + return result * 2; +}); + +promise.then(function(result) { + alert(result); // 1 + return result * 2; +}); + +promise.then(function(result) { + alert(result); // 1 + return result * 2; +}); +``` + +What we did here is just several handlers to one promise. They don't pass the result to each other, instead they process it independently. + +Here's the picture (compare it with the chaining above): + +![](promise-then-many.png) + +All `.then` on the same promise get the same result -- the result of that promise. So in the code above all `alert` show the same: `1`. + +In practice we rarely need multiple handlers for one promise. Chaining is used much more often. + +## Returning promises + +Normally, a value returned by a `.then` handler is immediately passed to the next handler. But there's an exception. + +If the returned value is a promise, then the further execution is suspended until it settles. After that, the result of that promise is given to the next `.then` handler. + +For instance: + +```js run +new Promise(function(resolve, reject) { + + setTimeout(() => resolve(1), 1000); + +}).then(function(result) { + + alert(result); // 1 + +*!* + return new Promise((resolve, reject) => { // (*) + setTimeout(() => resolve(result * 2), 1000); + }); +*/!* + +}).then(function(result) { // (**) + + alert(result); // 2 + + return new Promise((resolve, reject) => { + setTimeout(() => resolve(result * 2), 1000); + }); + +}).then(function(result) { + + alert(result); // 4 + +}); +``` + +Here the first `.then` shows `1` returns `new Promise(…)` in the line `(*)`. After one second it resolves, and the result (the argument of `resolve`, here it's `result*2`) is passed on to handler of the second `.then` in the line `(**)`. It shows `2` and does the same thing. + +So the output is again 1 -> 2 -> 4, but now with 1 second delay between `alert` calls. + +Returning promises allows us to build chains of asynchronous actions. + +## Example: loadScript + +Let's use this feature with `loadScript` to load scripts one by one, in sequence: + +```js run +loadScript("/article/promise-chaining/one.js") + .then(function(script) { + return loadScript("/article/promise-chaining/two.js"); + }) + .then(function(script) { + return loadScript("/article/promise-chaining/three.js"); + }) + .then(function(script) { + // use functions declared in scripts + // to show that they indeed loaded + one(); + two(); + three(); + }); +``` + +This code can be made bit shorter with arrow functions: + +```js run +loadScript("/article/promise-chaining/one.js") + .then(script => loadScript("/article/promise-chaining/two.js")) + .then(script => loadScript("/article/promise-chaining/three.js")) + .then(script => { + // scripts are loaded, we can use functions declared there + one(); + two(); + three(); + }); +``` + + +Here each `loadScript` call returns a promise, and the next `.then` runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another. + +We can add more asynchronous actions to the chain. Please note that code is still "flat", it grows down, not to the right. There are no signs of "pyramid of doom". + +Please note that technically we can add `.then` directly to each `loadScript`, like this: + +```js run +loadScript("/article/promise-chaining/one.js").then(script1 => { + loadScript("/article/promise-chaining/two.js").then(script2 => { + loadScript("/article/promise-chaining/three.js").then(script3 => { + // this function has access to variables script1, script2 and script3 + one(); + two(); + three(); + }); + }); +}); +``` + +This code does the same: loads 3 scripts in sequence. But it "grows to the right". So we have the same problem as with callbacks. + +People who start to use promises sometimes don't know about chaining, so they write it this way. Generally, chaining is preferred. + +Sometimes it's ok to write `.then` directly, because the nested function has access to the outer scope. In the example above the most nested callback has access to all variables `script1`, `script2`, `script3`. But that's an exception rather than a rule. + + +````smart header="Thenables" +To be precise, `.then` may return an arbitrary "thenable" object, and it will be treated the same way as a promise. + +A "thenable" object is any object with a method `.then`. + +The idea is that 3rd-party libraries may implement "promise-compatible" objects of their own. They can have extended set of methods, but also be compatible with native promises, because they implement `.then`. + +Here's an example of a thenable object: + +```js run +class Thenable { + constructor(num) { + this.num = num; + } + then(resolve, reject) { + alert(resolve); // function() { native code } + // resolve with this.num*2 after the 1 second + setTimeout(() => resolve(this.num * 2), 1000); // (**) + } +} + +new Promise(resolve => resolve(1)) + .then(result => { + return new Thenable(result); // (*) + }) + .then(alert); // shows 2 after 1000ms +``` + +JavaScript checks the object returned by `.then` handler in the line `(*)`: if it has a callable method named `then`, then it calls that method providing native functions `resolve`, `reject` as arguments (similar to executor) and waits until one of them is called. In the example above `resolve(2)` is called after 1 second `(**)`. Then the result is passed further down the chain. + +This feature allows to integrate custom objects with promise chains without having to inherit from `Promise`. +```` + + +## Bigger example: fetch + +In frontend programming promises are often used for network requests. So let's see an extended example of that. + +We'll use the [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) method to load the information about the user from the remote server. The method is quite complex, it has many optional parameters, but the basic usage is quite simple: + +```js +let promise = fetch(url); +``` + +This makes a network request to the `url` and returns a promise. The promise resolves with a `response` object when the remote server responds with headers, but *before the full response is downloaded*. + +To read the full response, we should call a method `response.text()`: it returns a promise that resolves when the full text downloaded from the remote server, with that text as a result. + +The code below makes a request to `user.json` and loads its text from the server: + +```js run +fetch('/article/promise-chaining/user.json') + // .then below runs when the remote server responds + .then(function(response) { + // response.text() returns a new promise that resolves with the full response text + // when we finish downloading it + return response.text(); + }) + .then(function(text) { + // ...and here's the content of the remote file + alert(text); // {"name": "iliakan", isAdmin: true} + }); +``` + +There is also a method `response.json()` that reads the remote data and parses it as JSON. In our case that's even more convenient, so let's switch to it. + +We'll also use arrow functions for brevity: + +```js run +// same as above, but response.json() parses the remote content as JSON +fetch('/article/promise-chaining/user.json') + .then(response => response.json()) + .then(user => alert(user.name)); // iliakan +``` + +Now let's do something with the loaded user. + +For instance, we can make one more request to GitHub, load the user profile and show the avatar: + +```js run +// Make a request for user.json +fetch('/article/promise-chaining/user.json') + // Load it as json + .then(response => response.json()) + // Make a request to GitHub + .then(user => fetch(`https://api.github.com/users/${user.name}`)) + // Load the response as json + .then(response => response.json()) + // Show the avatar image (githubUser.avatar_url) for 3 seconds (maybe animate it) + .then(githubUser => { + let img = document.createElement('img'); + img.src = githubUser.avatar_url; + img.className = "promise-avatar-example"; + document.body.append(img); + + setTimeout(() => img.remove(), 3000); // (*) + }); +``` + +The code works, see comments about the details, but it should be quite self-descriptive. Although, there's a potential problem in it, a typical error of those who begin to use promises. + +Look at the line `(*)`: how can we do something *after* the avatar has finished showing and gets removed? For instance, we'd like to show a form for editing that user or something else. As of now, there's no way. + +To make the chain extendable, we need to return a promise that resolves when the avatar finishes showing. + +Like this: + +```js run +fetch('/article/promise-chaining/user.json') + .then(response => response.json()) + .then(user => fetch(`https://api.github.com/users/${user.name}`)) + .then(response => response.json()) +*!* + .then(githubUser => new Promise(function(resolve, reject) { +*/!* + let img = document.createElement('img'); + img.src = githubUser.avatar_url; + img.className = "promise-avatar-example"; + document.body.append(img); + + setTimeout(() => { + img.remove(); +*!* + resolve(githubUser); +*/!* + }, 3000); + })) + // triggers after 3 seconds + .then(githubUser => alert(`Finished showing ${githubUser.name}`)); +``` + +Now right after `setTimeout` runs `img.remove()`, it calls `resolve(githubUser)`, thus passing the control to the next `.then` in the chain and passing forward the user data. + +As a rule, an asynchronous action should always return a promise. + +That makes it possible to plan actions after it. Even if we don't plan to extend the chain now, we may need it later. + +Finally, we can split the code into reusable functions: + +```js run +function loadJson(url) { + return fetch(url) + .then(response => response.json()); +} + +function loadGithubUser(name) { + return fetch(`https://api.github.com/users/${name}`) + .then(response => response.json()); +} + +function showAvatar(githubUser) { + return new Promise(function(resolve, reject) { + let img = document.createElement('img'); + img.src = githubUser.avatar_url; + img.className = "promise-avatar-example"; + document.body.append(img); + + setTimeout(() => { + img.remove(); + resolve(githubUser); + }, 3000); + }); +} + +// Use them: +loadJson('/article/promise-chaining/user.json') + .then(user => loadGithubUser(user.name)) + .then(showAvatar) + .then(githubUser => alert(`Finished showing ${githubUser.name}`)); + // ... +``` + +## Summary + +If a `.then` (or `catch/finally`, doesn't matter) handler returns a promise, the rest of the chain waits until it settles. When it does, its result (or error) is passed further. + +Here's a full picture: + +![](promise-handler-variants.png) diff --git a/6-async/03-promise-chaining/getMessage.js b/1-js/11-async/03-promise-chaining/getMessage.js similarity index 100% rename from 6-async/03-promise-chaining/getMessage.js rename to 1-js/11-async/03-promise-chaining/getMessage.js diff --git a/1-js/11-async/03-promise-chaining/head.html b/1-js/11-async/03-promise-chaining/head.html new file mode 100644 index 000000000..0a0075fb9 --- /dev/null +++ b/1-js/11-async/03-promise-chaining/head.html @@ -0,0 +1,22 @@ + + + diff --git a/6-async/03-promise-chaining/one.js b/1-js/11-async/03-promise-chaining/one.js similarity index 100% rename from 6-async/03-promise-chaining/one.js rename to 1-js/11-async/03-promise-chaining/one.js diff --git a/1-js/11-async/03-promise-chaining/promise-handler-variants-2.png b/1-js/11-async/03-promise-chaining/promise-handler-variants-2.png new file mode 100644 index 000000000..712f57b82 Binary files /dev/null and b/1-js/11-async/03-promise-chaining/promise-handler-variants-2.png differ diff --git a/1-js/11-async/03-promise-chaining/promise-handler-variants-2@2x.png b/1-js/11-async/03-promise-chaining/promise-handler-variants-2@2x.png new file mode 100644 index 000000000..5b57be809 Binary files /dev/null and b/1-js/11-async/03-promise-chaining/promise-handler-variants-2@2x.png differ diff --git a/1-js/11-async/03-promise-chaining/promise-handler-variants.png b/1-js/11-async/03-promise-chaining/promise-handler-variants.png new file mode 100644 index 000000000..2643fde89 Binary files /dev/null and b/1-js/11-async/03-promise-chaining/promise-handler-variants.png differ diff --git a/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png b/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png new file mode 100644 index 000000000..313e241cc Binary files /dev/null and b/1-js/11-async/03-promise-chaining/promise-handler-variants@2x.png differ diff --git a/1-js/11-async/03-promise-chaining/promise-then-chain.png b/1-js/11-async/03-promise-chaining/promise-then-chain.png new file mode 100644 index 000000000..fab8a92ad Binary files /dev/null and b/1-js/11-async/03-promise-chaining/promise-then-chain.png differ diff --git a/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png b/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png new file mode 100644 index 000000000..421b59359 Binary files /dev/null and b/1-js/11-async/03-promise-chaining/promise-then-chain@2x.png differ diff --git a/1-js/11-async/03-promise-chaining/promise-then-many.png b/1-js/11-async/03-promise-chaining/promise-then-many.png new file mode 100644 index 000000000..b13d20a9e Binary files /dev/null and b/1-js/11-async/03-promise-chaining/promise-then-many.png differ diff --git a/1-js/11-async/03-promise-chaining/promise-then-many@2x.png b/1-js/11-async/03-promise-chaining/promise-then-many@2x.png new file mode 100644 index 000000000..f0609c946 Binary files /dev/null and b/1-js/11-async/03-promise-chaining/promise-then-many@2x.png differ diff --git a/6-async/03-promise-chaining/three.js b/1-js/11-async/03-promise-chaining/three.js similarity index 100% rename from 6-async/03-promise-chaining/three.js rename to 1-js/11-async/03-promise-chaining/three.js diff --git a/6-async/03-promise-chaining/two.js b/1-js/11-async/03-promise-chaining/two.js similarity index 100% rename from 6-async/03-promise-chaining/two.js rename to 1-js/11-async/03-promise-chaining/two.js diff --git a/6-async/03-promise-chaining/user.json b/1-js/11-async/03-promise-chaining/user.json similarity index 100% rename from 6-async/03-promise-chaining/user.json rename to 1-js/11-async/03-promise-chaining/user.json diff --git a/6-async/03-promise-chaining/02-error-async/solution.md b/1-js/11-async/04-promise-error-handling/01-error-async/solution.md similarity index 100% rename from 6-async/03-promise-chaining/02-error-async/solution.md rename to 1-js/11-async/04-promise-error-handling/01-error-async/solution.md diff --git a/6-async/03-promise-chaining/02-error-async/task.md b/1-js/11-async/04-promise-error-handling/01-error-async/task.md similarity index 100% rename from 6-async/03-promise-chaining/02-error-async/task.md rename to 1-js/11-async/04-promise-error-handling/01-error-async/task.md diff --git a/1-js/11-async/04-promise-error-handling/article.md b/1-js/11-async/04-promise-error-handling/article.md new file mode 100644 index 000000000..3424d099e --- /dev/null +++ b/1-js/11-async/04-promise-error-handling/article.md @@ -0,0 +1,345 @@ + +# Error handling with promises + +Asynchronous actions may sometimes fail: in case of an error the corresponding promise becomes rejected. For instance, `fetch` fails if the remote server is not available. We can use `.catch` to handle errors (rejections). + +Promise chaining is great at that aspect. When a promise rejects, the control jumps to the closest rejection handler down the chain. That's very convenient in practice. + +For instance, in the code below the URL is wrong (no such site) and `.catch` handles the error: + +```js run +*!* +fetch('https://no-such-server.blabla') // rejects +*/!* + .then(response => response.json()) + .catch(err => alert(err)) // TypeError: failed to fetch (the text may vary) +``` + +Or, maybe, everything is all right with the site, but the response is not valid JSON: + +```js run +fetch('/') // fetch works fine now, the server responds with the HTML page +*!* + .then(response => response.json()) // rejects: the page is HTML, not a valid json +*/!* + .catch(err => alert(err)) // SyntaxError: Unexpected token < in JSON at position 0 +``` + +The easiest way to catch all errors is to append `.catch` to the end of chain: + +```js run +fetch('/article/promise-chaining/user.json') + .then(response => response.json()) + .then(user => fetch(`https://api.github.com/users/${user.name}`)) + .then(response => response.json()) + .then(githubUser => new Promise((resolve, reject) => { + let img = document.createElement('img'); + img.src = githubUser.avatar_url; + img.className = "promise-avatar-example"; + document.body.append(img); + + setTimeout(() => { + img.remove(); + resolve(githubUser); + }, 3000); + })) +*!* + .catch(error => alert(error.message)); +*/!* +``` + +Normally, `.catch` doesn't trigger at all, because there are no errors. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it. + +## Implicit try..catch + +The code of a promise executor and promise handlers has an "invisible `try..catch`" around it. If an exception happens, it gets caught and treated as a rejection. + +For instance, this code: + +```js run +new Promise((resolve, reject) => { +*!* + throw new Error("Whoops!"); +*/!* +}).catch(alert); // Error: Whoops! +``` + +...Works exactly the same as this: + +```js run +new Promise((resolve, reject) => { +*!* + reject(new Error("Whoops!")); +*/!* +}).catch(alert); // Error: Whoops! +``` + +The "invisible `try..catch`" around the executor automatically catches the error and treats it as a rejection. + +This happens not only in the executor, but in its handlers as well. If we `throw` inside a `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler. + +Here's an example: + +```js run +new Promise((resolve, reject) => { + resolve("ok"); +}).then((result) => { +*!* + throw new Error("Whoops!"); // rejects the promise +*/!* +}).catch(alert); // Error: Whoops! +``` + +This happens for all errors, not just those caused by the `throw` statement. For example, a programming error: + +```js run +new Promise((resolve, reject) => { + resolve("ok"); +}).then((result) => { +*!* + blabla(); // no such function +*/!* +}).catch(alert); // ReferenceError: blabla is not defined +``` + +The final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above. + +## Rethrowing + +As we already noticed, `.catch` behaves like `try..catch`. We may have as many `.then` handlers as we want, and then use a single `.catch` at the end to handle errors in all of them. + +In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises. + +If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler. + +In the example below the `.catch` successfully handles the error: + +```js run +// the execution: catch -> then +new Promise((resolve, reject) => { + + throw new Error("Whoops!"); + +}).catch(function(error) { + + alert("The error is handled, continue normally"); + +}).then(() => alert("Next successful handler runs")); +``` + +Here the `.catch` block finishes normally. So the next successful `.then` handler is called. + +In the example below we see the other situation with `.catch`. The handler `(*)` catches the error and just can't handle it (e.g. it only knows how to handle `URIError`), so it throws it again: + +```js run +// the execution: catch -> catch -> then +new Promise((resolve, reject) => { + + throw new Error("Whoops!"); + +}).catch(function(error) { // (*) + + if (error instanceof URIError) { + // handle it + } else { + alert("Can't handle such error"); + +*!* + throw error; // throwing this or another error jumps to the next catch +*/!* + } + +}).then(function() { + /* never runs here */ +}).catch(error => { // (**) + + alert(`The unknown error has occurred: ${error}`); + // don't return anything => execution goes the normal way + +}); +``` + +Then the execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain. + +In the section below we'll see a practical example of rethrowing. + +## Fetch error handling example + +Let's improve error handling for the user-loading example. + +The promise returned by [fetch](mdn:api/WindowOrWorkerGlobalScope/fetch) rejects when it's impossible to make a request. For instance, a remote server is not available, or the URL is malformed. But if the remote server responds with error 404, or even error 500, then it's considered a valid response. + +What if the server returns a non-JSON page with error 500 in the line `(*)`? What if there's no such user, and GitHub returns a page with error 404 at `(**)`? + +```js run +fetch('no-such-user.json') // (*) + .then(response => response.json()) + .then(user => fetch(`https://api.github.com/users/${user.name}`)) // (**) + .then(response => response.json()) + .catch(alert); // SyntaxError: Unexpected token < in JSON at position 0 + // ... +``` + + +As of now, the code tries to load the response as JSON no matter what and dies with a syntax error. You can see that by running the example above, as the file `no-such-user.json` doesn't exist. + +That's not good, because the error just falls through the chain, without details: what failed and where. + +So let's add one more step: we should check the `response.status` property that has HTTP status, and if it's not 200, then throw an error. + +```js run +class HttpError extends Error { // (1) + constructor(response) { + super(`${response.status} for ${response.url}`); + this.name = 'HttpError'; + this.response = response; + } +} + +function loadJson(url) { // (2) + return fetch(url) + .then(response => { + if (response.status == 200) { + return response.json(); + } else { + throw new HttpError(response); + } + }) +} + +loadJson('no-such-user.json') // (3) + .catch(alert); // HttpError: 404 for .../no-such-user.json +``` + +1. We make a custom class for HTTP Errors to distinguish them from other types of errors. Besides, the new class has a constructor that accepts `response` object and saves it in the error. So error-handling code will be able to access the response. +2. Then we put together the requesting and error-handling code into a function that fetches the `url` *and* treats any non-200 status as an error. That's convenient, because we often need such logic. +3. Now `alert` shows a more helpful descriptive message. + +The great thing about having our own class for errors is that we can easily check for it in error-handling code using `instanceof`. + +For instance, we can make a request, and then if we get 404 -- ask the user to modify the information. + +The code below loads a user with the given name from GitHub. If there's no such user, then it asks for the correct name: + +```js run +function demoGithubUser() { + let name = prompt("Enter a name?", "iliakan"); + + return loadJson(`https://api.github.com/users/${name}`) + .then(user => { + alert(`Full name: ${user.name}.`); + return user; + }) + .catch(err => { +*!* + if (err instanceof HttpError && err.response.status == 404) { +*/!* + alert("No such user, please reenter."); + return demoGithubUser(); + } else { + throw err; // (*) + } + }); +} + +demoGithubUser(); +``` + +Please note: `.catch` here catches all errors, but it "knows how to handle" only `HttpError 404`. In that particular case it means that there's no such user, and `.catch` just retries in that case. + +For other errors, it has no idea what could go wrong. Maybe a programming error or something. So it just rethrows it in the line `(*)`. + +## Unhandled rejections + +What happens when an error is not handled? For instance, after the rethrow `(*)` in the example above. + +Or we could just forget to append an error handler to the end of the chain, like here: + +```js untrusted run refresh +new Promise(function() { + noSuchFunction(); // Error here (no such function) +}) + .then(() => { + // successful promise handlers, one or more + }); // without .catch at the end! +``` + +In case of an error, the promise state becomes "rejected", and the execution should jump to the closest rejection handler. But there is no such handler in the examples above. So the error gets "stuck". There's no code to handle it. + +In practice, just like with a regular unhandled errors, it means that something has terribly gone wrong. + +What happens when a regular error occurs and is not caught by `try..catch`? The script dies. Similar thing happens with unhandled promise rejections. + +The JavaScript engine tracks such rejections and generates a global error in that case. You can see it in the console if you run the example above. + +In the browser we can catch such errors using the event `unhandledrejection`: + +```js run +*!* +window.addEventListener('unhandledrejection', function(event) { + // the event object has two special properties: + alert(event.promise); // [object Promise] - the promise that generated the error + alert(event.reason); // Error: Whoops! - the unhandled error object +}); +*/!* + +new Promise(function() { + throw new Error("Whoops!"); +}); // no catch to handle the error +``` + +The event is the part of the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections). + +If an error occurs, and there's no `.catch`, the `unhandledrejection` handler triggers, and gets the `event` object with the information about the error, so we can do something. + +Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report the incident to the server. + +In non-browser environments like Node.js there are other similar ways to track unhandled errors. + + +## Summary + +- `.catch` handles promise rejections of all kinds: be it a `reject()` call, or an error thrown in a handler. +- We should place `.catch` exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones. +- It's ok not to use `.catch` at all, if there's no way to recover from an error. +- In any case we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments), to track unhandled errors and inform the user (and probably our server) about the them, so that our app never "just dies". + +And finally, if we have load-indication, then `.finally` is a great handler to stop it when the fetch is complete: + +```js run +function demoGithubUser() { + let name = prompt("Enter a name?", "iliakan"); + +*!* + document.body.style.opacity = 0.3; // (1) start the indication +*/!* + + return loadJson(`https://api.github.com/users/${name}`) +*!* + .finally(() => { // (2) stop the indication + document.body.style.opacity = ''; + return new Promise(resolve => setTimeout(resolve)); // (*) + }) +*/!* + .then(user => { + alert(`Full name: ${user.name}.`); + return user; + }) + .catch(err => { + if (err instanceof HttpError && err.response.status == 404) { + alert("No such user, please reenter."); + return demoGithubUser(); + } else { + throw err; + } + }); +} + +demoGithubUser(); +``` + +Here on the line `(1)` we indicate loading by dimming the document. The method doesn't matter, could use any type of indication instead. + +When the promise is settled, be it a successful fetch or an error, `finally` triggers at the line `(2)` and stops the indication. + +There's a little browser trick `(*)` with returning a zero-timeout promise from `finally`. That's because some browsers (like Chrome) need "a bit time" outside promise handlers to paint document changes. So it ensures that the indication is visually stopped before going further on the chain. diff --git a/1-js/11-async/04-promise-error-handling/getMessage.js b/1-js/11-async/04-promise-error-handling/getMessage.js new file mode 100644 index 000000000..6c5893433 --- /dev/null +++ b/1-js/11-async/04-promise-error-handling/getMessage.js @@ -0,0 +1,3 @@ +function getMessage() { + return "Hello, world!"; +} diff --git a/6-async/03-promise-chaining/head.html b/1-js/11-async/04-promise-error-handling/head.html similarity index 62% rename from 6-async/03-promise-chaining/head.html rename to 1-js/11-async/04-promise-error-handling/head.html index 31c6b4271..a0b741962 100644 --- a/6-async/03-promise-chaining/head.html +++ b/1-js/11-async/04-promise-error-handling/head.html @@ -1,16 +1,4 @@ diff --git a/1-js/12-generators-iterators/index.md b/1-js/12-generators-iterators/index.md new file mode 100644 index 000000000..ccc909d1a --- /dev/null +++ b/1-js/12-generators-iterators/index.md @@ -0,0 +1,2 @@ + +# Generators, advanced iteration diff --git a/1-js/13-modules/01-modules-intro/article.md b/1-js/13-modules/01-modules-intro/article.md new file mode 100644 index 000000000..6bf66abbf --- /dev/null +++ b/1-js/13-modules/01-modules-intro/article.md @@ -0,0 +1,379 @@ + +# Modules, introduction + +As our application grows bigger, we want to split it into multiple files, so called 'modules'. +A module usually contains a class or a library of useful functions. + +For a long time, JavaScript existed without a language-level module syntax. That wasn't a problem, because initially scripts were small and simple. So there was no need. + +But eventually scripts became more and more complex, so the community invented a variety of ways to organize code into modules, special libraries to load modules on demand. + +For instance: + +- [AMD](https://en.wikipedia.org/wiki/Asynchronous_module_definition) -- one of the most ancient module systems, initially implemented by the library [require.js](http://requirejs.org/). +- [CommonJS](http://wiki.commonjs.org/wiki/Modules/1.1) -- the module system created for Node.js server. +- [UMD](https://github.com/umdjs/umd) -- one more module system, suggested as a universal one, compatible with AMD and CommonJS. + +Now all these slowly become a part of history, but we still can find them in old scripts. The language-level module system appeared in the standard in 2015, gradually evolved since then, and is now supported by all major browsers and in Node.js. + +## What is a module? + +A module is just a file, a single script, as simple as that. + +There are directives `export` and `import` to interchange functionality between modules, call functions of one module from another one: + +- `export` keyword labels variables and functions that should be accessible from outside the current module. +- `import` allows to import functionality from other modules. + +For instance, if we have a file `sayHi.js` exporting a function: + +```js +// 📁 sayHi.js +export function sayHi(user) { + alert(`Hello, ${user}!`); +} +``` + +...Then another file may import and use it: + +```js +// 📁 main.js +import {sayHi} from './sayHi.js'; + +alert(sayHi); // function... +sayHi('John'); // Hello, John! +``` + +In this tutorial we concentrate on the language itself, but we use browser as the demo environment, so let's see how to use modules in the browser. + +As modules support special keywords and features, we must tell the browser that a script should be treated as module, by using the attribute ` +``` + +Here we can see it in the browser, but the same is true for any module. + +### Module-level scope + +Each module has its own top-level scope. In other words, top-level variables and functions from a module are not seen in other scripts. + +In the example below, two scripts are imported, and `hello.js` tries to use `user` variable declared in `user.js`, and fails: + +[codetabs src="scopes" height="140" current="index.html"] + +Modules are expected to `export` what they want to be accessible from outside and `import` what they need. + +So we should import `user.js` directly into `hello.js` instead of `index.html`. + +That's the correct variant: + +[codetabs src="scopes-working" height="140" current="hello.js"] + +In the browser, independent top-level scope also exists for each ` + + +``` + +If we really need to make a window-level global variable, we can explicitly assign it to `window` and access as `window.user`. But that's an exception requiring a good reason. + +### A module code is evaluated only the first time when imported + +If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers. + +That has important consequences. Let's see that on examples. + +First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time: + +```js +// 📁 alert.js +alert("Module is evaluated!"); +``` + +```js +// Import the same module from different files + +// 📁 1.js +import `./alert.js`; // Module is evaluated! + +// 📁 2.js +import `./alert.js`; // (nothing) +``` + +In practice, top-level module code is mostly used for initialization. We create data structures, pre-fill them, and if we want something to be reusable -- export it. + +Now, a more advanced example. + +Let's say, a module exports an object: + +```js +// 📁 admin.js +export let admin = { + name: "John" +}; +``` + +If this module is imported from multiple files, the module is only evaluated the first time, `admin` object is created, and then passed to all further importers. + +All importers get exactly the one and only `admin` object: + +```js +// 📁 1.js +import {admin} from './admin.js'; +admin.name = "Pete"; + +// 📁 2.js +import {admin} from './admin.js'; +alert(admin.name); // Pete + +*!* +// Both 1.js and 2.js imported the same object +// Changes made in 1.js are visible in 2.js +*/!* +``` + +So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that . + +Such behavior is great for modules that require configuration. We can set required properties on the first import, and then in further imports it's ready. + +For instance, `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside: + +```js +// 📁 admin.js +export let admin = { }; + +export function sayHi() { + alert(`Ready to serve, ${admin.name}!`); +} +``` + +Now, in `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself: + +```js +// 📁 init.js +import {admin} from './admin.js'; +admin.name = "Pete"; +``` + +```js +// 📁 other.js +import {admin, sayHi} from './admin.js'; + +alert(admin.name); // *!*Pete*/!* + +sayHi(); // Ready to serve, *!*Pete*/!*! +``` + +### import.meta + +The object `import.meta` contains the information about the current module. + +Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML: + +```html run height=0 + +``` + +### Top-level "this" is undefined + +That's kind of a minor feature, but for completeness we should mention it. + +In a module, top-level `this` is undefined, as opposed to a global object in non-module scripts: + +```html run height=0 + + + +``` + +## Browser-specific features + +There are also several browser-specific differences of scripts with `type="module"` compared to regular ones. + +You may want skip those for now if you're reading for the first time, or if you don't use JavaScript in a browser. + +### Module scripts are deferred + +Module scripts are *always* deferred, same effect as `defer` attribute (described in the chapter [](info:script-async-defer)), for both external and inline scripts. + +In other words: +- external module scripts ` + +Compare to regular script below: + + + + +``` + +Please note: the second script actually works before the first! So we'll see `undefined` first, and then `object`. + +That's because modules are deferred, so way wait for the document to be processed. The regular scripts runs immediately, so we saw its output first. + +When using modules, we should be aware that HTML-page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put transparent overlays or "loading indicators", or otherwise ensure that the visitor won't be confused by that. + +### Async works on inline scripts + +Async attribute ` +``` + +### External scripts + +There are two notable differences of external module scripts: + +1. External scripts with same `src` run only once: + ```html + + + + ``` + +2. External scripts that are fetched from another domain require [CORS](mdn:Web/HTTP/CORS) headers. In other words, if a module script is fetched from another domain, the remote server must supply a header `Access-Control-Allow-Origin: *` (may use fetching domain instead of `*`) to indicate that the fetch is allowed. + ```html + + + + ``` + + That ensures better security by default. + +### No "bare" modules allowed + +In the browser, `import` must get either a relative or absolute URL. Modules without any path are called "bare" modules. Such modules are not allowed in `import`. + +For instance, this `import` is invalid: +```js +import {sayHi} from 'sayHi'; // Error, "bare" module +// the module must have a path, e.g. './sayHi.js' or wherever the module is +``` + +Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet. + +### Compatibility, "nomodule" + +Old browsers do not understand `type="module"`. Scripts of the unknown type are just ignored. For them, it's possible to provide a fallback using `nomodule` attribute: + +```html run + + + +``` + +If we use bundle tools, then as scripts are bundled together into a single file (or few files), `import/export` statements inside those scripts are replaced by special bundler functions. So the resulting "bundled" script does not contain any `import/export`, it doesn't require `type="module"`, and we can put it into a regular script: + +```html + + +``` + +## Build tools + +In real-life, browser modules are rarely used in their "raw" form. Usually, we bundle them together with a special tool such as [Webpack](https://webpack.js.org/) and deploy to the production server. + +One of the benefits of using bundlers -- they give more control over how modules are resolved, allowing bare modules and much more, like CSS/HTML modules. + +Build tools do the following: + +1. Take a "main" module, the one intended to be put in ` diff --git a/1-js/13-modules/01-modules-intro/say.view/say.js b/1-js/13-modules/01-modules-intro/say.view/say.js new file mode 100644 index 000000000..198a3be6d --- /dev/null +++ b/1-js/13-modules/01-modules-intro/say.view/say.js @@ -0,0 +1,3 @@ +export function sayHi(user) { + return `Hello, ${user}!`; +} diff --git a/1-js/13-modules/01-modules-intro/scopes-working.view/hello.js b/1-js/13-modules/01-modules-intro/scopes-working.view/hello.js new file mode 100644 index 000000000..6c087ea81 --- /dev/null +++ b/1-js/13-modules/01-modules-intro/scopes-working.view/hello.js @@ -0,0 +1,3 @@ +import {user} from './user.js'; + +document.body.innerHTML = user; // John diff --git a/1-js/13-modules/01-modules-intro/scopes-working.view/index.html b/1-js/13-modules/01-modules-intro/scopes-working.view/index.html new file mode 100644 index 000000000..b78f75912 --- /dev/null +++ b/1-js/13-modules/01-modules-intro/scopes-working.view/index.html @@ -0,0 +1,2 @@ + + diff --git a/1-js/13-modules/01-modules-intro/scopes-working.view/user.js b/1-js/13-modules/01-modules-intro/scopes-working.view/user.js new file mode 100644 index 000000000..d289329c6 --- /dev/null +++ b/1-js/13-modules/01-modules-intro/scopes-working.view/user.js @@ -0,0 +1 @@ +export let user = "John"; diff --git a/1-js/13-modules/01-modules-intro/scopes.view/hello.js b/1-js/13-modules/01-modules-intro/scopes.view/hello.js new file mode 100644 index 000000000..714aafa1f --- /dev/null +++ b/1-js/13-modules/01-modules-intro/scopes.view/hello.js @@ -0,0 +1 @@ +alert(user); // no such variable (each module has independent variables) diff --git a/1-js/13-modules/01-modules-intro/scopes.view/index.html b/1-js/13-modules/01-modules-intro/scopes.view/index.html new file mode 100644 index 000000000..a87e96fdf --- /dev/null +++ b/1-js/13-modules/01-modules-intro/scopes.view/index.html @@ -0,0 +1,3 @@ + + + diff --git a/1-js/13-modules/01-modules-intro/scopes.view/user.js b/1-js/13-modules/01-modules-intro/scopes.view/user.js new file mode 100644 index 000000000..12ec850d9 --- /dev/null +++ b/1-js/13-modules/01-modules-intro/scopes.view/user.js @@ -0,0 +1 @@ +let user = "John"; diff --git a/1-js/13-modules/02-import-export/article.md b/1-js/13-modules/02-import-export/article.md new file mode 100644 index 000000000..8aa122f07 --- /dev/null +++ b/1-js/13-modules/02-import-export/article.md @@ -0,0 +1,441 @@ +# Export and Import + +Export and import directives are very versatile. + +In the previous chapter we saw a simple use, now let's explore more examples. + +## Export before declarations + +We can label any declaration as exported by placing `export` before it, be it a variable, function or a class. + +For instance, here all exports are valid: + +```js +// export an array +*!*export*/!* let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +// export a constant +*!*export*/!* const MODULES_BECAME_STANDARD_YEAR = 2015; + +// export a class +*!*export*/!* class User { + constructor(name) { + this.name = name; + } +} +``` + +````smart header="No semicolons after export class/function" +Please note that `export` before a class or a function does not make it a [function expression](info:function-expressions-arrows). It's still a function declaration, albeit exported. + +Most JavaScript style guides recommend semicolons after statements, but not after function and class declarations. + +That's why there should be no semicolons at the end of `export class` and `export function`. + +```js +export function sayHi(user) { + alert(`Hello, ${user}!`); +} *!* // no ; at the end */!* +``` + +```` + +## Export apart from declarations + +Also, we can put `export` separately. + +Here we first declare, and then export: + +```js +// 📁 say.js +function sayHi(user) { + alert(`Hello, ${user}!`); +} + +function sayBye(user) { + alert(`Bye, ${user}!`); +} + +*!* +export {sayHi, sayBye}; // a list of exported variables +*/!* +``` + +...Or, technically we could put `export` above functions as well. + +## Import * + +Usually, we put a list of what to import into `import {...}`, like this: + +```js +// 📁 main.js +*!* +import {sayHi, sayBye} from './say.js'; +*/!* + +sayHi('John'); // Hello, John! +sayBye('John'); // Bye, John! +``` + +But if the list is long, we can import everything as an object using `import * as `, for instance: + +```js +// 📁 main.js +*!* +import * as say from './say.js'; +*/!* + +say.sayHi('John'); +say.sayBye('John'); +``` + +At first sight, "import everything" seems such a cool thing, short to write, why should we ever explicitly list what we need to import? + +Well, there are few reasons. + +1. Modern build tools ([webpack](http://webpack.github.io) and others) bundle modules together and optimize them to speedup loading and remove unused stuff. + + Let's say, we added a 3rd-party library `lib.js` to our project with many functions: + ```js + // 📁 lib.js + export function sayHi() { ... } + export function sayBye() { ... } + export function becomeSilent() { ... } + ``` + + Now if we only use one of `lib.js` functions in our project: + ```js + // 📁 main.js + import {sayHi} from './lib.js'; + ``` + ...Then the optimizer will automatically detect it and totally remove the other functions from the bundled code, thus making the build smaller. That is called "tree-shaking". + +2. Explicitly listing what to import gives shorter names: `sayHi()` instead of `lib.sayHi()`. +3. Explicit imports give better overview of the code structure: what is used and where. It makes code support and refactoring easier. + +## Import "as" + +We can also use `as` to import under different names. + +For instance, let's import `sayHi` into the local variable `hi` for brevity, and same for `sayBye`: + +```js +// 📁 main.js +*!* +import {sayHi as hi, sayBye as bye} from './say.js'; +*/!* + +hi('John'); // Hello, John! +bye('John'); // Bye, John! +``` + +## Export "as" + +The similar syntax exists for `export`. + +Let's export functions as `hi` and `bye`: + +```js +// 📁 say.js +... +export {sayHi as hi, sayBye as bye}; +``` + +Now `hi` and `bye` are official names for outsiders: + +```js +// 📁 main.js +import * as say from './say.js'; + +say.hi('John'); // Hello, John! +say.bye('John'); // Bye, John! +``` + +## export default + +So far, we've seen how to import/export multiple things, optionally "as" other names. + +In practice, modules contain either: +- A library, pack of functions, like `lib.js`. +- Or an entity, like `class User` is described in `user.js`, the whole module has only this class. + +Mostly, the second approach is preferred, so that every "thing" resides in its own module. + +Naturally, that requires a lot of files, as everything wants its own module, but that's not a problem at all. Actually, code navigation becomes easier, if files are well-named and structured into folders. + +Modules provide special `export default` syntax to make "one thing per module" way look better. + +It requires following `export` and `import` statements: + +1. Put `export default` before the "main export" of the module. +2. Call `import` without curly braces. + +For instance, here `user.js` exports `class User`: + +```js +// 📁 user.js +export *!*default*/!* class User { // just add "default" + constructor(name) { + this.name = name; + } +} +``` + +...And `main.js` imports it: + +```js +// 📁 main.js +import *!*User*/!* from './user.js'; // not {User}, just User + +new User('John'); +``` + +Imports without curly braces look nicer. A common mistake when starting to use modules is to forget curly braces at all. So, remember, `import` needs curly braces for named imports and doesn't need them for the default one. + +| Named export | Default export | +|--------------|----------------| +| `export class User {...}` | `export default class User {...}` | +| `import {User} from ...` | `import User from ...`| + +Naturally, there may be only one "default" export per file. + +We may have both default and named exports in a single module, but in practice people usually don't mix them. A module has either named exports or the default one. + +**Another thing to note is that named exports must (naturally) have a name, while `export default` may be anonymous.** + +For instance, these are all perfectly valid default exports: + +```js +export default class { // no class name + constructor() { ... } +} + +export default function(user) { // no function name + alert(`Hello, ${user}!`); +} + +// export a single value, without making a variable +export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; +``` + +That's fine, because `export default` is only one per file. Contrary to that, omitting a name for named imports would be an error: + +```js +export class { // Error! (non-default export needs a name) + constructor() {} +} +``` + +### "Default" alias + +The "default" keyword is used as an "alias" for the default export, for standalone exports and other scenarios when we need to reference it. + +For example, if we already have a function declared, that's how to `export default` it (separately from the definition): + +```js +function sayHi(user) { + alert(`Hello, ${user}!`); +} + +export {sayHi as default}; // same as if we added "export default" before the function +``` + +Or, let's say a module `user.js` exports one main "default" thing and a few named ones (rarely the case, but happens): + +```js +// 📁 user.js +export default class User { + constructor(name) { + this.name = name; + } +} + +export function sayHi(user) { + alert(`Hello, ${user}!`); +} +``` + +Here's how to import the default export along with a named one: + +```js +// 📁 main.js +import {*!*default as User*/!*, sayHi} from './user.js'; + +new User('John'); +``` + +Or, if we consider importing `*` as an object, then the `default` property is exactly the default export: + +```js +// 📁 main.js +import * as user from './user.js'; + +let User = user.default; +new User('John'); +``` + + +### Should I use default exports? + +One should be careful about using default exports, because they are more difficult to maintain. + +Named exports are explicit. They exactly name what they import, so we have that information from them, that's a good thing. + +Also, named exports enforce us to use exactly the right name to import: + +```js +import {User} from './user.js'; +// import {MyUser} won't work, the name must be {User} +``` + +For default exports, we always choose the name when importing: + +```js +import User from './user.js'; // works +import MyUser from './user.js'; // works too +// could be import Anything..., and it'll be work +``` + +So, there's a little bit more freedom that can be abused, so that team members may use different names for the same thing. + +Usually, to avoid that and keep the code consistent, there's a rule that imported variables should correspond to file names, e.g: + +```js +import User from './user.js'; +import LoginForm from './loginForm.js'; +import func from '/path/to/func.js'; +... +``` + +Another solution would be to use named exports everywhere. Even if only a single thing is exported, it's still exported under a name, without `default`. + +That also makes re-export (see below) a little bit easier. + +## Re-export + +"Re-export" syntax `export ... from ...` allows to import things and immediately export them (possibly under another name), like this: + +```js +export {sayHi} from './say.js'; +export {default as User} from './user.js'; +``` + +What's the point, why that's needed? Let's see a practical use case. + +Imagine, we're writing a "package": a folder with a lot of modules, mostly needed internally, with some of the functionality exported outside (tools like NPM allow to publish and distribute packages, but here it doesn't matter). + +A directory structure could be like this: +``` +auth/ + index.js + user.js + helpers.js + tests/ + login.js + providers/ + github.js + facebook.js + ... +``` + +We'd like to expose the package functionality via a single entry point, the "main file" `auth/index.js`, to be used like this: + +```js +import {login, logout} from 'auth/index.js' +``` + +The idea is that outsiders, developers who use our package, should not meddle with its internal structure. They should not search for files inside our package folder. We export only what's necessary in `auth/index.js` and keep the rest hidden from prying eyes. + +Now, as the actual exported functionality is scattered among the package, we can gather and "re-export" it in `auth/index.js`: + +```js +// 📁 auth/index.js +import {login, logout} from './helpers.js'; +export {login, logout}; + +import User from './user.js'; +export {User}; + +import Github from './providers/github.js'; +export {Github}; +... +``` + +"Re-exporting" is just a shorter notation for that: + +```js +// 📁 auth/index.js +export {login, logout} from './helpers.js'; +// or, to re-export all helpers, we could use: +// export * from './helpers.js'; + +export {default as User} from './user.js'; + +export {default as Github} from './providers/github.js'; +... +``` + +````warn header="Re-exporting default is tricky" +Please note: `export User from './user.js'` won't work. It's actually a syntax error. To re-export the default export, we must mention it explicitly `{default as ...}`, like in the example above. + +Also, there's another oddity: `export * from './user.js'` re-exports only named exports, excluding the default one. Once again, we need to mention it explicitly. + +For instance, to re-export everything, two statements will be necessary: +```js +export * from './module.js'; // to re-export named exports +export {default} from './module.js'; // to re-export default +``` + +The default should be mentioned explicitly only when re-exporting: `import * as obj` works fine. It imports the default export as `obj.default`. So there's a slight asymmetry between import and export constructs here. +```` + +## Summary + +There are following types of `export`: + +- Before declaration: + - `export [default] class/function/variable ...` +- Standalone: + - `export {x [as y], ...}`. +- Re-export: + - `export {x [as y], ...} from "mod"` + - `export * from "mod"` (doesn't re-export default). + - `export {default [as y]} from "mod"` (re-export default). + +Import: + +- Named exports from module: + - `import {x [as y], ...} from "mod"` +- Default export: + - `import x from "mod"` + - `import {default as x} from "mod"` +- Everything: + - `import * as obj from "mod"` +- Import the module (it runs), but do not assign it to a variable: + - `import "mod"` + +We can put import/export statements at the top or at the bottom of a script, that doesn't matter. + +So this is technically fine: +```js +sayHi(); + +// ... + +import {sayHi} from './say.js'; // import at the end of the script +``` + +In practice imports are usually at the start of the file, but that's only for better convenience. + +**Please note that import/export statements don't work if inside `{...}`.** + +A conditional import, like this, won't work: +```js +if (something) { + import {sayHi} from "./say.js"; // Error: import must be at top level +} +``` + +...But what if we really need to import something conditionally? Or at the right time? Like, load a module upon request, when it's really needed? + +We'll see dynamic imports in the next chapter. diff --git a/1-js/13-modules/03-modules-dynamic-imports/article.md b/1-js/13-modules/03-modules-dynamic-imports/article.md new file mode 100644 index 000000000..ee8bf0a4c --- /dev/null +++ b/1-js/13-modules/03-modules-dynamic-imports/article.md @@ -0,0 +1,54 @@ + +# Dynamic imports + +Export and import statements that we covered in previous chapters are called "static". + +That's because they are indeed static. The syntax is very strict. + +First, we can't dynamically generate any parameters of `import`. + +The module path must be a primitive string, can't be a function call. This won't work: + +```js +import ... from *!*getModuleName()*/!*; // Error, only from "string" is allowed +``` + +Second, we can't import conditionally or at run-time: + +```js +if(...) { + import ...; // Error, not allowed! +} + +{ + import ...; // Error, we can't put import in any block +} +``` + +That's because, import/export aim to provide a backbone for the code structure. That's a good thing, as code structure can be analyzed, modules can be gathered and bundled together, unused exports can be removed (tree-shaken). That's possible only because everything is fixed. + +But how do we import a module dynamically, on-demand? + +## The import() function + +The `import(module)` function can be called from anywhere. It returns a promise that resolves into a module object. + +The usage pattern looks like this: + +```js run +let modulePath = prompt("Module path?"); + +import(modulePath) + .then(obj => ) + .catch(err => ) +``` + +Or, we could use `let module = await import(modulePath)` if inside an async function. + +Like this: + +[codetabs src="say" current="index.html"] + +So, dynamic imports are very simple to use. + +Also, dynamic imports work in regular scripts, they don't require `script type="module"`. diff --git a/1-js/13-modules/03-modules-dynamic-imports/say.view/index.html b/1-js/13-modules/03-modules-dynamic-imports/say.view/index.html new file mode 100644 index 000000000..80909cf94 --- /dev/null +++ b/1-js/13-modules/03-modules-dynamic-imports/say.view/index.html @@ -0,0 +1,10 @@ + + + diff --git a/1-js/13-modules/03-modules-dynamic-imports/say.view/say.js b/1-js/13-modules/03-modules-dynamic-imports/say.view/say.js new file mode 100644 index 000000000..cff234b7c --- /dev/null +++ b/1-js/13-modules/03-modules-dynamic-imports/say.view/say.js @@ -0,0 +1,11 @@ +export function hi() { + alert(`Hello`); +} + +export function bye() { + alert(`Bye`); +} + +export default function() { + alert("Module loaded (export default)!"); +} diff --git a/1-js/13-modules/index.md b/1-js/13-modules/index.md new file mode 100644 index 000000000..78fb060e8 --- /dev/null +++ b/1-js/13-modules/index.md @@ -0,0 +1,2 @@ + +# Modules diff --git a/1-js/plan3.txt b/1-js/plan3.txt deleted file mode 100644 index 4d553174f..000000000 --- a/1-js/plan3.txt +++ /dev/null @@ -1,5 +0,0 @@ -todo: - -intl? -proxy? -eval? diff --git a/10-misc/12-mutation-observer/article.md b/10-misc/12-mutation-observer/article.md new file mode 100644 index 000000000..552c2a890 --- /dev/null +++ b/10-misc/12-mutation-observer/article.md @@ -0,0 +1,250 @@ + +# Mutation observer + +`MutationObserver` is a built-in object that observes a DOM element and fires a callback in case of changes. + +We'll first see syntax, and then explore a real-world use case. + +## Syntax + +`MutationObserver` is easy to use. + +First, we create an observer with a callback-function: + +```js +let observer = new MutationObserver(callback); +``` + +And then attach it to a DOM node: + +```js +observer.observe(node, config); +``` + +`config` is an object with boolean options "what kind of changes to react on": +- `childList` -- changes in the direct children of `node`, +- `subtree` -- in all descendants of `node`, +- `attributes` -- attributes of `node`, +- `attributeOldValue` -- record the old value of attribute (infers `attributes`), +- `characterData` -- whether to observe `node.data` (text content), +- `characterDataOldValue` -- record the old value of `node.data` (infers `characterData`), +- `attributeFilter` -- an array of attribute names, to observe only selected ones. + +Then after any changes, the `callback` is executed, with a list of [MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects as the first argument, and the observer itself as the second argument. + +[MutationRecord](https://dom.spec.whatwg.org/#mutationrecord) objects have properties: + +- `type` -- mutation type, one of + - `"attributes"`: attribute modified + - `"characterData"`: data modified, used for text nodes, + - `"childList"`: child elements added/removed, +- `target` -- where the change occurred: an element for "attributes", or text node for "characterData", or an element for a "childList" mutation, +- `addedNodes/removedNodes` -- nodes that were added/removed, +- `previousSibling/nextSibling` -- the previous and next sibling to added/removed nodes, +- `attributeName/attributeNamespace` -- the name/namespace (for XML) of the changed attribute, +- `oldValue` -- the previous value, only for attribute or text changes. + + +For example, here's a `
` with `contentEditable` attribute. That attribute allows us to focus on it and edit. + +```html run +
Click and edit, please
+ + +``` + +If we change the text inside `me`, we'll get a single mutation: + +```js +mutationRecords = [{ + type: "characterData", + oldValue: "me", + target: , + // other properties empty +}]; +``` + +If we select and remove the `me` altogether, we'll get multiple mutations: + +```js +mutationRecords = [{ + type: "childList", + target: , + removedNodes: [], + nextSibling: , + previousSibling: + // other properties empty +}, { + type: "characterData" + target: + // ...details depend on how the browser handles the change + // it may coalesce two adjacent text nodes "Edit " and ", please" into one node + // or it can just delete the extra space after "Edit". + // may be one mutation or a few +}]; +``` + +## Observer use case + +When `MutationObserver` is needed? Is there a scenario when such thing can be useful? + +We can track something like `contentEditable` and implement "undo/redo" functionality (record mutations and rollback/redo them on demand). There are also cases when `MutationObserver` is good from architectural standpoint. + +Let's say we're making a website about programming. Naturally, articles and other materials may contain source code snippets. + +An HTML markup of a code snippet looks like this: +```html +... +

+  // here's the code
+  let hello = "world";
+
+... +``` + +Also we'll use a JavaScript highlighting library on our site, e.g. [Prism.js](https://prismjs.com/). A call to `Prism.highlightElem(pre)` examines the contents of such `pre` elements and adds into them special tags and styles for colored syntax highlighting, similar to what you see in examples here, at this page. + +When to run that method? We can do it on `DOMContentLoaded` event, or at the bottom of the page. At that moment we have DOM ready, can search for elements `pre[class*="language"]` and call `Prism.highlightElem` on them: + +```js +// highlight all code snippets on the page +document.querySelectorAll('pre[class*="language"]').forEach(Prism.highlightElem); +``` + +Now the `
` snippet looks like this (without line numbers by default):
+
+```js
+// here's the code
+let hello = "world";
+```
+
+Everything's simple so far, right? There are `
` code snippets in HTML, we highlight them.
+
+Now let's go on. Let's say we're going to dynamically fetch materials from a server. We'll study methods for that [later in the tutorial](info:fetch-basics). For now it only matters that we fetch an HTML article from a webserver and display it on demand:
+
+```js
+let article = /* fetch new content from server */
+articleElem.innerHTML = article;
+```
+
+The new `article` HTML may contain code snippets. We need to call `Prism.highlightElem` on them, otherwise they won't get highlighted.
+
+**Where and when to call `Prism.highlightElem` for a dynamically loaded article?**
+
+We could append that call to the code that loads an article, like this:
+
+```js
+let article = /* fetch new content from server */
+articleElem.innerHTML = article;
+
+*!*
+let snippets = articleElem.querySelectorAll('pre[class*="language-"]');
+snippets.forEach(Prism.highlightElem);
+*/!*
+```
+
+...But imagine, we have many places in the code where we load contents: articles, quizzes, forum posts. Do we need to put the highlighting call everywhere? That's not very convenient, and also easy to forget.
+
+And what if the content is loaded by a third-party module? E.g. we have a forum written by someone else, that loads contents dynamically, and we'd like to add syntax highlighting to it. No one likes to patch third-party scripts.
+
+Luckily, there's another option.
+
+We can use `MutationObserver` to automatically detect code snippets inserted in the page and highlight them.
+
+So we'll handle the highlighting functionality in one place, relieving us from the need to integrate it.
+
+## Dynamic highlight demo
+
+Here's the working example.
+
+If you run this code, it starts observing the element below and highlighting any code snippets that appear there:
+
+```js run
+let observer = new MutationObserver(mutations => {
+
+  for(let mutation of mutations) {
+    // examine new nodes
+
+    for(let node of mutation.addedNodes) {
+      // we track only elements, skip other nodes (e.g. text nodes)
+      if (!(node instanceof HTMLElement)) continue;
+
+      // check the inserted element for being a code snippet
+      if (node.matches('pre[class*="language-"]')) {
+        Prism.highlightElement(node);
+      }
+
+      // maybe there's a code snippet somewhere in its subtree?
+      for(let elem of node.querySelectorAll('pre[class*="language-"]')) {
+        Prism.highlightElement(elem);
+      }
+    }
+  }
+
+});
+
+let demoElem = document.getElementById('highlight-demo');
+
+observer.observe(demoElem, {childList: true, subtree: true});
+```
+
+

Demo element with id="highlight-demo", obverved by the example above.

+ +The code below populates `innerHTML`. Please run the code above first, it will watch and highlight the new content: + +```js run +let demoElem = document.getElementById('highlight-demo'); + +// dynamically insert content with code snippets +demoElem.innerHTML = `A code snippet is below: +
 let hello = "world!"; 
+
Another one:
+
+
.class { margin: 5px; } 
+
+`; +``` + +Now we have `MutationObserver` that can track all highlighting in observed elements or the whole `document`. We can add/remove code snippets in HTML without thinking about it. + +## Additional methods + +There's a method to stop observing the node: + +- `observer.disconnect()` -- stops the observation. + +Additionally: + +- `mutationRecords = observer.takeRecords()` -- gets a list of unprocessed mutation records, those that happened, but the callback did not handle them. + +```js +// we're going to disconnect the observer +// it might have not yet handled some mutations +let mutationRecords = observer.takeRecords(); +// process mutationRecords + +// now all handled, disconnect +observer.disconnect(); +``` + +## Garbage collection + +Observers use weak references to nodes internally. That is: if a node is removed from DOM, and becomes unreachable, then it becomes garbage collected, an observer doesn't prevent that. + +## Summary + +`MutationObserver` can react on changes in DOM: attributes, added/removed elements, text content. + +We can use it to track changes introduced by other parts of our own or 3rd-party code. + +For example, to post-process dynamically inserted content, as demo `innerHTML`, like highlighting in the example above. diff --git a/10-misc/index.md b/10-misc/index.md new file mode 100644 index 000000000..65ab3188a --- /dev/null +++ b/10-misc/index.md @@ -0,0 +1,4 @@ + +# Miscellaneous + +Not yet categorized articles. diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index 2266a2def..6ff17f9b8 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -4,7 +4,7 @@ The JavaScript language was initially created for web browsers. Since then, it h A platform may be a browser, or a web-server, or a washing machine, or another *host*. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*. -A host environment provides platform-specific objects and functions additional to the language core. Web browsers give a means to control web pages. Node.JS provides server-side features, and so on. +A host environment provides platform-specific objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. Here's a bird's-eye view of what we have when JavaScript runs in a web-browser: diff --git a/2-ui/1-document/01-browser-environment/windowObjects.png b/2-ui/1-document/01-browser-environment/windowObjects.png index 81803bd09..5408ecb15 100644 Binary files a/2-ui/1-document/01-browser-environment/windowObjects.png and b/2-ui/1-document/01-browser-environment/windowObjects.png differ diff --git a/2-ui/1-document/01-browser-environment/windowObjects@2x.png b/2-ui/1-document/01-browser-environment/windowObjects@2x.png index e6dae7c3d..d84b380d5 100644 Binary files a/2-ui/1-document/01-browser-environment/windowObjects@2x.png and b/2-ui/1-document/01-browser-environment/windowObjects@2x.png differ diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index a701fb0ce..c496b16d1 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -106,7 +106,7 @@ drawHtmlTree(node3, 'div.domtree', 690, 150); While generating the DOM, browsers automatically process errors in the document, close tags and so on. -Such an "invalid" document: +Such an document with unclosed tags: ```html no-beautify

Hello @@ -176,7 +176,7 @@ drawHtmlTree(node6, 'div.domtree', 690, 500); Here we see a new tree node type -- *comment node*, labeled as `#comment`. -We may think -- why a comment is added to the DOM? It doesn't affect the visual representation in any way. But there's a rule -- if something's in HTML, then it also must be in the DOM tree. +We may think -- why is a comment added to the DOM? It doesn't affect the visual representation in any way. But there's a rule -- if something's in HTML, then it also must be in the DOM tree. **Everything in HTML, even comments, becomes a part of the DOM.** @@ -225,7 +225,7 @@ The best way to study them is to click around. Most values are editable in-place ## Interaction with console -As we explore the DOM, we also may want to apply JavaScript to it. Like: get a node and run some code to modify it, to see how it looks. Here are few tips to travel between the Elements tab and the console. +As we explore the DOM, we also may want to apply JavaScript to it. Like: get a node and run some code to modify it, to see the result. Here are few tips to travel between the Elements tab and the console. - Select the first `

  • ` in the Elements tab. - Press `key:Esc` -- it will open console right below the Elements tab. diff --git a/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md b/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md index e962eb662..266d26b1b 100644 --- a/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md +++ b/2-ui/1-document/03-dom-navigation/3-navigation-links-which-null/solution.md @@ -1,4 +1,6 @@ -1. Yes, true. The element `elem.lastChild` is always the last one, it has no `nextSibling`, so if there are children, then yes. -2. No, wrong, because `elem.children[0]` is the first child among elements. But there may be non-element nodes before it. So `previousSibling` may be a text node. +1. Yes, true. The element `elem.lastChild` is always the last one, it has no `nextSibling`. +2. No, wrong, because `elem.children[0]` is the first child *among elements*. But there may exist non-element nodes before it. So `previousSibling` may be a text node. Also, if there are no children, then trying to access `elem.children[0]` -Please note that for both cases if there are no children, then there will be an error. For instance, if `elem.lastChild` is `null`, we can't access `elem.lastChild.nextSibling`. +Please note: for both cases if there are no children, then there will be an error. + +If there are no children, `elem.lastChild` is `null`, so we can't access `elem.lastChild.nextSibling`. And the collection `elem.children` is empty (like an empty array `[]`). diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index 976e595a2..be5a6531b 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -7,11 +7,11 @@ libs: # Walking the DOM -The DOM allows to do anything with elements and their contents, but first we need to reach the corresponding DOM object, get it into a variable, and then we are able to modify it. +The DOM allows us to do anything with elements and their contents, but first we need to reach the corresponding DOM object. All operations on the DOM start with the `document` object. From it we can access any node. -Here's a picture of links that allow to travel between DOM nodes: +Here's a picture of links that allow for travel between DOM nodes: ![](dom-links.png) @@ -86,7 +86,7 @@ For instance, here `` has children `
    ` and `
      ` (and few blank text ``` -...And if we ask for all descendants of ``, then we get direct children `
      `, `
        ` and also more nested elements like `
      • ` (being a child of `
          `) and `` (being a child of `
        • `) -- the entire subtree. +...And all descendants of `` are not only direct children `
          `, `
            ` but also more deeply nested elements, such as `
          • ` (a child of `
              `) and `` (a child of `
            • `) -- the entire subtree. **The `childNodes` collection provides access to all child nodes, including text nodes.** @@ -155,9 +155,9 @@ The first thing is nice. The second is tolerable, because we can use `Array.from ```warn header="DOM collections are read-only" DOM collections, and even more -- *all* navigation properties listed in this chapter are read-only. -We can't replace a child by something else assigning `childNodes[i] = ...`. +We can't replace a child by something else by assigning `childNodes[i] = ...`. -Changing DOM needs other methods, we'll see them in the next chapter. +Changing DOM needs other methods. We will see them in the next chapter. ``` ```warn header="DOM collections are live" @@ -237,7 +237,12 @@ alert( document.documentElement.parentElement ); // null In other words, the `documentElement` (``) is the root node. Formally, it has `document` as its parent. But `document` is not an element node, so `parentNode` returns it and `parentElement` does not. -Sometimes that matters when we're walking over the chain of parents and call a method on each of them, but `document` doesn't have it, so we exclude it. +This loop travels up from an arbitrary element `elem` to ``, but not to the `document`: +```js +while(elem = elem.parentElement) { + alert( elem ); // parent chain till +} +``` ```` Let's modify one of the examples above: replace `childNodes` with `children`. Now it shows only elements: @@ -309,7 +314,7 @@ An example of usage: The specification: [tabular data](https://html.spec.whatwg.org/multipage/tables.html). -There are also additional navigation properties for HTML forms. We'll look at them later when start working with forms. +There are also additional navigation properties for HTML forms. We'll look at them later when we start working with forms. # Summary diff --git a/2-ui/1-document/03-dom-navigation/dom-links-elements.png b/2-ui/1-document/03-dom-navigation/dom-links-elements.png index f7eef5d1f..c0e2f6941 100644 Binary files a/2-ui/1-document/03-dom-navigation/dom-links-elements.png and b/2-ui/1-document/03-dom-navigation/dom-links-elements.png differ diff --git a/2-ui/1-document/03-dom-navigation/dom-links-elements@2x.png b/2-ui/1-document/03-dom-navigation/dom-links-elements@2x.png index cf4e220cf..663471177 100644 Binary files a/2-ui/1-document/03-dom-navigation/dom-links-elements@2x.png and b/2-ui/1-document/03-dom-navigation/dom-links-elements@2x.png differ diff --git a/2-ui/1-document/03-dom-navigation/dom-links.png b/2-ui/1-document/03-dom-navigation/dom-links.png index 25b25b377..d6b5ddf96 100644 Binary files a/2-ui/1-document/03-dom-navigation/dom-links.png and b/2-ui/1-document/03-dom-navigation/dom-links.png differ diff --git a/2-ui/1-document/03-dom-navigation/dom-links@2x.png b/2-ui/1-document/03-dom-navigation/dom-links@2x.png index 84cd33d4e..827552f65 100644 Binary files a/2-ui/1-document/03-dom-navigation/dom-links@2x.png and b/2-ui/1-document/03-dom-navigation/dom-links@2x.png differ diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index 3a7a9aeee..b5dbba50d 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -1,13 +1,14 @@ -# Searching: getElement* and querySelector* +# Searching: getElement*, querySelector* DOM navigation properties are great when elements are close to each other. What if they are not? How to get an arbitrary element of the page? There are additional searching methods for that. + ## document.getElementById or just id If an element has the `id` attribute, then there's a global variable by the name from that `id`. -We can use it to access the element, like this: +We can use it to immediately access the element no matter where it is: ```html run
              @@ -24,7 +25,9 @@ We can use it to access the element, like this: ``` -That's unless we declare the same-named variable by our own: +The behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. Good for very simple scripts, but there may be name conflicts. Also, when we look in JS and don't have HTML in view, it's not obvious where the variable comes from. + +If we declare a variable with the same name, it takes precedence: ```html run untrusted height=0
              @@ -32,12 +35,10 @@ That's unless we declare the same-named variable by our own: ``` -The behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. Good for very simple scripts, but there may be name conflicts. Also, when we look in JS and don't have HTML in view, it's not obvious where the variable comes from. - The better alternative is to use a special method `document.getElementById(id)`. For instance: @@ -68,104 +69,9 @@ If there are multiple elements with the same `id`, then the behavior of correspo The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document. ``` -## getElementsBy* - -There are also other methods to look for nodes: - -- `elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` parameter can also be a star `"*"` for "any tags". - -For instance: -```js -// get all divs in the document -let divs = document.getElementsByTagName('div'); -``` - -This method is callable in the context of any DOM element. - -Let's find all `input` tags inside the table: - -```html run height=50 - - - - - - -
              Your age: - - - -
              - - -``` - -```warn header="Don't forget the `\"s\"` letter!" -Novice developers sometimes forget the letter `"s"`. That is, they try to call `getElementByTagName` instead of getElementsByTagName. - -The `"s"` letter is absent in `getElementById`, because it returns a single element. But `getElementsByTagName` returns a collection of elements, so there's `"s"` inside. -``` - -````warn header="It returns a collection, not an element!" -Another widespread novice mistake is to write: - -```js -// doesn't work -document.getElementsByTagName('input').value = 5; -``` - -That won't work, because it takes a *collection* of inputs and assigns the value to it rather than to elements inside it. - -We should either iterate over the collection or get an element by its index, and then assign, like this: - -```js -// should work (if there's an input) -document.getElementsByTagName('input')[0].value = 5; -``` -```` - -There are also other rarely used methods of this kind: - -- `elem.getElementsByClassName(className)` returns elements that have the given CSS class. Elements may have other classes too. -- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. Exists for historical reasons, very rarely used, we mention it here only for completeness. - -For instance: - -```html run height=50 -
              -
              Article
              -
              Long article
              -
              - - -``` - ## querySelectorAll [#querySelectorAll] -Now goes the heavy artillery. - -The call to `elem.querySelectorAll(css)` returns all elements inside `elem` matching the given CSS selector. That's the most often used and powerful method. +By far, the most versatile method, `elem.querySelectorAll(css)` returns all elements inside `elem` matching the given CSS selector. Here we look for all `
            • ` elements that are last children: @@ -195,7 +101,6 @@ This method is indeed powerful, because any CSS selector can be used. Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `` to the most nested one). ``` - ## querySelector [#querySelector] The call to `elem.querySelector(css)` returns the first element for the given CSS selector. @@ -230,9 +135,7 @@ For instance: ## closest -All elements that are directly above the given one are called its *ancestors*. - -In other words, ancestors are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top. +*Ancestors* of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top. The method `elem.closest(css)` looks the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. @@ -260,14 +163,106 @@ For instance: ``` +## getElementsBy* + +There are also other methods to look for nodes by a tag, class, etc. + +Today, they are mostly history, as `querySelector` is more powerful and shorter to write. + +So here we cover them mainly for completeness, while you can still find them in the old scripts. + +- `elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` parameter can also be a star `"*"` for "any tags". +- `elem.getElementsByClassName(className)` returns elements that have the given CSS class. +- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. very rarely used. + +For instance: +```js +// get all divs in the document +let divs = document.getElementsByTagName('div'); +``` + +Let's find all `input` tags inside the table: + +```html run height=50 + + + + + + +
              Your age: + + + +
              + + +``` + +```warn header="Don't forget the `\"s\"` letter!" +Novice developers sometimes forget the letter `"s"`. That is, they try to call `getElementByTagName` instead of getElementsByTagName. + +The `"s"` letter is absent in `getElementById`, because it returns a single element. But `getElementsByTagName` returns a collection of elements, so there's `"s"` inside. +``` + +````warn header="It returns a collection, not an element!" +Another widespread novice mistake is to write: + +```js +// doesn't work +document.getElementsByTagName('input').value = 5; +``` + +That won't work, because it takes a *collection* of inputs and assigns the value to it rather than to elements inside it. + +We should either iterate over the collection or get an element by its index, and then assign, like this: + +```js +// should work (if there's an input) +document.getElementsByTagName('input')[0].value = 5; +``` +```` + +Looking for `.article` elements: + +```html run height=50 +
              +
              Article
              +
              Long article
              +
              + + +``` + ## Live collections All methods `"getElementsBy*"` return a *live* collection. Such collections always reflect the current state of the document and "auto-update" when it changes. In the example below, there are two scripts. -1. The first one creates a reference to the collection of `
              `. As of now, it's length is `1`. -2. The second scripts runs after the browser meets one more `
              `, so it's length is `2`. +1. The first one creates a reference to the collection of `
              `. As of now, its length is `1`. +2. The second scripts runs after the browser meets one more `
              `, so its length is `2`. ```html run
              First div
              @@ -310,8 +305,6 @@ If we use it instead, then both scripts output `1`: Now we can easily see the difference. The static collection did not increase after the appearance of a new `div` in the document. -Here we used separate scripts to illustrate how the element addition affects the collection, but any DOM manipulations affect them. Soon we'll see more of them. - ## Summary There are 6 main methods to search for nodes in DOM: @@ -327,6 +320,18 @@ There are 6 main methods to search for nodes in DOM: +querySelector +CSS-selector +✔ +- + + +querySelectorAll +CSS-selector +✔ +- + + getElementById id - @@ -350,29 +355,15 @@ There are 6 main methods to search for nodes in DOM: ✔ ✔ - -querySelector -CSS-selector -✔ -- - - -querySelectorAll -CSS-selector -✔ -- - -Please note that methods `getElementById` and `getElementsByName` can only be called in the context of the document: `document.getElementById(...)`. But not on an element: `elem.getElementById(...)` would cause an error. - -Other methods can be called on elements too. For instance `elem.querySelectorAll(...)` will search inside `elem` (in the DOM subtree). +By far the most used are `querySelector` and `querySelectorAll`, but `getElementBy*` can be sporadically helpful or found in the old scripts. Besides that: - There is `elem.matches(css)` to check if `elem` matches the given CSS selector. - There is `elem.closest(css)` to look for the nearest ancestor that matches the given CSS-selector. The `elem` itself is also checked. -And let's mention one more method here to check for the child-parent relationship: +And let's mention one more method here to check for the child-parent relationship, as it's sometimes useful: - `elemA.contains(elemB)` returns true if `elemB` is inside `elemA` (a descendant of `elemA`) or when `elemA==elemB`. diff --git a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.md b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.md index 781b7a929..0088882c2 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.md +++ b/2-ui/1-document/05-basic-dom-node-properties/2-tree-info/solution.md @@ -6,7 +6,9 @@ for (let li of document.querySelectorAll('li')) { } ``` -In the loop we need to get the text inside every `li`. We can read it directly from the first child node, that is the text node: +In the loop we need to get the text inside every `li`. + +We can read the text from the first child node of `li`, that is the text node: ```js for (let li of document.querySelectorAll('li')) { @@ -16,4 +18,4 @@ for (let li of document.querySelectorAll('li')) { } ``` -Then we can get the number of descendants `li.getElementsByTagName('li')`. +Then we can get the number of descendants as `li.getElementsByTagName('li').length`. diff --git a/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md b/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md index db7ebc9d8..185d2c130 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md +++ b/2-ui/1-document/05-basic-dom-node-properties/4-where-document-in-hierarchy/solution.md @@ -27,7 +27,7 @@ Also, there's a reference to the constructor function inside the `prototype`: alert(HTMLDocument.prototype.constructor === HTMLDocument); // true ``` -For built-in classes in all prototypes there's a `constructor` reference, and we can get `constructor.name` to see the name of the class. Let's do it for all objects in the `document` prototype chain: +To get a name of the class as a string, we can use `constructor.name`. Let's do it for the whole `document` prototype chain, till class `Node`: ```js run alert(HTMLDocument.prototype.constructor.name); // HTMLDocument diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index eca02163a..7fc36f854 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -20,7 +20,7 @@ The classes are: - [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. - [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes. -- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. In the browser there may be not only HTML, but also XML and SVG documents. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`. +- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. A browser supports not only HTML, but also XML and SVG. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`. - [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by various HTML elements: - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `` elements, - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `` elements, @@ -76,7 +76,7 @@ Try it on `document.body`. ``` ````smart header="IDL in the spec" -In the specification, classes are described not using JavaScript, but a special [Interface description language](https://en.wikipedia.org/wiki/Interface_description_language) (IDL), that is usually easy to understand. +In the specification, DOM classes are described not using JavaScript, but a special [Interface description language](https://en.wikipedia.org/wiki/Interface_description_language) (IDL), that is usually easy to understand. In IDL all properties are prepended with their types. For instance, `DOMString`, `boolean` and so on. @@ -156,7 +156,7 @@ alert( document.body.nodeName ); // BODY alert( document.body.tagName ); // BODY ``` -Is there any difference between tagName and nodeName? +Is there any difference between `tagName` and `nodeName`? Sure, the difference is reflected in their names, but is indeed a bit subtle. @@ -175,11 +175,11 @@ For instance, let's compare `tagName` and `nodeName` for the `document` and a co @@ -232,14 +232,12 @@ We can try to insert invalid HTML, the browser will fix our errors: ``` ```smart header="Scripts don't execute" -If `innerHTML` inserts a ` ``` -That's an important difference. But even if a DOM property type is a string, it may differ from the attribute! +Most properties are strings though. -For instance, the `href` DOM property is always a *full* URL, even if the attribute contains a relative URL or just a `#hash`. +Quite rarely, even if a DOM property type is a string, it may differ from the attribute. For instance, the `href` DOM property is always a *full* URL, even if the attribute contains a relative URL or just a `#hash`. Here's an example: @@ -380,7 +380,7 @@ Methods to work with attributes are: - `elem.removeAttribute(name)` -- to remove the attribute. - `elem.attributes` is a collection of all attributes. -For most needs, DOM properties can serve us well. We should refer to attributes only when DOM properties do not suit us, when we need exactly attributes, for instance: +For most situations using DOM properties is preferable. We should refer to attributes only when DOM properties do not suit us, when we need exactly attributes, for instance: - We need a non-standard attribute. But if it starts with `data-`, then we should use `dataset`. - We want to read the value "as written" in HTML. The value of the DOM property may be different, for instance the `href` property is always a full URL, and we may want to get the "original" value. diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md index 93ae862fd..a38f01645 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/solution.md @@ -12,7 +12,7 @@ Here's an example: let text = 'text'; elem1.append(document.createTextNode(text)); - elem2.textContent = text; - elem3.innerHTML = text; + elem2.innerHTML = text; + elem3.textContent = text; ``` diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md index 70bc2ae9d..a1b53e337 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/task.md @@ -8,4 +8,4 @@ Create a colored clock like here: [iframe src="solution" height=60] -Use HTML/CSS for the styling, Javascript only updates time in elements. +Use HTML/CSS for the styling, JavaScript only updates time in elements. diff --git a/2-ui/1-document/07-modifying-document/7-create-object-tree/innerhtml.view/index.html b/2-ui/1-document/07-modifying-document/7-create-object-tree/innerhtml.view/index.html index 9ba40a6d0..fd85e6c76 100644 --- a/2-ui/1-document/07-modifying-document/7-create-object-tree/innerhtml.view/index.html +++ b/2-ui/1-document/07-modifying-document/7-create-object-tree/innerhtml.view/index.html @@ -29,11 +29,12 @@ function createTreeText(obj) { // standalone recursive function let li = ''; + let ul; for (let key in obj) { li += '
            • ' + key + createTreeText(obj[key]) + '
            • '; } if (li) { - let ul = '
                ' + li + '
              ' + ul = '
                ' + li + '
              ' } return ul || ''; } diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index 9c38b3344..16aacb456 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -136,7 +136,7 @@ Here's a brief list of methods to insert a node into a parent element (`parentEl ``` To insert `newLi` as the first element, we can do it like this: - + ```js list.insertBefore(newLi, list.firstChild); ``` @@ -335,13 +335,81 @@ An example of copying the message: ``` + +## DocumentFragment [#document-fragment] + +`DocumentFragment` is a special DOM node that serves as a wrapper to pass around groups of nodes. + +We can append other nodes to it, but when we insert it somewhere, then it "disappears", leaving its content inserted instead. + +For example, `getListContent` below generates a fragment with `
            • ` items, that are later inserted into `
                `: + +```html run +
                  + + +``` + +Please note, at the last line `(*)` we append `DocumentFragment`, but it "blends in", so the resulting structure will be: + +```html +
                    +
                  • 1
                  • +
                  • 2
                  • +
                  • 3
                  • +
                  +``` + +`DocumentFragment` is rarely used explicitly. Why append to a special kind of node, if we can return an array of nodes instead? Rewritten example: + +```html run +
                    + + +``` + +We mention `DocumentFragment` mainly because there are some concepts on top of it, like [template](info:template-element) element, that we'll cover much later. + + ## Removal methods To remove nodes, there are the following methods: `parentElem.removeChild(node)` -: Removes `elem` from `parentElem` (assuming it's a child). +: Removes `node` from `parentElem` (assuming it's a child). `node.remove()` : Removes the `node` from its place. diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after.png b/2-ui/1-document/07-modifying-document/before-prepend-append-after.png index 5bff84d85..858056f72 100644 Binary files a/2-ui/1-document/07-modifying-document/before-prepend-append-after.png and b/2-ui/1-document/07-modifying-document/before-prepend-append-after.png differ diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after@2x.png b/2-ui/1-document/07-modifying-document/before-prepend-append-after@2x.png index 44c369ee6..9d6894314 100644 Binary files a/2-ui/1-document/07-modifying-document/before-prepend-append-after@2x.png and b/2-ui/1-document/07-modifying-document/before-prepend-append-after@2x.png differ diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent.png b/2-ui/1-document/07-modifying-document/insert-adjacent.png index 08063bc51..7cf2f5973 100644 Binary files a/2-ui/1-document/07-modifying-document/insert-adjacent.png and b/2-ui/1-document/07-modifying-document/insert-adjacent.png differ diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent@2x.png b/2-ui/1-document/07-modifying-document/insert-adjacent@2x.png index 60333ad1b..fcb14017a 100644 Binary files a/2-ui/1-document/07-modifying-document/insert-adjacent@2x.png and b/2-ui/1-document/07-modifying-document/insert-adjacent@2x.png differ diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index ed9db3032..5159c532d 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -116,7 +116,7 @@ Sometimes we want to assign a style property, and later remove it. For instance, to hide an element, we can set `elem.style.display = "none"`. -Then later we may want to remove the `style.display` as if it were not set. Instead of `delete elem.style.display` we should assign an empty line to it: `elem.style.display = ""`. +Then later we may want to remove the `style.display` as if it were not set. Instead of `delete elem.style.display` we should assign an empty string to it: `elem.style.display = ""`. ```js run // if we run this code, the "blinks" @@ -207,7 +207,7 @@ For instance, here `style` doesn't see the margin: ``` -...But what if we need, say, to increase the margin by 20px? We want the current value for the start. +...But what if we need, say, to increase the margin by 20px? We would want the current value of it. There's another method for that: `getComputedStyle`. @@ -281,7 +281,7 @@ Visited links may be colored using `:visited` CSS pseudoclass. But `getComputedStyle` does not give access to that color, because otherwise an arbitrary page could find out whether the user visited a link by creating it on the page and checking the styles. -JavaScript we may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids to apply geometry-changing styles in `:visited`. That's to guarantee that there's no sideway for an evil page to test if a link was visited and hence to break the privacy. +JavaScript may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids to apply geometry-changing styles in `:visited`. That's to guarantee that there's no sideway for an evil page to test if a link was visited and hence to break the privacy. ``` ## Summary diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.png b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.png index 098f9eb6d..67b9019a0 100644 Binary files a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.png and b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.png differ diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field@2x.png b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field@2x.png index 3ecb29375..fe58f3542 100644 Binary files a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field@2x.png and b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/article.md b/2-ui/1-document/09-size-and-scroll/article.md index da02abdc0..774857f3f 100644 --- a/2-ui/1-document/09-size-and-scroll/article.md +++ b/2-ui/1-document/09-size-and-scroll/article.md @@ -64,7 +64,7 @@ The `offsetParent` is the nearest ancestor that is: 2. or ``, ``, ``, 2. or ``. -In most practical cases we can use `offsetParent` to get the nearest CSS-positioned ancestor. And `offsetLeft/offsetTop` provide x/y coordinates relative to it's upper-left corner. +In most practical cases we can use `offsetParent` to get the nearest CSS-positioned ancestor. And `offsetLeft/offsetTop` provide x/y coordinates relative to its upper-left corner. In the example below the inner `
                    ` has `
                    ` as `offsetParent` and `offsetLeft/offsetTop` shifts from its upper-left corner (`180`): @@ -202,7 +202,7 @@ On the picture below we can see `scrollHeight` and `scrollTop` for a block with In other words, `scrollTop` is "how much is scrolled up". ````smart header="`scrollLeft/scrollTop` can be modified" -Most geometry properties that are read-only, but `scrollLeft/scrollTop` can be changed, and the browser will scroll the element. +Most of the geometry properties here are read-only, but `scrollLeft/scrollTop` can be changed, and the browser will scroll the element. ```online If you click the element below, the code `elem.scrollTop += 10` executes. That makes the element content scroll `10px` down. diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.png b/2-ui/1-document/09-size-and-scroll/metric-all.png index a6335bcd8..6060b9890 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-all.png and b/2-ui/1-document/09-size-and-scroll/metric-all.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-all@2x.png b/2-ui/1-document/09-size-and-scroll/metric-all@2x.png index e084c257b..3c53be1dd 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-all@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-all@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.png b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.png index ffb10aa1b..efcc9440b 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.png and b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl@2x.png b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl@2x.png index 48510e6e0..9b7edcf57 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.png b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.png index 33faf9be7..787557205 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.png and b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top@2x.png b/2-ui/1-document/09-size-and-scroll/metric-client-left-top@2x.png index a8e170eab..8034c7aff 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-left-top@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-client-left-top@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.png b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.png index d10edb5f6..d50d1186f 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.png and b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height@2x.png b/2-ui/1-document/09-size-and-scroll/metric-client-width-height@2x.png index f31f483e4..76017b9ee 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-width-height@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-client-width-height@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.png b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.png index 8280e40f1..173bf1529 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.png and b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding@2x.png b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding@2x.png index 5a73f87a9..2d145d77f 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-css.png b/2-ui/1-document/09-size-and-scroll/metric-css.png index a28bbab78..5579b4abd 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-css.png and b/2-ui/1-document/09-size-and-scroll/metric-css.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-css@2x.png b/2-ui/1-document/09-size-and-scroll/metric-css@2x.png index e4fd7bb87..c4df7706b 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-css@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-css@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.png b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.png index a0fba50d2..059ef13ef 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.png and b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent@2x.png b/2-ui/1-document/09-size-and-scroll/metric-offset-parent@2x.png index 125a91e15..0f86b4f85 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-offset-parent@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-offset-parent@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.png b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.png index f25957848..82551b8e1 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.png and b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height@2x.png b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height@2x.png index 7bfb24c22..805687cd6 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.png b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.png index 13c57161f..83f410837 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.png and b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top@2x.png b/2-ui/1-document/09-size-and-scroll/metric-scroll-top@2x.png index bf0cf711a..1ef15502e 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-scroll-top@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-scroll-top@2x.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.png b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.png index 197a5d764..54e6b9634 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.png and b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.png differ diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height@2x.png b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height@2x.png index 1e2a20f54..d57c5c198 100644 Binary files a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height@2x.png and b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height@2x.png differ diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index 4a62f56c6..b8c16a3c7 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -1,6 +1,6 @@ # Window sizes and scrolling -How to find out the width of the browser window? How to get the full height of the document, including the scrolled out part? How to scroll the page using JavaScript? +How to find out the width and height of the browser window? How to get the full width and height of the document, including the scrolled out part? How to scroll the page using JavaScript? From the DOM point of view, the root document element is `document.documentElement`. That element corresponds to `` and has geometry properties described in the [previous chapter](info:size-and-scroll). For some cases we can use it, but there are additional methods and peculiarities important enough to consider. @@ -44,7 +44,7 @@ Theoretically, as the root document element is `documentElement.clientWidth/Heig These properties work well for regular elements. But for the whole page these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! For regular elements that's a nonsense. -To have a reliable full window size, we should take the maximum of these properties: +To have a reliable result on the full document height, we should take the maximum of these properties: ```js run let scrollHeight = Math.max( @@ -96,7 +96,7 @@ It should work, but smells like cross-browser incompatibilities. Not good. Fortu ``` -- The method `scrollTo(pageX,pageY)` scrolls the page relative to the document top-left corner. It's like setting `scrollLeft/scrollTop`. +- The method `scrollTo(pageX,pageY)` scrolls the page relative to the document's top-left corner. It's like setting `scrollLeft/scrollTop`. To scroll to the very beginning, we can use `scrollTo(0,0)`. @@ -129,7 +129,7 @@ And this button scrolls the page to show it at the bottom: Sometimes we need to make the document "unscrollable". For instance, when we need to cover it with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. -To make the document unscrollable, its enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll. +To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll. ```online Try it: @@ -145,7 +145,7 @@ We can use the same technique to "freeze" the scroll for other elements, not jus The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it. -That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width same. +That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width the same. ## Summary diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.png b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.png index 76a45a7a7..a3cf95f11 100644 Binary files a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.png and b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.png differ diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height@2x.png b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height@2x.png index 249db0edb..868dc8a26 100644 Binary files a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height@2x.png and b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height@2x.png differ diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index ac5978f12..61b5161eb 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -48,7 +48,7 @@ Also: - Coordinates may be decimal fractions. That's normal, internally browser uses them for calculations. We don't have to round them when setting to `style.position.left/top`, the browser is fine with fractions. - Coordinates may be negative. For instance, if the page is scrolled down and the top `elem` is now above the window. Then, `elem.getBoundingClientRect().top` is negative. -- Some browsers (like Chrome) provide additional properties (`width` and `height`) to `getBoundingClientRect` as the result. We can also get them by subtraction: `height=bottom-top`, `width=right-left`. +- Some browsers (like Chrome) provide additional properties, `width` and `height` of the element that invoked the method to `getBoundingClientRect` as the result. We can also get them by subtraction: `height=bottom-top`, `width=right-left`. ```warn header="Coordinates right/bottom are different from CSS properties" If we compare window coordinates versus CSS positioning, then there are obvious similarities to `position:fixed`. The positioning of an element is also relative to the viewport. diff --git a/2-ui/1-document/11-coordinates/coords.png b/2-ui/1-document/11-coordinates/coords.png index da4869fb9..79d1fdbb6 100644 Binary files a/2-ui/1-document/11-coordinates/coords.png and b/2-ui/1-document/11-coordinates/coords.png differ diff --git a/2-ui/1-document/11-coordinates/coords@2x.png b/2-ui/1-document/11-coordinates/coords@2x.png index c3469f4cd..d3e340f50 100644 Binary files a/2-ui/1-document/11-coordinates/coords@2x.png and b/2-ui/1-document/11-coordinates/coords@2x.png differ diff --git a/2-ui/1-document/11-coordinates/document-window-coordinates-scroll.png b/2-ui/1-document/11-coordinates/document-window-coordinates-scroll.png index 9e4d9d5e9..29c59aad1 100644 Binary files a/2-ui/1-document/11-coordinates/document-window-coordinates-scroll.png and b/2-ui/1-document/11-coordinates/document-window-coordinates-scroll.png differ diff --git a/2-ui/1-document/11-coordinates/document-window-coordinates-scroll@2x.png b/2-ui/1-document/11-coordinates/document-window-coordinates-scroll@2x.png index 52e555c49..3c188ee45 100644 Binary files a/2-ui/1-document/11-coordinates/document-window-coordinates-scroll@2x.png and b/2-ui/1-document/11-coordinates/document-window-coordinates-scroll@2x.png differ diff --git a/2-ui/1-document/11-coordinates/document-window-coordinates-zero.png b/2-ui/1-document/11-coordinates/document-window-coordinates-zero.png index 3624196b0..3dc17f025 100644 Binary files a/2-ui/1-document/11-coordinates/document-window-coordinates-zero.png and b/2-ui/1-document/11-coordinates/document-window-coordinates-zero.png differ diff --git a/2-ui/1-document/11-coordinates/document-window-coordinates-zero@2x.png b/2-ui/1-document/11-coordinates/document-window-coordinates-zero@2x.png index 83f31950c..45f6b9e7f 100644 Binary files a/2-ui/1-document/11-coordinates/document-window-coordinates-zero@2x.png and b/2-ui/1-document/11-coordinates/document-window-coordinates-zero@2x.png differ diff --git a/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/solution.md b/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/solution.md index ec2806696..cded5b622 100644 --- a/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/02-hide-self-onclick/solution.md @@ -1,4 +1,4 @@ -Can use `this` in the handler to reference "itself" here: +Can use `this` in the handler to reference "the element itself" here: ```html run height=50 diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.png b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.png index ca2f49edb..4a0549582 100644 Binary files a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.png and b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords.png differ diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords@2x.png b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords@2x.png index b4c4d3ef2..ecf4e5d2f 100644 Binary files a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords@2x.png and b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/move-ball-coords@2x.png differ diff --git a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.md b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.md index 8312f0391..00de64008 100644 --- a/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/04-move-ball-field/solution.md @@ -33,17 +33,17 @@ We have `event.clientX/clientY` -- window-relative coordinates of the click. To get field-relative `left` coordinate of the click, we can substract the field left edge and the border width: ```js -let left = event.clientX - fieldInnerCoords.left - field.clientLeft; +let left = event.clientX - fieldCoords.left - field.clientLeft; ``` -Normally, `ball.style.position.left` means the "left edge of the element" (the ball). So if we assign that `left`, then the ball edge would be under the mouse cursor. +Normally, `ball.style.position.left` means the "left edge of the element" (the ball). So if we assign that `left`, then the ball edge, not center, would be under the mouse cursor. We need to move the ball half-width left and half-height up to make it center. So the final `left` would be: ```js -let left = event.clientX - fieldInnerCoords.left - field.clientLeft - ball.offsetWidth/2; +let left = event.clientX - fieldCoords.left - field.clientLeft - ball.offsetWidth/2; ``` The vertical coordinate is calculated using the same logic. diff --git a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.md b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.md index f4c105941..7554a2f09 100644 --- a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.md +++ b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/solution.md @@ -2,9 +2,9 @@ # HTML/CSS First let's create HTML/CSS. -A menu is a standalone graphical component on the page, so its better to put it into a single DOM element. +A menu is a standalone graphical component on the page, so it's better to put it into a single DOM element. -A list of menu items can be layed out as a list `ul/li`. +A list of menu items can be laid out as a list `ul/li`. Here's the example structure: @@ -29,7 +29,7 @@ Like this: So if we set `onclick` on it, then it will catch clicks to the right of the text. -...but `` has an implicit `display: inline`, so it occupies exactly enough place to fit all the text: +As `` has an implicit `display: inline`, it occupies exactly enough place to fit all the text: ```html autorun height=50 Sweeties (click me)! diff --git a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/task.md b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/task.md index 05af13cca..34c313710 100644 --- a/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/task.md +++ b/2-ui/2-events/01-introduction-browser-events/05-sliding-menu/task.md @@ -2,7 +2,7 @@ importance: 5 --- -# Create a menu sliding menu +# Create a sliding menu Create a menu that opens/collapses on click: diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.png b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.png index b58ac0a66..93defb0bb 100644 Binary files a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.png and b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1.png differ diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1@2x.png b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1@2x.png index fe6a0badd..7aa0d0f84 100644 Binary files a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1@2x.png and b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel1@2x.png differ diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.png b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.png index 351ead9c4..a61bf5e57 100644 Binary files a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.png and b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2.png differ diff --git a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2@2x.png b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2@2x.png index 05dbc1adf..4742579db 100644 Binary files a/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2@2x.png and b/2-ui/2-events/01-introduction-browser-events/07-carousel/carousel2@2x.png differ diff --git a/2-ui/2-events/01-introduction-browser-events/article.md b/2-ui/2-events/01-introduction-browser-events/article.md index dcf64a200..ee69e9df2 100644 --- a/2-ui/2-events/01-introduction-browser-events/article.md +++ b/2-ui/2-events/01-introduction-browser-events/article.md @@ -18,7 +18,7 @@ Here's a list of the most useful DOM events, just to take a look at: **Keyboard events:** - `keydown` and `keyup` -- when the visitor presses and then releases the button. -**Document events** +**Document events:** - `DOMContentLoaded` -- when the HTML is loaded and processed, DOM is fully built. **CSS events:** @@ -160,9 +160,9 @@ button.onclick = sayThanks; button.onclick = sayThanks(); ``` -If we add brackets, then `sayThanks()` -- will be the *result* of the function execution, so `onclick` in the last code becomes `undefined` (the function returns nothing). That won't work. +If we add parentheses, `sayThanks()` -- is a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work. -...But in the markup we do need the brackets: +...But in the markup we do need the parentheses: ```html @@ -216,7 +216,7 @@ Web-standard developers understood that long ago and suggested an alternative wa The syntax to add a handler: ```js -element.addEventListener(event, handler[, phase]); +element.addEventListener(event, handler[, options]); ``` `event` @@ -225,15 +225,17 @@ element.addEventListener(event, handler[, phase]); `handler` : The handler function. -`phase` -: An optional argument, the "phase" for the handler to work. To be covered later. Usually we don't use it. +`options` +: An additional optional object with properties: + - `once`: if `true`, then the listener is automatically removed after it triggers. + - `capture`: the phase where to handle the event, to be covered later in the chapter . For historical reasons, `options` can also be `false/true`, that's the same as `{capture: false/true}`. + - `passive`: if `true`, then the handler will not `preventDefault()`, we'll cover that later in . -To remove the handler, use `removeEventListener`: +To remove the handler, use `removeEventListener`: ```js -// exactly the same arguments as addEventListener -element.removeEventListener(event, handler[, phase]); +element.removeEventListener(event, handler[, options]); ``` ````warn header="Removal requires the same function" @@ -349,7 +351,7 @@ Some properties of `event` object: : Event type, here it's `"click"`. `event.currentTarget` -: Element that handled the event. That's exactly the same as `this`, unless you bind `this` to something else, and then `event.currentTarget` becomes useful. +: Element that handled the event. That's exactly the same as `this`, unless the handler is an arrow function, or its `this` is bound to something else, then `event.currentTarget` becomes useful. `event.clientX / event.clientY` : Window-relative coordinates of the cursor, for mouse events. diff --git a/2-ui/2-events/02-bubbling-and-capturing/article.md b/2-ui/2-events/02-bubbling-and-capturing/article.md index 287f74f2a..bae476f7e 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -108,7 +108,7 @@ Sometimes `event.stopPropagation()` creates hidden pitfalls that later may becom For instance: -1. We create a nested menu. Each submenu handles clicks on its elements and calls `stopPropagation` so that outer menu don't trigger. +1. We create a nested menu. Each submenu handles clicks on its elements and calls `stopPropagation` so that the outer menu won't trigger. 2. Later we decide to catch clicks on the whole window, to track users' behavior (where people click). Some analytic systems do that. Usually the code uses `document.addEventListener('click'…)` to catch all clicks. 3. Our analytic won't work over the area where clicks are stopped by `stopPropagation`. We've got a "dead zone". @@ -136,18 +136,22 @@ That is: for a click on `
                    ` the event first goes through the ancestors chain Handlers added using `on`-property or using HTML attributes or using `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. -To catch an event on the capturing phase, we need to set the 3rd argument of `addEventListener` to `true`. +To catch an event on the capturing phase, we need to set the handler `capture` option to `true`: -There are two possible values for that optional last argument: +```js +elem.addEventListener(..., {capture: true}) +// or, just "true" is an alias to {capture: true} +elem.addEventListener(..., true) +``` + +There are two possible values of the `capture` option: - If it's `false` (default), then the handler is set on the bubbling phase. - If it's `true`, then the handler is set on the capturing phase. Note that while formally there are 3 phases, the 2nd phase ("target phase": the event reached the element) is not handled separately: handlers on both capturing and bubbling phases trigger at that phase. -If one puts capturing and bubbling handlers on the target element, the capture handler triggers last in the capturing phase and the bubble handler triggers first in the bubbling phase. - -Let's see it in action: +Let's see both capturing and bubbling in action: ```html run autorun height=140 edit + + +``` + +Once again, `pseudo` is a non-standard attribute. Chronologically, browsers first started to experiment with internal DOM structures to implement controls, and then, after time, shadow DOM was standardized to allow us, developers, to do the similar thing. + +Further on, we'll use the modern shadow DOM standard, covered by [DOM spec](https://dom.spec.whatwg.org/#shadow-trees) other related specifications. + +## Shadow tree + +A DOM element can have two types of DOM subtrees: + +1. Light tree -- a regular DOM subtree, made of HTML children. All subtrees that we've seen in previous chapters were "light". +2. Shadow tree -- a hidden DOM subtree, not reflected in HTML, hidden from prying eyes. + +If an element has both, then the browser renders only the shadow tree. But we can setup a kind of composition between shadow and light trees as well. We'll see the details later in the chapter . + +Shadow tree can be used in Custom Elements to hide component internals and apply component-local styles. + +For example, this `` element hides its internal DOM in shadow tree: + +```html run autorun height=60 + + + +``` + +That's how the resulting DOM looks in Chrome dev tools, all the content is under "#shadow-root": + +![](shadow-dom-say-hello.png) + +First, the call to `elem.attachShadow({mode: …})` creates a shadow tree. + +There are two limitations: +1. We can create only one shadow root per element. +2. The `elem` must be either a custom element, or one of: "article", "aside", "blockquote", "body", "div", "footer", "h1..h6", "header", "main" "nav", "p", "section", or "span". Other elements, like ``, can't host shadow tree. + +The `mode` option sets the encapsulation level. It must have any of two values: +- `"open"` -- the shadow root is available as `elem.shadowRoot`. + + Any code is able to access the shadow tree of `elem`. +- `"closed"` -- `elem.shadowRoot` is always `null`. + + We can only access the shadow DOM by the reference returned by `attachShadow` (and probably hidden inside a class). Browser-native shadow trees, such as ``, are closed. There's no way to access them. + +The [shadow root](https://dom.spec.whatwg.org/#shadowroot), returned by `attachShadow`, is like an element: we can use `innerHTML` or DOM methods, such as `append`, to populate it. + +The element with a shadow root is called a "shadow tree host", and is available as the shadow root `host` property: + +```js +// assuming {mode: "open"}, otherwise elem.shadowRoot is null +alert(elem.shadowRoot.host === elem); // true +``` + +## Encapsulation + +Shadow DOM is strongly delimited from the main document: + +1. Shadow DOM elements are not visible to `querySelector` from the light DOM. In particular, Shadow DOM elements may have ids that conflict with those in the light DOM. They must be unique only within the shadow tree. +2. Shadow DOM has own stylesheets. Style rules from the outer DOM don't get applied. + +For example: + +```html run untrusted height=40 + + +
                    + + +``` + +1. The style from the document does not affect the shadow tree. +2. ...But the style from the inside works. +3. To get elements in shadow tree, we must query from inside the tree. + +## References + +- DOM: +- Compatibility: +- Shadow DOM is mentioned in many other specifications, e.g. [DOM Parsing](https://w3c.github.io/DOM-Parsing/#the-innerhtml-mixin) specifies that shadow root has `innerHTML`. + + +## Summary + +Shadow DOM is a way to create a component-local DOM. + +1. `shadowRoot = elem.attachShadow({mode: open|closed})` -- creates shadow DOM for `elem`. If `mode="open"`, then it's accessible as `elem.shadowRoot` property. +2. We can populate `shadowRoot` using `innerHTML` or other DOM methods. + +Shadow DOM elements: +- Have their own ids space, +- Invisible to JavaScript selectors from the main document, such as `querySelector`, +- Use styles only from the shadow tree, not from the main document. + +Shadow DOM, if exists, is rendered by the browser instead of so-called "light DOM" (regular children). In the chapter we'll see how to compose them. diff --git a/8-web-components/3-shadow-dom/shadow-dom-range.png b/8-web-components/3-shadow-dom/shadow-dom-range.png new file mode 100644 index 000000000..126206107 Binary files /dev/null and b/8-web-components/3-shadow-dom/shadow-dom-range.png differ diff --git a/8-web-components/3-shadow-dom/shadow-dom-range@2x.png b/8-web-components/3-shadow-dom/shadow-dom-range@2x.png new file mode 100644 index 000000000..179d132ec Binary files /dev/null and b/8-web-components/3-shadow-dom/shadow-dom-range@2x.png differ diff --git a/8-web-components/3-shadow-dom/shadow-dom-say-hello.png b/8-web-components/3-shadow-dom/shadow-dom-say-hello.png new file mode 100644 index 000000000..f99285e29 Binary files /dev/null and b/8-web-components/3-shadow-dom/shadow-dom-say-hello.png differ diff --git a/8-web-components/3-shadow-dom/shadow-dom-say-hello@2x.png b/8-web-components/3-shadow-dom/shadow-dom-say-hello@2x.png new file mode 100644 index 000000000..8f7bb2513 Binary files /dev/null and b/8-web-components/3-shadow-dom/shadow-dom-say-hello@2x.png differ diff --git a/8-web-components/4-template-element/article.md b/8-web-components/4-template-element/article.md new file mode 100644 index 000000000..25aada6fc --- /dev/null +++ b/8-web-components/4-template-element/article.md @@ -0,0 +1,116 @@ + +# Template element + +A built-in `