up
|
@ -1,49 +1,56 @@
|
||||||
# An introduction to JavaScript
|
# An introduction to JavaScript
|
||||||
|
|
||||||
Let's see what's so special about JavaScript, what we can achieve with it and what other technologies coexist with it.
|
Let's see what's so special about JavaScript, what we can achieve with it and which other technologies play well with it.
|
||||||
|
|
||||||
## What is JavaScript?
|
## What is JavaScript?
|
||||||
|
|
||||||
*JavaScript* was initially created to *"make webpages alive"*.
|
*JavaScript* was initially created to *"make webpages alive"*.
|
||||||
|
|
||||||
The programs in this language are called *scripts*. They are put directly into HTML and execute automatically as it loads.
|
The programs in this language are called *scripts*. They can be written right in the HTML and execute automatically as the page loads.
|
||||||
|
|
||||||
Scripts are provided and executed a plain text. They don't need a special preparation or compilation to run.
|
Scripts are provided and executed a plain text. They don't need a special preparation or a compilation to run.
|
||||||
|
|
||||||
In this aspect, JavaScript is very different from another language called [Java](http://en.wikipedia.org/wiki/Java).
|
In this aspect, JavaScript is very different from another language called [Java](http://en.wikipedia.org/wiki/Java).
|
||||||
|
|
||||||
[smart header="Why <u>Java</u>Script?"]
|
[smart header="Why <u>Java</u>Script?"]
|
||||||
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 language 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 altogether.
|
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.
|
||||||
|
|
||||||
It has quite a few special features that make mastering a bit hard at first, but we'll nicely deal with them later.
|
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
Since the time of its creation, JavaScript evolved.
|
At present, JavaScript can execute not only in the browser, but also on the server, or actually on any device where a special program called [an interpreter]("http://en.wikipedia.org/wiki/Interpreter_(computing)") is installed. The execution process is called "an interpretation".
|
||||||
|
|
||||||
As of now, JavaScript can execute not only in the browser, but also on the server, or actually on any device where a special program called [an interpreter]("http://en.wikipedia.org/wiki/Interpreter_(computing)") is installed. The execution process is called "an interpretation".
|
The browser has an embedded JavaScript interpreter, sometimes it's also called a "JavaScript engine" or a "JavaScript virtual machine".
|
||||||
|
|
||||||
The browser has an embedded JavaScript interpreter, of course. Sometimes it's also called a *JavaScript engine* or a "JavaScript virtual machine".
|
|
||||||
|
|
||||||
Different engines have different "codenames", for example:
|
Different engines have different "codenames", for example:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Chrome and Opera browsers and Node.JS server use [V8 engine]("https://en.wikipedia.org/wiki/V8_(JavaScript_engine)") (hence the same support for modern features).</li>
|
<li>[V8 engine]("https://en.wikipedia.org/wiki/V8_(JavaScript_engine)") -- in Chrome and Opera.</li>
|
||||||
<li>Firefox browser uses [Gecko]("https://en.wikipedia.org/wiki/Gecko_(software)").</li>
|
<li>[Gecko]("https://en.wikipedia.org/wiki/Gecko_(software)") -- in Firefox.</li>
|
||||||
<li>...There are other codenames like "Trident", "Chakra" for different versions of IE, "Nitro" and "SquirrelFish" for Safari etc.</li>
|
<li>...There are other codenames like "Trident", "Chakra" for different versions of IE, "Nitro" and "SquirrelFish" for Safari etc.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
The codenames are usually used when searching for detailed information in the internet. Also, we'll use them further to be more exact. Instead of the words "Chrome supports feature..." we'd rather say "V8 supports feature...", not just because it's more precise, but because that also implies Opera and Node.JS.
|
The codenames are good to know. They are used when searching for detailed information in the internet. Also, we'll sometimes reference them further in the tutorial. Instead of the words "Chrome supports feature..." we'd rather say "V8 supports feature...", not just because it's more precise, but because that also implies Opera and Node.JS.
|
||||||
|
|
||||||
[smart header="Compilation and interpretation"]
|
[smart header="Compilation and interpretation"]
|
||||||
There are in fact two general approaches to execute programs: "compilers" and "interpreters".
|
There are two general approaches to execute programs: "compilation" and "interpretation".
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>*Compilers* convert the program text (source code) to binary code (usually) without executing it. This is done by the developer and then the binary code is distributed to the system which actually runs it.</li>
|
<li>*Compilers* convert the program text (source code) to binary code (or kind-of) without executing it. When a developer wants to publish the program, he runs a compiler with the source code and then distributes the binary files that it produces.</li>
|
||||||
<li>*Interpreters*, and in particular the one embedded in the browser -- get the source code and execute it "as is". The source code (script) is distributed to the system as a plain text.</li>
|
<li>*Interpreters*, and in particular the one embedded in the browser -- get the source code and execute it "as is".</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Modern interpreters actually combine these approaches into one: the script is distributed as a plain text, but prior to execution is converted to the machine language. That's why JavaScript executes very fast.
|
As we can see, an interpretation is simpler. No intermediate steps involved. But a compilation is more powerful, because the binary code is more "machine-friendly" and runs faster at the end user.
|
||||||
|
|
||||||
|
Modern javascript engines actually combine these approaches into one:
|
||||||
|
<ol>
|
||||||
|
<li>The script is written and distributed as a plain text (can be compressed/optimized by so-called "javascript minifiers").</li>
|
||||||
|
<li>The engine (in-browser for the web) reads the script and converts it to the machine language. And then it runs it. That's why JavaScript executes very fast.
|
||||||
|
|
||||||
|
Even more than that, the binary code may be adjusted later, through the process of its execution. The engine learns more about the actual data that it works with and then can optimize it better.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
So the term "interpretation" is used mostly for historical reasons. We do know what there's actually a two-stage (at least) process behind it.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,36 +58,38 @@ Modern interpreters actually combine these approaches into one: the script is di
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
Other capabilities depend on the environment which runs JavaScript. For instance, Node.JS has functionality that allows JavaScript to read/write arbitrary files, perform network requests etc etc.
|
Other capabilities depend on the environment which runs JavaScript. For instance, Node.JS has functionality that allows JavaScript to read/write arbitrary files, perform network requests etc.
|
||||||
|
|
||||||
In the browser JavaScript can do everything related to webpage manipulation, interaction with the user and the webserver.
|
In the browser JavaScript can do everything related to webpage manipulation, interaction with the user and the webserver.
|
||||||
|
|
||||||
In more details, in-browser JavaScript is able to:
|
For instance, in-browser JavaScript is able to:
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Create new HTML tags, remove the existing ones, change styles, hide/show elements...</li>
|
<li>Add new HTML to the page, change the existing content, modify styles.</li>
|
||||||
<li>React on user actions, run on mouse clicks, pointer movements, key presses...</li>
|
<li>React on user actions, run on mouse clicks, pointer movements, key presses.</li>
|
||||||
<li>Send requests over the network to remote servers, download and upload data without reloading the page (a so-called "AJAX" technology)...</li>
|
<li>Send requests over the network to remote servers, download and upload data without reloading the page (a so-called "AJAX" technology).</li>
|
||||||
<li>Get and set cookies, ask for data, show messages...</li>
|
<li>Get and set cookies, prompt user for the data, show messages.</li>
|
||||||
<li>...and can actually do almost anything with the page and it's content!</li>
|
<li>Store data in-browser ("localStorage").</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
## What in-browser JavaScript can NOT do?
|
## What in-browser JavaScript can NOT do?
|
||||||
|
|
||||||
JavaScript abilities in the browser are limited. That is for user safety, mainly not to let an evil webpage access private information or harm the user's data.
|
JavaScript 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:
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>JavaScript on the 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.
|
<li>JavaScript on the 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.
|
||||||
|
|
||||||
Modern browsers allow it to work with files, but limit the access to a specially created directory called "a sandbox".
|
Modern browsers allow it to work with files, but the access is limited and only provided if the user does certain actions, like "dropping" a file into a browser window or selecting it via an `<input>` tag.
|
||||||
|
|
||||||
There are ways to interact with camera/microphone and other devices, but they require an explicit user's permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to NSA.
|
There are ways to interact with camera/microphone and other devices, but they require an explicit user's permission. So a JavaScript-enabled page may not sneakily enable a web-camera, observe the surroundings and send the information to the NSA.
|
||||||
</li>
|
</li>
|
||||||
<li>JavaScript may not freely access other pages opened in the same browser. The exception is when the pages come from the same site.
|
<li>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. Such action is allowed. But even in this case, JavaScript from one page may not access the other if they compe from different sites (from a different domain, protocol or port).
|
||||||
|
|
||||||
There are ways to workaround this, of course. But if two pages come from different sites (different domain, protocol or port), they require a special code on *both of them* allowing to interact.
|
That is called a "Same Origin Policy". To workaround that, *both pages* must contain a special JavaScript code that handles data exchange.
|
||||||
|
|
||||||
The limitation is again for user safety. A page from `http://evilsite.com` which a user has opened occasionaly will be unable to access other browser tabs and steal information from there.
|
The limitation is again for a user's safety. A page from `http://anysite.com` which a user has opened occasionaly must not be able to open or access another browser tab with the URL `http://gmail.com` and steal information from there.
|
||||||
</li>
|
</li>
|
||||||
<li>JavaScript can easily communicate over the net to the server where the current page came from. But it's ability to receive data from other sites/domains is crippled. Though possible, it requires the explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's safety limitations.
|
<li>JavaScript can easily communicate over the net to the server where the current page came from. But it's ability to receive data from other sites/domains is crippled. Though possible, it requires the explicit agreement (expressed in HTTP headers) from the remote side. Once again, that's safety limitations.
|
||||||
</li>
|
</li>
|
||||||
|
@ -89,7 +98,7 @@ The limitation is again for user safety. A page from `http://evilsite.com` which
|
||||||
<img src="limitations.png">
|
<img src="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 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 installing plugin/extensions which may get extended permissions.
|
||||||
|
|
||||||
|
|
||||||
## Why JavaScript is unique?
|
## Why JavaScript is unique?
|
||||||
|
@ -106,7 +115,7 @@ Combined, these 3 things only exist in JavaScript and no other browser technolog
|
||||||
|
|
||||||
That's what makes JavaScript unique. That's why it is the most widespread way of creating browser interfaces.
|
That's what makes JavaScript unique. That's why it is the most widespread way of creating browser interfaces.
|
||||||
|
|
||||||
Of course, there are certain trends including new languages and browser abilities. While planning to learn a new technology, it's beneficial to check it's perspectives, so we go ahead with that.
|
While planning to learn a new technology, it's beneficial to check it's perspectives. So let's move on to the modern trends that include new languages and browser abilities.
|
||||||
|
|
||||||
## HTML 5
|
## HTML 5
|
||||||
|
|
||||||
|
@ -115,7 +124,7 @@ Of course, there are certain trends including new languages and browser abilitie
|
||||||
Few examples:
|
Few examples:
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Read/write files on disk (in a "sandbox", not just any file).</li>
|
<li>Write files on disk (in a "sandbox", not to any folder).</li>
|
||||||
<li>A database embedded in the browser, to keep data on a user's computer and effeciently operate on it.</li>
|
<li>A database embedded in the browser, to keep data on a user's computer and effeciently operate on it.</li>
|
||||||
<li>Multitasking with the usage of many CPU cores in one time.</li>
|
<li>Multitasking with the usage of many CPU cores in one time.</li>
|
||||||
<li>Audio/video playback.</li>
|
<li>Audio/video playback.</li>
|
||||||
|
@ -128,9 +137,9 @@ Many new abilities are still in progress, but browsers gradually improve the sup
|
||||||
The trend: browser can do more and more, it is becoming more like an all-purpose desktop application.
|
The trend: browser can do more and more, it is becoming more like an all-purpose desktop application.
|
||||||
[/summary]
|
[/summary]
|
||||||
|
|
||||||
Still, there is a small gotcha with those "extra-fresh" modern browser abilities. Sometimes browsers try to implement them on very early stages when they are nor fully defined neither agreed upon, but still are so interesting that the developers just can't wait.
|
Still, there is a small gotcha with those "extra-fresh" modern browser abilities. Sometimes browsers try to implement them on very early stages when they are nor fully defined neither agreed upon, but are so interesting that the developers just can't wait.
|
||||||
|
|
||||||
...As the time goes, the specification matures and changes, and browsers must adapt it. That may lead to errors in the older code which was too eager to use the early browser implementation. So one should think twice before relying on things that are in draft yet.
|
...As the time goes, the specification matures and changes, and browsers must adapt it. That may lead to errors in the older code which was too eager to use the early version. So one should think twice before relying on things that are in draft yet.
|
||||||
|
|
||||||
But what's great -- eventually all browsers tend to follow the standard. There are much less differences between them now than only a couple years ago.
|
But what's great -- eventually all browsers tend to follow the standard. There are much less differences between them now than only a couple years ago.
|
||||||
|
|
||||||
|
@ -152,7 +161,7 @@ The trend: JavaScript is becoming faster, gets new syntax and language features.
|
||||||
|
|
||||||
The syntax of JavaScript does not suit everyone's needs: some people think that it's too flexible, the others consider it too limited, the third ones want to add new features absent in the standard...
|
The syntax of JavaScript does not suit everyone's needs: some people think that it's too flexible, the others consider it too limited, the third ones want to add new features absent in the standard...
|
||||||
|
|
||||||
That's normal, because projects and requirements are different for everyone. There's no a single standard for a carpenter's hammer, why should it exist for the language?
|
That's normal, because projects and requirements are different for everyone.
|
||||||
|
|
||||||
So recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run.
|
So recently a plethora of new languages appeared, which are *transpiled* (converted) to JavaScript before they run.
|
||||||
|
|
||||||
|
@ -163,7 +172,7 @@ Examples of such languages:
|
||||||
<ul>
|
<ul>
|
||||||
<li>[CoffeeScript](http://coffeescript.org/) is a "syntax sugar" for JavaScript, it introduces shorter syntax, allowing to write more precise and clear code. Usually Ruby guys like it.</li>
|
<li>[CoffeeScript](http://coffeescript.org/) is a "syntax sugar" for JavaScript, it introduces shorter syntax, allowing to write more precise and clear code. Usually Ruby guys like it.</li>
|
||||||
<li>[TypeScript](http://www.typescriptlang.org/) is concentrated on adding "strict data typing", to simplify development and support of complex systems. Developed by Microsoft.</li>
|
<li>[TypeScript](http://www.typescriptlang.org/) is concentrated on adding "strict data typing", to simplify development and support of complex systems. Developed by Microsoft.</li>
|
||||||
<li>[Dart](https://www.dartlang.org/) was offered by Google as a replacement for JavaScript, but other leading internet companies declared that they are not interested. Maybe later, we'll see. Right now it can be transpiled to JavaScript, but used less often compared to two previous alternatives.</li>
|
<li>[Dart](https://www.dartlang.org/) is a standalone language that has it's 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 browsers require it to be transpiled to JavaScript just like the ones above.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
|
@ -2,21 +2,22 @@
|
||||||
|
|
||||||
For the comfortable development we need a good code editor.
|
For the comfortable development we need a good code editor.
|
||||||
|
|
||||||
It must support at least:
|
It should support at least:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Syntax highlight.</li>
|
<li>Syntax highlight.</li>
|
||||||
<li>Autocompletion.</li>
|
<li>Autocompletion.</li>
|
||||||
<li>Folding -- hiding/opening blocks of code.</li>
|
<li>Folding -- collapsing/opening blocks of code.</li>
|
||||||
|
<li>...the more features -- the better.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
## IDE
|
## IDE
|
||||||
|
|
||||||
The term "IDE" (Integrated Development Environment) -- denotes an editor which is extended by a number of plugins, can work with additional systems, such as bugtrackering, version control and much more.
|
The term "IDE" (Integrated Development Environment) -- denotes an advanced editor which can integrate with additional systems, such as bugtrackering, version control etc.
|
||||||
|
|
||||||
Usually IDE loads the "project" and then can navigate between files, provide autocompletion based on the whole project, do other "project-level" stuff.
|
An IDE operates on a "whole project": loads it and then can navigate between files, provide autocompletion based on the whole project, do other "project-level" stuff.
|
||||||
|
|
||||||
If you haven't considered selecting an IDE, pleae look at the following variants:
|
If you haven't considered selecting an IDE, pleae look at the following variants:
|
||||||
|
|
||||||
|
@ -30,44 +31,43 @@ If you haven't considered selecting an IDE, pleae look at the following variants
|
||||||
|
|
||||||
All of them with the exception of Visual Studio are cross-platform.
|
All of them with the exception of Visual Studio are cross-platform.
|
||||||
|
|
||||||
Most IDEs are paid, but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose what's most convenient.
|
Most 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 for you.
|
||||||
|
|
||||||
## Lightweight editors
|
## Lightweight editors
|
||||||
|
|
||||||
Lightweight editors are not as powerful as IDE, but they're fast and simple.
|
"Lightweight editors" are not as powerful as IDEs, but they're fast and simple.
|
||||||
|
|
||||||
They are mainly used to instantly open and edit a file.
|
They are mainly used to instantly open and edit a file.
|
||||||
|
|
||||||
The main differenct between a "lightweight editor" and an "IDE" is that the latter works on a project-level, meaning it has to load a lot of data to start, and the former one opens just the files. That's much faster.
|
The main difference between a "lightweight editor" and an "IDE" is that the latter works on a project-level, meaning it has to load a lot of data to start, and the former one opens just the files. That's much faster.
|
||||||
|
|
||||||
In practice, "lightweight" editors may have a lot of plugins including directory-level syntax analyzers and autocompleters, so there's no strict frontier between a "lightweight editor" and an IDE. There's no point in argueing what is what.
|
In practice, "lightweight" editors may have a lot of plugins including directory-level syntax analyzers and autocompleters, so there's no strict border between a lightweight editor and an IDE. There's no point in arguing what is what.
|
||||||
|
|
||||||
The following options deserve your attention:
|
The following options deserve your attention:
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="http://www.sublimetext.com">Sublime Text</a> (cross-platform, shareware).</li>
|
<li><a href="http://www.sublimetext.com">Sublime Text</a> (cross-platform, shareware).</li>
|
||||||
<li><a href="http://www.scintilla.org/">SciTe</a> (Windows, free).</li>
|
<li><a href="https://atom.io/">Atom</a> (cross-platform, free).</li>
|
||||||
<li><a href="http://sourceforge.net/projects/notepad-plus/">Notepad++</a> (Windows, free).</li>
|
<li><a href="http://sourceforge.net/projects/notepad-plus/">Notepad++</a> (Windows, free).</li>
|
||||||
<li>Vim, Emacs are cool. If you know how to use them.</li>
|
<li>Vim, Emacs are cool. If you know how to use them.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
## My editors
|
## My favorites
|
||||||
|
|
||||||
I believe one should have both an IDE for projects and a lightweight editor for quick-n-fast file editing.
|
I believe one should have both an IDE for projects and a lightweight editor for quick and easy file editing.
|
||||||
|
|
||||||
I'm using:
|
I'm using:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Jetbrains editors as IDE: [WebStorm](http://www.jetbrains.com/webstorm/) for JS and if I have other language in the project, then [PHPStorm (PHP)](http://www.jetbrains.com/phpstorm/), [IDEA (Java)](http://www.jetbrains.com/idea/), [RubyMine (Ruby)](http://www.jetbrains.com/ruby/). These guys provide editors for other languages too, but I didn't use them.</li>
|
<li>[WebStorm](http://www.jetbrains.com/webstorm/) for JS, and if there is one more language in the project, then I switch to other Jetbrains editors like [PHPStorm](http://www.jetbrains.com/phpstorm/) (PHP), [IDEA](http://www.jetbrains.com/idea/) (Java), [RubyMine](http://www.jetbrains.com/ruby/) (Ruby). There are editors for other languages too, but I didn't use them.</li>
|
||||||
<li>As a lightweight editor -- <a href="http://www.sublimetext.com">Sublime Text</a>.</li>
|
<li>As a lightweight editor -- <a href="http://www.sublimetext.com">Sublime Text</a>.</li>
|
||||||
<li>Visual Studio, sometimes very rarely if that's a .NET project (Win).</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
If you don't know what to choose -- you can consider those.
|
If you don't know what to choose -- you can consider these ones.
|
||||||
|
|
||||||
## Let's not argue
|
## Let's not argue
|
||||||
|
|
||||||
The editors listed above are those that me or my friends -- good developers use for a long time and happy with them.
|
The editors in the lists above are those that me or my friends -- good developers use for a long time and are happy with.
|
||||||
|
|
||||||
There are of course other great editors, please choose the one you like the most.
|
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, habbits, personal preferences.
|
The choice of an editor, like any other tool, is individual and depends on your projects, habbits, personal preferences.
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# Developer console
|
# Developer console
|
||||||
|
|
||||||
As the last step before we start developing, let's learn the basics of developer console.
|
And the last step before we start developing...
|
||||||
|
|
||||||
A code is error-prone. You are quite likely to have errors... Oh what I'm talking? You are *absolutely* going to make errors, if you're a human, not a [robot]("https://en.wikipedia.org/wiki/Bender_(Futurama)").
|
A code is error-prone. You are quite likely to have errors... Oh what I'm talking? You are *absolutely* going to make errors, if you're a human, not a [robot]("https://en.wikipedia.org/wiki/Bender_(Futurama)").
|
||||||
|
|
||||||
In browser, visitors don't see the errors by default. So, if something goes wrong, we won't see what's broken and can't fix it.
|
So let's grasp the basics of developer console.
|
||||||
|
|
||||||
|
In 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.
|
||||||
|
|
||||||
To see errors and get a lot of other useful information about scripts, browsers have embedded "developer tools".
|
To see errors and get a lot of other useful information about scripts, browsers have embedded "developer tools".
|
||||||
|
|
||||||
|
@ -12,7 +14,7 @@ To see errors and get a lot of other useful information about scripts, browsers
|
||||||
|
|
||||||
Other browsers also provide developer tools, but are usually in a "catching-up" position, compared to Chrome/Firefox which are the best.
|
Other browsers also provide developer tools, but are usually in a "catching-up" position, compared to Chrome/Firefox which are the best.
|
||||||
|
|
||||||
If there is an error in Internet Explorer only, then we can use it's developer tools, but usually -- Chrome/Firefox.
|
If there is an error in the certain browser only, then we can use it's developer tools, but usually -- Chrome/Firefox.
|
||||||
|
|
||||||
Developer tools are really powerful, there are many features, but on this stage let's just look how to open them, look at errors and run JavaScript commands.
|
Developer tools are really powerful, there are many features, but on this stage let's just look how to open them, look at errors and run JavaScript commands.
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ Developer tools are really powerful, there are many features, but on this stage
|
||||||
|
|
||||||
Open the page [bug.html](bug.html).
|
Open the page [bug.html](bug.html).
|
||||||
|
|
||||||
There's an error in the JavaScript code on it. An ordinary visitor won't see it, we need t open developer tools for that.
|
There's an error in the JavaScript code on it. An ordinary visitor won't see it, so let's open developer tools.
|
||||||
|
|
||||||
Press the key [key F12] or, if you're on Mac, then [key Cmd+Opt+J].
|
Press the key [key F12] or, if you're on Mac, then [key Cmd+Opt+J].
|
||||||
|
|
||||||
|
@ -39,7 +41,7 @@ The exact look depends on your Chrome version. It changes from time to time, but
|
||||||
<li>On the right, there is a clickable link to the source `bug.html:12` with the line number where the error has occured.</li>
|
<li>On the right, there is a clickable link to the source `bug.html:12` with the line number where the error has occured.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
In that very Console tab next to the blue `>` symbol, we can also type JavaScript commands and press enter to run them ([key Shift+Enter] to input multiline commands).
|
Below the error message there is a blue `>` symbol. It marks a "command line" where we can type JavaScript commands and press enter to run them ([key Shift+Enter] to input multiline commands).
|
||||||
|
|
||||||
Now we can see errors and that's enough for the start. We'll be back to developer tools later and cover debugging more in-depth in the chapter [](/debugging-chrome).
|
Now we can see errors and that's enough for the start. We'll be back to developer tools later and cover debugging more in-depth in the chapter [](/debugging-chrome).
|
||||||
|
|
||||||
|
@ -47,11 +49,11 @@ Now we can see errors and that's enough for the start. We'll be back to develope
|
||||||
|
|
||||||
For Safari, we need to enable the "Develop menu" first.
|
For Safari, we need to enable the "Develop menu" first.
|
||||||
|
|
||||||
It is done on the "Advanced" pane of the preferences:
|
There's a checkbox for that at the bottom of the "Advanced" pane of the preferences:
|
||||||
|
|
||||||
<img src="safari.png">
|
<img src="safari.png">
|
||||||
|
|
||||||
Now [key Cmd+Opt+C] can toggle the console. Also the new top menu item has appeared with many useful options.
|
Now [key Cmd+Opt+C] can toggle the console. Also note that the new top menu item has appeared with many useful options.
|
||||||
|
|
||||||
## Other browsers
|
## Other browsers
|
||||||
|
|
||||||
|
@ -62,7 +64,7 @@ The look & feel of them is quite similar, once we know how to use one of them (c
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Developer tools allow us to see errors (crucial now), run commands, examine variables and much more.</li>
|
<li>Developer tools allow us to see errors, run commands, examine variables and much more.</li>
|
||||||
<li>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).
|
<li>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).
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
# ES-2015 сейчас
|
|
||||||
|
|
||||||
[Стандарт ES-2015](http://www.ecma-international.org/publications/standards/Ecma-262.htm) был принят в июне 2015. Пока что большинство браузеров реализуют его частично, текущее состояние реализации различных возможностей можно посмотреть здесь: [](https://kangax.github.io/compat-table/es6/).
|
|
||||||
|
|
||||||
Когда стандарт будет более-менее поддерживаться во всех браузерах, то весь учебник будет обновлён в соответствии с ним. Пока же, как центральное место для "сбора" современных фич JavaScript, создан этот раздел.
|
|
||||||
|
|
||||||
Чтобы писать код на ES-2015 прямо сейчас, есть следующие варианты.
|
|
||||||
|
|
||||||
## Конкретный движок JS
|
|
||||||
|
|
||||||
Самое простое -- это когда нужен один конкретный движок JS, например V8 (Chrome).
|
|
||||||
|
|
||||||
Тогда можно использовать только то, что поддерживается именно в нём. Заметим, что в V8 большинство возможностей ES-2015 поддерживаются только при включённом `use strict`.
|
|
||||||
|
|
||||||
При разработке на Node.JS обычно так и делают. Если же нужна кросс-браузерная поддержка, то этот вариант не подойдёт.
|
|
||||||
|
|
||||||
## Babel.JS
|
|
||||||
|
|
||||||
[Babel.JS](https://babeljs.io) -- это [транспайлер](https://en.wikipedia.org/wiki/Source-to-source_compiler), переписывающий код на ES-2015 в код на предыдущем стандарте ES5.
|
|
||||||
|
|
||||||
Он состоит из двух частей:
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Собственно транспайлер, который переписывает код.</li>
|
|
||||||
<li>Полифилл, который добавляет методы `Array.from`, `String.prototype.repeat` и другие.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
На странице [](https://babeljs.io/repl/) можно поэкспериментировать с транспайлером: слева вводится код в ES-2015, а справа появляется результат его преобразования в ES5.
|
|
||||||
|
|
||||||
Обычно Babel.JS работает на сервере в составе системы сборки JS-кода (например [webpack](http://webpack.github.io/) или [brunch](http://brunch.io/)) и автоматически переписывает весь код в ES5.
|
|
||||||
|
|
||||||
Настройка такой конвертации тривиальна, единственно -- нужно поднять саму систему сборки, а добавить к ней Babel легко, плагины есть к любой из них.
|
|
||||||
|
|
||||||
Если же хочется "поиграться", то можно использовать и браузерный вариант Babel.
|
|
||||||
|
|
||||||
Это выглядит так:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!--+ run -->
|
|
||||||
*!*
|
|
||||||
<!-- browser.js лежит на моём сервере, не надо брать с него -->
|
|
||||||
<script src="https://js.cx/babel-core/browser.min.js"></script>
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
<script type="text/babel">
|
|
||||||
let arr = ["hello", 2]; // let
|
|
||||||
|
|
||||||
let [str, times] = arr; // деструктуризация
|
|
||||||
|
|
||||||
alert( str.repeat(times) ); // hellohello, метод repeat
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
Сверху подключается браузерный скрипт `browser.min.js` из пакета Babel. Он включает в себя полифилл и транспайлер. Далее он автоматически транслирует и выполняет скрипты с `type="text/babel"`.
|
|
||||||
|
|
||||||
Размер `browser.min.js` превышает 1 мегабайт, поэтому такое использование в production строго не рекомендуется.
|
|
||||||
|
|
||||||
# Примеры на этом сайте
|
|
||||||
|
|
||||||
[warn header="Только при поддержке браузера"]
|
|
||||||
Запускаемые примеры с ES-2015 будут работать только если ваш браузер поддерживает соответствующую возможность стандарта.
|
|
||||||
[/warn]
|
|
||||||
|
|
||||||
Это означает, что при запуске примеров в браузере, который их не поддерживает, будет ошибка. Это не означает, что пример неправильный! Просто пока нет поддержки...
|
|
||||||
|
|
||||||
Рекомендуется [Chrome Canary](https://www.google.com/chrome/browser/canary.html) большинство примеров в нём работает. [Firefox Developer Edition](https://www.mozilla.org/en-US/firefox/channel/#developer) тоже неплох в поддержке современного стандарта, но на момент написания этого текста переменные [let](/let-const) работают только при указании `version=1.7` в типе скрипта: `<script type="application/javascript;version=1.7">`. Надеюсь, скоро это требование (`version=1.7`) отменят.
|
|
||||||
|
|
||||||
Впрочем, если пример в браузере не работает (обычно проявляется как ошибка синтаксиса) -- почти все примеры вы можете запустить его при помощи Babel, на странице [Babel: try it out](https://babeljs.io/repl/). Там же увидите и преобразованный код.
|
|
||||||
|
|
||||||
На практике для кросс-браузерности всё равно используют Babel.
|
|
||||||
|
|
||||||
Ещё раз заметим, что самая актуальная ситуация по поддержке современного стандарта браузерами и транспайлерами отражена на странице [](https://kangax.github.io/compat-table/es6/).
|
|
||||||
|
|
||||||
Итак, поехали!
|
|
||||||
|
|
|
@ -1,284 +0,0 @@
|
||||||
|
|
||||||
# Set, Map, WeakSet и WeakMap
|
|
||||||
|
|
||||||
В ES-2015 появились новые типы коллекций в JavaScript: `Set`, `Map`, `WeakSet` и `WeakMap`.
|
|
||||||
|
|
||||||
|
|
||||||
## Map
|
|
||||||
|
|
||||||
`Map` -- коллекция для хранения записей вида `ключ:значение`.
|
|
||||||
|
|
||||||
В отличие от объектов, в которых ключами могут быть только строки, в `Map` ключом может быть произвольное значение, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let map = new Map();
|
|
||||||
|
|
||||||
map.set('1', 'str1'); // ключ-строка
|
|
||||||
map.set(1, 'num1'); // число
|
|
||||||
map.set(true, 'bool1'); // булевое значение
|
|
||||||
|
|
||||||
// в обычном объекте это было бы одно и то же,
|
|
||||||
// map сохраняет тип ключа
|
|
||||||
alert( map.get(1) ); // 'num1'
|
|
||||||
alert( map.get('1') ); // 'str1'
|
|
||||||
|
|
||||||
alert( map.size ); // 3
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно из примера выше, для сохранения и чтения значений используются методы `get` и `set`. И ключи и значения сохраняются "как есть", без преобразований типов.
|
|
||||||
|
|
||||||
Свойство `map.size` хранит общее количество записей в `map`.
|
|
||||||
|
|
||||||
Метод `set` можно чейнить:
|
|
||||||
|
|
||||||
```js
|
|
||||||
map
|
|
||||||
.set('1', 'str1')
|
|
||||||
.set(1, 'num1')
|
|
||||||
.set(true, 'bool1');
|
|
||||||
```
|
|
||||||
|
|
||||||
При создании `Map` можно сразу инициализовать списком значений.
|
|
||||||
|
|
||||||
Объект `map` с тремя ключами, как и в примере выше:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let map = new Map([
|
|
||||||
['1', 'str1'],
|
|
||||||
[1, 'num1'],
|
|
||||||
[true, 'bool1']
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
Аргументом `new Map` должен быть итерируемый объект (не обязательно именно массив). Везде утиная типизация, максимальная гибкость.
|
|
||||||
|
|
||||||
**В качестве ключей `map` можно использовать и объекты:**
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let user = { name: "Вася" };
|
|
||||||
|
|
||||||
// для каждого пользователя будем хранить количество посещений
|
|
||||||
let visitsCountMap = new Map();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// объект user является ключом в visitsCountMap
|
|
||||||
visitsCountMap.set(user, 123);
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( visitsCountMap.get(user) ); // 123
|
|
||||||
```
|
|
||||||
|
|
||||||
[smart header="Как map сравнивает ключи"]
|
|
||||||
Для проверки значений на эквивалентность используется алгоритм [SameValueZero](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevaluezero). Он аналогичен строгому равенству `===`, отличие -- в том, что `NaN` считается равным `NaN`. Поэтому значение `NaN` также может быть использовано в качестве ключа.
|
|
||||||
|
|
||||||
Этот алгоритм жёстко фиксирован в стандарте, его нельзя изменять или задавать свою функцию сравнения.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
Методы для удаления записей:
|
|
||||||
<ul>
|
|
||||||
<li>`map.delete(key)` удаляет запись с ключом `key`, возвращает `true`, если такая запись была, иначе `false`.</li>
|
|
||||||
<li>`map.clear()` -- удаляет все записи, очищает `map`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Для проверки существования ключа:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>`map.has(key)` -- возвращает `true`, если ключ есть, иначе `false`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
### Итерация
|
|
||||||
|
|
||||||
Для итерации по `map` используется один из трёх методов:
|
|
||||||
<ul>
|
|
||||||
<li>`map.keys()` -- возвращает итерируемый объект для ключей,</li>
|
|
||||||
<li>`map.values()` -- возвращает итерируемый объект для значений,</li>
|
|
||||||
<li>`map.entries()` -- возвращает итерируемый объект для записей `[ключ, значение]`, он используется по умолчанию в `for..of`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let recipeMap = new Map([
|
|
||||||
['огурцов', '500 гр'],
|
|
||||||
['помидоров', '350 гр'],
|
|
||||||
['сметаны', '50 гр']
|
|
||||||
]);
|
|
||||||
|
|
||||||
// цикл по ключам
|
|
||||||
for(let fruit of recipeMap.keys()) {
|
|
||||||
alert(fruit); // огурцов, помидоров, сметаны
|
|
||||||
}
|
|
||||||
|
|
||||||
// цикл по значениям [ключ,значение]
|
|
||||||
for(let amount of recipeMap.values()) {
|
|
||||||
alert(amount); // 500 гр, 350 гр, 50 гр
|
|
||||||
}
|
|
||||||
|
|
||||||
// цикл по записям
|
|
||||||
for(let entry of recipeMap) { // то же что и recipeMap.entries()
|
|
||||||
alert(entry); // огурцов,500 гр , и т.д., массивы по 2 значения
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[smart header="Перебор идёт в том же порядке, что и вставка"]
|
|
||||||
Перебор осуществляется в порядке вставки. Объекты `Map` гарантируют это, в отличие от обычных объектов `Object`.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
Кроме того, у `Map` есть стандартный метод `forEach`, аналогичный массиву:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let recipeMap = new Map([
|
|
||||||
['огурцов', '500 гр'],
|
|
||||||
['помидоров', '350 гр'],
|
|
||||||
['сметаны', '50 гр']
|
|
||||||
]);
|
|
||||||
|
|
||||||
recipeMap.forEach( (value, key, map) => {
|
|
||||||
alert(`${key}: ${value}`); // огурцов: 500 гр, и т.д.
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Set
|
|
||||||
|
|
||||||
`Set` -- коллекция для хранения множества значений, причём каждое значение может встречаться лишь один раз.
|
|
||||||
|
|
||||||
Например, к нам приходят посетители, и мы хотели бы сохранять всех, кто пришёл. При этом повторные визиты не должны приводить к дубликатам, то есть каждого посетителя нужно "посчитать" ровно один раз.
|
|
||||||
|
|
||||||
`Set` для этого отлично подходит:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let set = new Set();
|
|
||||||
|
|
||||||
let vasya = {name: "Вася"};
|
|
||||||
let petya = {name: "Петя"};
|
|
||||||
let dasha = {name: "Даша"};
|
|
||||||
|
|
||||||
// посещения, некоторые пользователи заходят много раз
|
|
||||||
set.add(vasya);
|
|
||||||
set.add(petya);
|
|
||||||
set.add(dasha);
|
|
||||||
set.add(vasya);
|
|
||||||
set.add(petya);
|
|
||||||
|
|
||||||
// set сохраняет только уникальные значения
|
|
||||||
alert( set.size ); // 3
|
|
||||||
|
|
||||||
set.forEach( user => alert(user.name ) ); // Вася, Петя, Даша
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше многократные добавления одного и того же объекта в `set` не создают лишних копий.
|
|
||||||
|
|
||||||
Альтернатива `Set` -- это массивы с поиском дубликата при каждом добавлении, но они гораздо хуже по производительности. Или можно использовать обычные объекты, где в качестве ключа выступает какой-нибудь уникальный идентификатор посетителя. Но это менее удобно, чем простой и наглядный `Set`.
|
|
||||||
|
|
||||||
Основные методы:
|
|
||||||
<ul>
|
|
||||||
<li>`set.add(item)` -- добавляет в коллекцию `item`, возвращает `set` (чейнится).</li>
|
|
||||||
<li>`set.delete(item)` -- удаляет `item` из коллекции, возвращает `true`, если он там был, иначе `false`.</li>
|
|
||||||
<li>`set.has(item)` -- возвращает `true`, если `item` есть в коллекции, иначе `false`.</li>
|
|
||||||
<li>`set.clear()` -- очищает `set`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Перебор `Set` осуществляется через `forEach` или `for..of` аналогично `Map`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let set = new Set(["апельсины", "яблоки", "бананы"]);
|
|
||||||
|
|
||||||
// то же, что: for(let value of set)
|
|
||||||
set.forEach((value, valueAgain, set) => {
|
|
||||||
alert(value); // апельсины, затем яблоки, затем бананы
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, что в `Set` у функции в `.forEach` три аргумента: значение, ещё раз значение, и затем сам перебираемый объект `set`. При этом значение повторяется в аргументах два раза.
|
|
||||||
|
|
||||||
Так сделано для совместимости с `Map`, где у `.forEach`-функции также три аргумента. Но в `Set` первые два всегда совпадают и содержат очередное значение множества.
|
|
||||||
|
|
||||||
## WeakMap и WeakSet
|
|
||||||
|
|
||||||
`WeakSet` -- особый вид `Set` не препятствующий сборщику мусора удалять свои элементы. То же самое -- `WeakMap` для `Map`.
|
|
||||||
|
|
||||||
То есть, если некий объект присутствует только в `WeakSet/WeakMap` -- он удаляется из памяти.
|
|
||||||
|
|
||||||
Это нужно для тех ситуаций, когда основное место для хранения и использования объектов находится где-то в другом месте кода, а здесь мы хотим хранить для них "вспомогательные" данные, существующие лишь пока жив объект.
|
|
||||||
|
|
||||||
Например, у нас есть элементы на странице или, к примеру, пользователи, и мы хотим хранить для них вспомогательную инфомацию, например обработчики событий или просто данные, но действительные лишь пока объект, к которому они относятся, существует.
|
|
||||||
|
|
||||||
Если поместить такие данные в `WeakMap`, а объект сделать ключом, то они будут автоматически удалены из памяти, когда удалится элемент.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// текущие активные пользователи
|
|
||||||
let activeUsers = [
|
|
||||||
{name: "Вася"},
|
|
||||||
{name: "Петя"},
|
|
||||||
{name: "Маша"}
|
|
||||||
];
|
|
||||||
|
|
||||||
// вспомогательная информация о них,
|
|
||||||
// которая напрямую не входит в объект юзера,
|
|
||||||
// и потому хранится отдельно
|
|
||||||
let weakMap = new WeakMap();
|
|
||||||
|
|
||||||
weakMap[activeUsers[0]] = 1;
|
|
||||||
weakMap[activeUsers[1]] = 2;
|
|
||||||
weakMap[activeUsers[2]] = 3;
|
|
||||||
|
|
||||||
alert( weakMap[activeUsers[0]] ); // 1
|
|
||||||
|
|
||||||
activeUsers.splice(0, 1); // Вася более не активный пользователь
|
|
||||||
|
|
||||||
// weakMap теперь содержит только 2 элемента
|
|
||||||
|
|
||||||
activeUsers.splice(0, 1); // Петя более не активный пользователь
|
|
||||||
|
|
||||||
// weakMap теперь содержит только 1 элемент
|
|
||||||
```
|
|
||||||
|
|
||||||
Таким образом, `WeakMap` избавляет нас от необходимости вручную удалять вспомогательные данные, когда удалён основной объект.
|
|
||||||
|
|
||||||
У WeakMap есть ряд ограничений:
|
|
||||||
<ul>
|
|
||||||
<li>Нет свойства `size`.</li>
|
|
||||||
<li>Нельзя перебрать элементы итератором или `forEach`.</li>
|
|
||||||
<li>Нет метода `clear()`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Иными словами, `WeakMap` работает только на запись (`set`, `delete`) и чтение (`get`, `has`) элементов по конкретному ключу, а не как полноценная коллекция. Нельзя вывести всё содержимое `WeakMap`, нет соответствующих методов.
|
|
||||||
|
|
||||||
Это связано с тем, что содержимое `WeakMap` может быть модифицировано сборщиком мусора в любой момент, независимо от программиста. Сборщик мусора работает сам по себе. Он не гарантирует, что очистит объект сразу же, когда это стало возможным. В равной степени он не гарантирует и обратное. Нет какого-то конкретного момента, когда такая очистка точно произойдёт -- это определяется внутренними алгоритмами сборщика и его сведениями о системе.
|
|
||||||
|
|
||||||
Поэтому содержимое `WeakMap` в произвольный момент, строго говоря, не определено. Может быть, сборщик мусора уже удалил какие-то записи, а может и нет. С этим, а также с требованиями к эффективной реализации `WeakMap`, и связано отсутствие методов, осуществляющих доступ ко всем записям.
|
|
||||||
|
|
||||||
То же самое относится и к `WeakSet`: можно добавлять элементы, проверять их наличие, но нельзя получить их список и даже узнать количество.
|
|
||||||
|
|
||||||
Эти ограничения могут показаться неудобными, но по сути они не мешают `WeakMap/WeakSet` выполнять свою основную задачу -- быть "вторичным" хранилищем данных для объектов, актуальный список которых (и сами они) хранятся в каком-то другом месте.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>`Map` -- коллекция записей вида `ключ: значение`, лучше `Object` тем, что перебирает всегда в порядке вставки и допускает любые ключи.</li>
|
|
||||||
<li>`Set` -- коллекция уникальных элементов, также допускает любые ключи.
|
|
||||||
<li>`WeakMap` и `WeakSet` -- "урезанные" по функционалу варианты `Map/Set`, которые позволяют только "точечно" обращаться элементам (по конкретному ключу или значению). Они не препятствуют сборке мусора, то есть если ссылка на объект осталась только в `WeakSet/WeakMap` -- он будет удалён.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
```js
|
|
||||||
function delay(ms) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
setTimeout(resolve, ms);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
|
@ -1,27 +0,0 @@
|
||||||
|
|
||||||
# Промисифицировать setTimeout
|
|
||||||
|
|
||||||
Напишите функцию `delay(ms)`, которая возвращает промис, переходящий в состояние `"resolved"` через `ms` миллисекунд.
|
|
||||||
|
|
||||||
Пример использования:
|
|
||||||
```js
|
|
||||||
delay(1000)
|
|
||||||
.then(() => alert("Hello!"))
|
|
||||||
```
|
|
||||||
|
|
||||||
Такая полезна для использования в других промис-цепочках.
|
|
||||||
|
|
||||||
Вот такой вызов:
|
|
||||||
```js
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
doSomeThing();
|
|
||||||
resolve();
|
|
||||||
}, ms)
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Станет возможным переписать так:
|
|
||||||
```js
|
|
||||||
return delay(ms).then(doSomething);
|
|
||||||
```
|
|
|
@ -1,27 +0,0 @@
|
||||||
Для последовательной загрузки нужно организовать промисы в цепочку, чтобы они выполнялись строго -- один после другого.
|
|
||||||
|
|
||||||
Вот код, который это делает:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// начало цепочки
|
|
||||||
let chain = Promise.resolve();
|
|
||||||
|
|
||||||
let results = [];
|
|
||||||
|
|
||||||
// в цикле добавляем задачи в цепочку
|
|
||||||
urls.forEach(function(url) {
|
|
||||||
chain = chain
|
|
||||||
.then(() => httpGet(url))
|
|
||||||
.then((result) => {
|
|
||||||
results.push(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// в конце — выводим результаты
|
|
||||||
chain.then(() => {
|
|
||||||
alert(results);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Использование `Promise.resolve()` как начала асинхронной цепочки -- очень распространённый приём.
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "guest",
|
|
||||||
"isAdmin": false
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
function httpGet(url) {
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', url, true);
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (this.status == 200) {
|
|
||||||
resolve(this.response);
|
|
||||||
} else {
|
|
||||||
var error = new Error(this.statusText);
|
|
||||||
error.code = this.status;
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function() {
|
|
||||||
reject(new Error("Network Error"));
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<script src="httpGet.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let urls = ['guest.json', 'user.json'];
|
|
||||||
|
|
||||||
let chain = Promise.resolve();
|
|
||||||
|
|
||||||
let results = [];
|
|
||||||
|
|
||||||
urls.forEach(function(url) {
|
|
||||||
chain = chain
|
|
||||||
.then(() => httpGet(url))
|
|
||||||
.then((result) => {
|
|
||||||
results.push(result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
chain.then(() => {
|
|
||||||
alert(results);
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "iliakan",
|
|
||||||
"isAdmin": true
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "guest",
|
|
||||||
"isAdmin": false
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
function httpGet(url) {
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', url, true);
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (this.status == 200) {
|
|
||||||
resolve(this.response);
|
|
||||||
} else {
|
|
||||||
var error = new Error(this.statusText);
|
|
||||||
error.code = this.status;
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function() {
|
|
||||||
reject(new Error("Network Error"));
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<script src="httpGet.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let urls = ['guest.json', 'user.json'];
|
|
||||||
|
|
||||||
// ... ваш код
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "iliakan",
|
|
||||||
"isAdmin": true
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
|
|
||||||
# Загрузить массив последовательно
|
|
||||||
|
|
||||||
Есть массив URL:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let urls = [
|
|
||||||
'user.json',
|
|
||||||
'guest.json'
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
Напишите код, который все URL из этого массива загружает -- один за другим (последовательно), и сохраняет в результаты в массиве `results`, а потом выводит.
|
|
||||||
|
|
||||||
Вариант с параллельной загрузкой выглядел бы так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Promise.all( urls.map(httpGet) )
|
|
||||||
.then(alert);
|
|
||||||
```
|
|
||||||
|
|
||||||
В этой задаче загрузку нужно реализовать последовательно.
|
|
||||||
|
|
Before Width: | Height: | Size: 30 KiB |
|
@ -1,843 +0,0 @@
|
||||||
# Promise
|
|
||||||
|
|
||||||
Promise (обычно их так и называют "промисы") -- предоставляют удобный способ организации асинхронного кода.
|
|
||||||
|
|
||||||
В современном JavaScript промисы часто используются в том числе и неявно, при помощи генераторов, но об этом чуть позже.
|
|
||||||
|
|
||||||
## Что такое Promise?
|
|
||||||
|
|
||||||
Promise -- это специальный объект, который содержит своё состояние. Вначале `pending` ("ожидание"), затем -- одно из: `fulfilled` ("выполнено успешно") или `rejected` ("выполнено с ошибкой").
|
|
||||||
|
|
||||||
<img src="promiseInit.png">
|
|
||||||
|
|
||||||
На `promise` можно навешивать коллбэки двух типов:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>`onFulfilled` -- срабатывают, когда `promise` в состоянии "выполнен успешно".</li>
|
|
||||||
<li>`onRejected` -- срабатывают, когда `promise` в состоянии "выполнен с ошибкой".</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Способ использования, в общих чертах, такой:
|
|
||||||
<ol>
|
|
||||||
<li>Код, которому надо сделать что-то асинхронно, создаёт объект `promise` и возвращает его.</li>
|
|
||||||
<li>Внешний код, получив `promise`, навешивает на него обработчики.</li>
|
|
||||||
<li>По завершении процесса асинхронный код переводит `promise` в состояние `fulfilled` (с результатом) или `rejected` (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Синтаксис создания `Promise`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var promise = new Promise(function(resolve, reject) {
|
|
||||||
// Эта функция будет вызвана автоматически
|
|
||||||
|
|
||||||
// В ней можно делать любые асинхронные операции,
|
|
||||||
// А когда они завершатся — нужно вызвать одно из:
|
|
||||||
// resolve(результат) при успешном выполнении
|
|
||||||
// reject(ошибка) при ошибке
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Универсальный метод для навешивания обработчиков:
|
|
||||||
|
|
||||||
```js
|
|
||||||
promise.then(onFulfilled, onRejected)
|
|
||||||
```
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>`onFulfilled` -- функция, которая будет вызвана с результатом при `resolve`.</li>
|
|
||||||
<li>`onRejected` -- функция, которая будет вызвана с ошибкой при `reject`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
С его помощью можно назначить как оба обработчика сразу, так и только один:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// onFulfilled сработает при успешном выполнении
|
|
||||||
promise.then(onFulfilled)
|
|
||||||
// onRejected сработает при ошибке
|
|
||||||
promise.then(null, onRejected)
|
|
||||||
```
|
|
||||||
|
|
||||||
[smart header=".catch"]
|
|
||||||
Для того, чтобы поставить обработчик только на ошибку, вместо `.then(null, onRejected)` можно написать `.catch(onRejected)` -- это то же самое.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
[smart header="Синхронный `throw` -- то же самое, что `reject`"]
|
|
||||||
Если в функции промиса происходит синхронный `throw` (или иная ошибка), то вызывается `reject`:
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let p = new Promise((resolve, reject) => {
|
|
||||||
// то же что reject(new Error("o_O"))
|
|
||||||
throw new Error("o_O");
|
|
||||||
})
|
|
||||||
|
|
||||||
p.catch(alert); // Error: o_O
|
|
||||||
```
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
Посмотрим, как это выглядит вместе, на простом примере.
|
|
||||||
|
|
||||||
|
|
||||||
## Пример с setTimeout
|
|
||||||
|
|
||||||
Возьмём `setTimeout` в качестве асинхронной операции, которая должна через некоторое время успешно завершиться с результатом "result":
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Создаётся объект promise
|
|
||||||
let promise = new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
// переведёт промис в состояние fulfilled с результатом "result"
|
|
||||||
resolve("result");
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// promise.then навешивает обработчики на успешный результат или ошибку
|
|
||||||
promise
|
|
||||||
.then(
|
|
||||||
result => {
|
|
||||||
// первая функция-обработчик - запустится при вызове resolve
|
|
||||||
alert("Fulfilled: " + result); // result - аргумент resolve
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
// вторая функция - запустится при вызове reject
|
|
||||||
alert("Rejected: " + error); // error - аргумент reject
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
В результате запуска кода выше -- через 1 секунду выведется "Fulfilled: result".
|
|
||||||
|
|
||||||
А если бы вместо `resolve("result")` был вызов `reject("error")`, то вывелось бы "Rejected: error". Впрочем, как правило, если при выполнении возникла проблема, то `reject` вызывают не со строкой, а с объектом ошибки типа `new Error`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
// Этот promise завершится с ошибкой через 1 секунду
|
|
||||||
var promise = new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
*!*
|
|
||||||
reject(new Error("время вышло!"));
|
|
||||||
*/!*
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
promise
|
|
||||||
.then(
|
|
||||||
result => alert("Fulfilled: " + result),
|
|
||||||
*!*
|
|
||||||
error => alert("Rejected: " + error.message) // Rejected: время вышло!
|
|
||||||
*/!*
|
|
||||||
);
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Конечно, вместо `setTimeout` внутри функции промиса может быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс. Главное, чтобы по своему завершению он вызвал `resolve` или `reject`, которые передадут результат обработчикам.
|
|
||||||
|
|
||||||
[smart header="Только один аргумент"]
|
|
||||||
Функции `resolve/reject` принимают ровно один аргумент -- результат/ошибку.
|
|
||||||
|
|
||||||
Именно он передаётся обработчикам в `.then`, как можно видеть в примерах выше.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
## Promise после reject/resolve -- неизменны
|
|
||||||
|
|
||||||
Заметим, что после вызова `resolve/reject` промис уже не может "передумать".
|
|
||||||
|
|
||||||
Когда промис переходит в состояние "выполнен" -- с результатом (resolve) или ошибкой (reject) -- это навсегда.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let promise = new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// через 1 секунду готов результат: result
|
|
||||||
*/!*
|
|
||||||
setTimeout(() => resolve("result"), 1000);
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// через 2 секунды — reject с ошибкой, он будет проигнорирован
|
|
||||||
*/!*
|
|
||||||
setTimeout(() => reject(new Error("ignored")), 2000);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
promise
|
|
||||||
.then(
|
|
||||||
result => alert("Fulfilled: " + result), // сработает
|
|
||||||
error => alert("Rejected: " + error) // не сработает
|
|
||||||
);
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
В результате вызова этого кода сработает только первый обработчик `then`, так как после вызова `resolve` промис уже получил состояние (с результатом), и в дальнейшем его уже ничто не изменит.
|
|
||||||
|
|
||||||
Последующие вызовы resolve/reject будут просто проигнороированы.
|
|
||||||
|
|
||||||
А так -- наоборот, ошибка будет раньше:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let promise = new Promise((resolve, reject) => {
|
|
||||||
|
|
||||||
// reject вызван раньше, resolve будет проигнорирован
|
|
||||||
setTimeout(() => reject(new Error("error")), 1000);
|
|
||||||
|
|
||||||
setTimeout(() => resolve("ignored"), 2000);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
promise
|
|
||||||
.then(
|
|
||||||
result => alert("Fulfilled: " + result), // не сработает
|
|
||||||
error => alert("Rejected: " + error) // сработает
|
|
||||||
);
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Промисификация
|
|
||||||
|
|
||||||
*Промисификация* -- это когда берут асинхронный функционал и делают для него обёртку, возвращающую промис.
|
|
||||||
|
|
||||||
После промисификации использование функционала зачастую становится гораздо удобнее.
|
|
||||||
|
|
||||||
В качестве примера сделаем такую обёртку для запросов при помощи XMLHttpRequest.
|
|
||||||
|
|
||||||
Функция `httpGet(url)` будет возвращать промис, который при успешной загрузке данных с `url` будет переходить в `fulfilled` с этими данными, а при ошибке -- в `rejected` с информацией об ошибке:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ autorun
|
|
||||||
function httpGet(url) {
|
|
||||||
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('GET', url, true);
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
|
||||||
if (this.status == 200) {
|
|
||||||
*!*
|
|
||||||
resolve(this.response);
|
|
||||||
*/!*
|
|
||||||
} else {
|
|
||||||
*!*
|
|
||||||
var error = new Error(this.statusText);
|
|
||||||
error.code = this.status;
|
|
||||||
reject(error);
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onerror = function() {
|
|
||||||
*!*
|
|
||||||
reject(new Error("Network Error"));
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.send();
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно, внутри функции объект `XMLHttpRequest` создаётся и отсылается как обычно, при `onload/onerror` вызываются, соответственно, `resolve` (при статусе 200) или `reject`.
|
|
||||||
|
|
||||||
Использование:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
httpGet("/article/promise/user.json")
|
|
||||||
.then(
|
|
||||||
response => alert(`Fulfilled: ${response}`),
|
|
||||||
error => alert(`Rejected: ${error}`)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
[smart header="Метод `fetch`"]
|
|
||||||
Заметим, что ряд современных браузеров уже поддерживает [fetch](/fetch) -- новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он гораздо мощнее, чем `httpGet`. И -- да, этот метод использует промисы. Полифилл для него доступен на [](https://github.com/github/fetch).
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
|
|
||||||
## Цепочки промисов
|
|
||||||
|
|
||||||
"Чейнинг" (chaining), то есть возможность строить асинхронные цепочки из промисов -- пожалуй, основная причина, из-за которой существуют и активно используются промисы.
|
|
||||||
|
|
||||||
Например, мы хотим по очереди:
|
|
||||||
<ol>
|
|
||||||
<li>Загрузить данные посетителя с сервера (асинхронно).</li>
|
|
||||||
<li>Затем отправить запрос о нём на github (асинхронно).</li>
|
|
||||||
<li>Когда это будет готово, вывести его github-аватар на экран (асинхронно).</li>
|
|
||||||
<li>...И сделать код расширяемым, чтобы цепочку можно было легко продолжить.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Вот код для этого, использующий функцию `httpGet`, описанную выше:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// сделать запрос
|
|
||||||
httpGet('/article/promise/user.json')
|
|
||||||
*!*
|
|
||||||
// 1. Получить данные о пользователе в JSON и передать дальше
|
|
||||||
*/!*
|
|
||||||
.then(response => {
|
|
||||||
console.log(response);
|
|
||||||
let user = JSON.parse(response);
|
|
||||||
*!*
|
|
||||||
return user;
|
|
||||||
*/!*
|
|
||||||
})
|
|
||||||
*!*
|
|
||||||
// 2. Получить информацию с github
|
|
||||||
*/!*
|
|
||||||
.then(user => {
|
|
||||||
console.log(user);
|
|
||||||
*!*
|
|
||||||
return httpGet(`https://api.github.com/users/${user.name}`);
|
|
||||||
*/!*
|
|
||||||
})
|
|
||||||
*!*
|
|
||||||
// 3. Вывести аватар на 3 секунды (можно с анимацией)
|
|
||||||
*/!*
|
|
||||||
.then(githubUser => {
|
|
||||||
console.log(githubUser);
|
|
||||||
githubUser = JSON.parse(githubUser);
|
|
||||||
|
|
||||||
let img = new Image();
|
|
||||||
img.src = githubUser.avatar_url;
|
|
||||||
img.className = "promise-avatar-example";
|
|
||||||
document.body.appendChild(img);
|
|
||||||
|
|
||||||
*!*
|
|
||||||
setTimeout(() => img.remove(), 3000); // (*)
|
|
||||||
*/!*
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Самое главное в этом коде -- последовательность вызовов:
|
|
||||||
|
|
||||||
```js
|
|
||||||
httpGet(...)
|
|
||||||
.then(...)
|
|
||||||
.then(...)
|
|
||||||
.then(...)
|
|
||||||
```
|
|
||||||
|
|
||||||
При чейнинге, то есть последовательных вызовах `.then…then…then`, в каждый следующий `then` переходит результат от предыдущего. Вызовы `console.log` оставлены, чтобы при запуске можно было посмотреть конкретные значения, хотя они здесь и не очень важны.
|
|
||||||
|
|
||||||
**Если очередной `then` вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.**
|
|
||||||
|
|
||||||
В коде выше:
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>В первом `then` возвращается объект `user`, он переходит в следующий `then`.</li>
|
|
||||||
<li>Во втором `then` возвращается промис (результат нового вызова `httpGet`). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий `then`.</li>
|
|
||||||
<li>Третий `then` ничего не возвращает.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Схематично его работу можно изобразить так:
|
|
||||||
|
|
||||||
<img src="promiseUserFlow.png">
|
|
||||||
|
|
||||||
Значком "песочные часы" помечены периоды ожидания, которых всего два: в исходном `httpGet` и в подвызове далее по цепочке.
|
|
||||||
|
|
||||||
Если `then` возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки будет ждать.
|
|
||||||
|
|
||||||
То есть, логика довольно проста:
|
|
||||||
<ul>
|
|
||||||
<li>В каждом `then` мы получаем текущий результат работы.</li>
|
|
||||||
<li>Можно его обработать синхронно и вернуть результат (например, применить `JSON.parse`). Или же, если нужна асинхронная обработка -- инициировать её и вернуть промис.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Обратим внимание, что последний `then` в нашем примере ничего не возвращает. Если мы хотим, чтобы после `setTimeout` `(*)` асинхронная цепочка могла быть продолжена, то последний `then` тоже должен вернуть промис. Это общее правило: если внутри `then` стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис.
|
|
||||||
|
|
||||||
В данном случае промис должен перейти в состояние "выполнен" после срабатывания `setTimeout`.
|
|
||||||
|
|
||||||
Строку `(*)` для этого нужно переписать так:
|
|
||||||
```js
|
|
||||||
.then(githubUser => {
|
|
||||||
...
|
|
||||||
|
|
||||||
// вместо setTimeout(() => img.remove(), 3000); (*)
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
img.remove();
|
|
||||||
// после таймаута — вызов resolve,
|
|
||||||
// можно без результата, чтобы управление перешло в следующий then
|
|
||||||
// (или можно передать данные пользователя дальше по цепочке)
|
|
||||||
*!*
|
|
||||||
resolve();
|
|
||||||
*/!*
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Теперь, если к цепочке добавить ещё `then`, то он будет вызван после окончания `setTimeout`.
|
|
||||||
|
|
||||||
## Перехват ошибок
|
|
||||||
|
|
||||||
Выше мы рассмотрели "идеальный случай" выполнения, когда ошибок нет.
|
|
||||||
|
|
||||||
А что, если github не отвечает? Или JSON.parse бросил синтаксическую ошибку при обработке данных?
|
|
||||||
|
|
||||||
Да мало ли, где ошибка...
|
|
||||||
|
|
||||||
Правило здесь очень простое.
|
|
||||||
|
|
||||||
**При возникновении ошибки -- она отправляется в ближайший обработчик `onRejected`.**
|
|
||||||
|
|
||||||
Такой обработчик нужно поставить через второй аргумент `.then(..., onRejected)` или, что то же самое, через `.catch(onRejected)`.
|
|
||||||
|
|
||||||
Чтобы поймать всевозможные ошибки, которые возникнут при загрузке и обработке данных, добавим `catch` в конец нашей цепочки:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// в httpGet обратимся к несуществующей странице
|
|
||||||
*/!*
|
|
||||||
httpGet('/page-not-exists')
|
|
||||||
.then(response => JSON.parse(response))
|
|
||||||
.then(user => httpGet(`https://api.github.com/users/${user.name}`))
|
|
||||||
.then(githubUser => {
|
|
||||||
githubUser = JSON.parse(githubUser);
|
|
||||||
|
|
||||||
let img = new Image();
|
|
||||||
img.src = githubUser.avatar_url;
|
|
||||||
img.className = "promise-avatar-example";
|
|
||||||
document.body.appendChild(img);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
img.remove();
|
|
||||||
resolve();
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
*!*
|
|
||||||
.catch(error => {
|
|
||||||
alert(error); // Error: Not Found
|
|
||||||
});
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше ошибка возникает в первом же `httpGet`, но `catch` с тем же успехом поймал бы ошибку во втором `httpGet` или в `JSON.parse`.
|
|
||||||
|
|
||||||
Принцип очень похож на обычный `try..catch`: мы делаем асинхронную цепочку из `.then`, а затем, когда нужно перехватить ошибки, вызываем `.catch(onRejected)`.
|
|
||||||
|
|
||||||
|
|
||||||
[smart header="А что после `catch`?"]
|
|
||||||
Обработчик `.catch(onRejected)` получает ошибку и должен обработать её.
|
|
||||||
|
|
||||||
Есть два варианта развития событий:
|
|
||||||
<ol>
|
|
||||||
<li>Если ошибка не критичная, то `onRejected` возвращает значение через `return`, и управление переходит в ближайший `.then(onFulfilled)`.</li>
|
|
||||||
<li>Если продолжить выполнение с такой ошибкой нельзя, то он делает `throw`, и тогда ошибка переходит в следующий ближайший `.catch(onRejected)`.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Это также похоже на обычный `try..catch` -- в блоке `catch` ошибка либо обрабатывается, и тогда выполнение кода продолжается как обычно, либо он делает `throw`. Существенное отличие -- в том, что промисы асинхронные, поэтому при отсутствии внешнего `.catch` ошибка не "вываливается" в консоль и не "убивает" скрипт.
|
|
||||||
|
|
||||||
Ведь возможно, что новый обработчик `.catch` будет добавлен в цепочку позже.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
## Промисы в деталях
|
|
||||||
|
|
||||||
Самым основным источником информации по промисам является, разумеется, [стандарт](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-promise-objects).
|
|
||||||
|
|
||||||
Чтобы наше понимание промисов было полным, и мы могли с лёгкостью разрешать сложные ситуации, посмотрим внимательнее, что такое промис и как он работает, но уже не в общих словах, а детально, в соответствии со стандартом EcmaScript.
|
|
||||||
|
|
||||||
Согласно стандарту, у объекта `new Promise(executor)` при создании есть четыре внутренних свойства:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>`PromiseState` -- состояние, вначале "pending".</li>
|
|
||||||
<li>`PromiseResult` -- результат, при создании значения нет.</li>
|
|
||||||
<li>`PromiseFulfillReactions` -- список функций-обработчиков успешного выполнения.</li>
|
|
||||||
<li>`PromiseRejectReactions` -- список функций-обработчиков ошибки.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<img src="promiseEcma.png">
|
|
||||||
|
|
||||||
Когда функция-executor вызывает `reject` или `resolve`, то `PromiseState` становится `"resolved"` или `"rejected"`, а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь `"PromiseJobs"`.
|
|
||||||
|
|
||||||
Эта очередь автоматически выполняется, когда интерпретатору "нечего делать". Иначе говоря, все функции-обработчики выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как `setTimeout(..,0)`.
|
|
||||||
|
|
||||||
Исключение из этого правила -- если `resolve` возвращает другой `Promise`. Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
|
|
||||||
|
|
||||||
Добавляет обработчики в списки один метод: `.then(onResolved, onRejected)`. Метод `.catch(onRejected)` -- всего лишь сокращённая запись `.then(null, onRejected)`.
|
|
||||||
|
|
||||||
Он делает следующее:
|
|
||||||
<ul>
|
|
||||||
<li>Если `PromiseState == "pending"`, то есть промис ещё не выполнен, то обработчики добавляются в соответствующие списки.</li>
|
|
||||||
<li>Иначе обработчики сразу помещаются в очередь на выполнение.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Здесь важно, что обработчики можно добавлять в любой момент. Можно до выполнения промиса (они подождут), а можно -- после (выполнятся в ближайшее время, через асинхронную очередь).
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
// Промис выполнится сразу же
|
|
||||||
var promise = new Promise((resolve, reject) => resolve(1));
|
|
||||||
|
|
||||||
// PromiseState = "resolved"
|
|
||||||
// PromiseResult = 1
|
|
||||||
|
|
||||||
// Добавили обработчик к выполненному промису
|
|
||||||
promise.then(alert); // ...он сработает тут же
|
|
||||||
```
|
|
||||||
|
|
||||||
Разумеется, можно добавлять и много обработчиков на один и тот же промис:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
// Промис выполнится сразу же
|
|
||||||
var promise = new Promise((resolve, reject) => resolve(1));
|
|
||||||
|
|
||||||
promise.then( function f1(result) {
|
|
||||||
*!*
|
|
||||||
alert(result); // 1
|
|
||||||
*/!*
|
|
||||||
return 'f1';
|
|
||||||
})
|
|
||||||
|
|
||||||
promise.then( function f2(result) {
|
|
||||||
*!*
|
|
||||||
alert(result); // 1
|
|
||||||
*/!*
|
|
||||||
return 'f2';
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Вид объекта `promise` после этого:
|
|
||||||
|
|
||||||
<img src="promiseTwo.png">
|
|
||||||
|
|
||||||
На этой иллюстрации можно увидеть добавленные нами обработчики `f1`, `f2`, а также -- автоматические добавленные обработчики ошибок `"Thrower"`.
|
|
||||||
|
|
||||||
Дело в том, что `.then`, если один из обработчиков не указан, добавляет его "от себя", следующим образом:
|
|
||||||
<ul>
|
|
||||||
<li>Для успешного выполнения -- функция `Identity`, которая выглядит как `arg => return arg`, то есть возвращает аргумент без изменений.</li>
|
|
||||||
<li>Для ошибки -- функция `Thrower`, которая выглядит как `arg => throw arg`, то есть генерирует ошибку.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Это, по сути дела, формальность, но без неё некоторые особенности поведения промисов могут "не сойтись" в общую логику, поэтому мы упоминаем о ней здесь.
|
|
||||||
|
|
||||||
Обратим внимание, в этом примере намеренно *не используется чейнинг*. То есть, обработчики добавляются именно на один и тот же промис.
|
|
||||||
|
|
||||||
Поэтому оба `alert` выдадут одно значение `1`.
|
|
||||||
|
|
||||||
Все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками в рамках одного промиса нет, а сам результат промиса (`PromiseResult`) после установки не меняется.
|
|
||||||
|
|
||||||
Поэтому, чтобы продолжить работу с результатом, используется чейнинг.
|
|
||||||
|
|
||||||
**Для того, чтобы результат обработчика передать следующей функции, `.then` создаёт новый промис и возвращает его.**
|
|
||||||
|
|
||||||
В примере выше создаётся два таких промиса (т.к. два вызова `.then`), каждый из которых даёт свою ветку выполнения:
|
|
||||||
|
|
||||||
<img src="promiseTwoThen.png">
|
|
||||||
|
|
||||||
Изначально эти новые промисы -- "пустые", они ждут. Когда в будущем выполнятся обработчики `f1, f2`, то их результат будет передан в новые промисы по стандартному принципу:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Если вернётся обычное значение (не промис), новый промис перейдёт в `"resolved"` с ним.</li>
|
|
||||||
<li>Если был `throw`, то новый промис перейдёт в состояние `"rejected"` с ошибкой.</li>
|
|
||||||
<li>Если вернётся промис, то используем его результат (он может быть как `resolved`, так и `rejected`).</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<img src="promiseHandlerVariants.png">
|
|
||||||
|
|
||||||
Дальше выполнятся уже обработчики на новом промисе, и так далее.
|
|
||||||
|
|
||||||
Чтобы лучше понять происходящее, посмотрим на цепочку, которая получается в процессе написания кода для показа github-аватара.
|
|
||||||
|
|
||||||
Первый промис и обработка его результата:
|
|
||||||
|
|
||||||
```js
|
|
||||||
httpGet('/article/promise/user.json')
|
|
||||||
.then(JSON.parse)
|
|
||||||
```
|
|
||||||
|
|
||||||
<img src="promiseLoadAvatarChain-1.png">
|
|
||||||
|
|
||||||
|
|
||||||
Если промис завершился через `resolve`, то результат -- в `JSON.parse`, если `reject` -- то в Thrower.
|
|
||||||
|
|
||||||
Как было сказано выше, `Thrower` -- это стандартная внутренняя функция, которая автоматически используется, если второй обработчик не указан.
|
|
||||||
|
|
||||||
Можно считать, что второй обработчик выглядит так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
httpGet('/article/promise/user.json')
|
|
||||||
.then(JSON.parse, *!*err => throw err*/!*)
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, что когда обработчик в промисах делает `throw` -- в данном случае, при ошибке запроса, то такая ошибка не "валит" скрипт и не выводится в консоли. Она просто будет передана в ближайший следующий обработчик `onRejected`.
|
|
||||||
|
|
||||||
Добавим в код ещё строку:
|
|
||||||
|
|
||||||
```js
|
|
||||||
httpGet('/article/promise/user.json')
|
|
||||||
.then(JSON.parse)
|
|
||||||
*!*
|
|
||||||
.then(user => httpGet(`https://api.github.com/users/${user.name}`))
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Цепочка "выросла вниз":
|
|
||||||
|
|
||||||
<img src="promiseLoadAvatarChain-2.png">
|
|
||||||
|
|
||||||
Функция `JSON.parse` либо возвращает объект с данными, либо генерирует ошибку (что расценивается как `reject`).
|
|
||||||
|
|
||||||
Если всё хорошо, то `then(user => httpGet(…))` вернёт новый промис, на который стоят уже два обработчика:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
httpGet('/article/promise/user.json')
|
|
||||||
.then(JSON.parse)
|
|
||||||
.then(user => httpGet(`https://api.github.com/users/${user.name}`))
|
|
||||||
.then(
|
|
||||||
*!*
|
|
||||||
JSON.parse,
|
|
||||||
function avatarError(error) {
|
|
||||||
if (error.code == 404) {
|
|
||||||
return {name: "NoGithub", avatar_url: '/article/promise/anon.png'};
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
<img src="promiseLoadAvatarChain-3.png">
|
|
||||||
|
|
||||||
Наконец-то хоть какая-то обработка ошибок!
|
|
||||||
|
|
||||||
Обработчик `avatarError` перехватит ошибки, которые были ранее. Функция `httpGet` при генерации ошибки записывает её HTTP-код в свойство `error.code`, так что мы легко можем понять -- что это:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Если страница на Github не найдена -- можно продолжить выполнение, используя "аватар по умолчанию"</li>
|
|
||||||
<li>Иначе -- пробрасываем ошибку дальше.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Итого, после добавления оставшейся части цепочки, картина получается следующей:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
httpGet('/article/promise/userNoGithub.json')
|
|
||||||
.then(JSON.parse)
|
|
||||||
.then(user => loadUrl(`https://api.github.com/users/${user.name}`))
|
|
||||||
.then(
|
|
||||||
JSON.parse,
|
|
||||||
function githubError(error) {
|
|
||||||
if (error.code == 404) {
|
|
||||||
return {name: "NoGithub", avatar_url: '/article/promise/anon.png'};
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(function showAvatar(githubUser) {
|
|
||||||
let img = new Image();
|
|
||||||
img.src = githubUser.avatar_url;
|
|
||||||
img.className = "promise-avatar-example";
|
|
||||||
document.body.appendChild(img);
|
|
||||||
setTimeout(() => img.remove(), 3000);
|
|
||||||
})
|
|
||||||
.catch(function genericError(error) {
|
|
||||||
alert(error); // Error: Not Found
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
<img src="promiseLoadAvatarChain-4.png">
|
|
||||||
|
|
||||||
В конце срабатывает общий обработчик `genericError`, который перехватывает любые ошибки. В данном случае ошибки, которые в него попадут, уже носят критический характер, что-то серьёзно не так. Чтобы посетитель не удивился отсутствию информации, мы показываем ему сообщение об этом.
|
|
||||||
|
|
||||||
Можно и как-то иначе вывести уведомление о проблеме, главное -- не забыть обработать ошибки в конце. Если последнего `catch` не будет, а цепочка завершится с ошибкой, то посетитель об этом не узнает.
|
|
||||||
|
|
||||||
В консоли тоже ничего не будет, так как ошибка остаётся "внутри" промиса, ожидая добавления следующего обработчика `onRejected`, которому будет передана.
|
|
||||||
|
|
||||||
Итак, мы рассмотрели основные приёмы использования промисов. Далее -- посмотрим некоторые полезные вспомогательные методы.
|
|
||||||
|
|
||||||
## Параллельное выполнение
|
|
||||||
|
|
||||||
Что, если мы хотим осуществить несколько асинхронных процессов одновременно и обработать их результат?
|
|
||||||
|
|
||||||
|
|
||||||
В классе `Promise` есть следующие статические методы.
|
|
||||||
|
|
||||||
### Promise.all(iterable)
|
|
||||||
|
|
||||||
Вызов `Promise.all(iterable)` получает массив (или другой итерируемый объект) промисов и возвращает промис, который ждёт, пока все переданные промисы завершатся, и переходит в состояние "выполнено" с массивом их результатов.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
|
|
||||||
Promise.all([
|
|
||||||
httpGet('/article/promise/user.json'),
|
|
||||||
httpGet('/article/promise/guest.json')
|
|
||||||
]).then(results => {
|
|
||||||
alert(results);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Допустим, у нас есть массив с URL.
|
|
||||||
|
|
||||||
```js
|
|
||||||
let urls = [
|
|
||||||
'/article/promise/user.json',
|
|
||||||
'/article/promise/guest.json'
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
Чтобы загрузить их параллельно, нужно:
|
|
||||||
<ol>
|
|
||||||
<li>Создать для каждого URL соответствующий промис.</li>
|
|
||||||
<li>Обернуть массив таких промисов в `Promise.all`.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Получится так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let urls = [
|
|
||||||
'/article/promise/user.json',
|
|
||||||
'/article/promise/guest.json'
|
|
||||||
];
|
|
||||||
|
|
||||||
*!*
|
|
||||||
Promise.all( urls.map(httpGet) )
|
|
||||||
*/!*
|
|
||||||
.then(results => {
|
|
||||||
alert(results);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, что если какой-то из промисов завершился с ошибкой, то результатом `Promise.all` будет эта ошибка. При этом остальные промисы игнорируются.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
|
|
||||||
Promise.all([
|
|
||||||
httpGet('/article/promise/user.json'),
|
|
||||||
httpGet('/article/promise/guest.json'),
|
|
||||||
httpGet('/article/promise/no-such-page.json') // (нет такой страницы)
|
|
||||||
]).then(
|
|
||||||
result => alert("не сработает"),
|
|
||||||
error => alert("Ошибка: " + error.message) // Ошибка: Not Found
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Promise.race(iterable)
|
|
||||||
|
|
||||||
Вызов `Promise.race`, как и `Promise.all`, получает итерируемый объект с промисами, которые нужно выполнить, и возвращает новый промис.
|
|
||||||
|
|
||||||
Но, в отличие от `Promise.all`, результатом будет только первый успешно выполнившийся промис из списка. Остальные игнорируются.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
|
|
||||||
Promise.race([
|
|
||||||
httpGet('/article/promise/user.json'),
|
|
||||||
httpGet('/article/promise/guest.json')
|
|
||||||
]).then(firstResult => {
|
|
||||||
firstResult = JSON.parse(firstResult);
|
|
||||||
alert( firstResult.name ); // iliakan или guest, смотря что загрузится раньше
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Promise.resolve(value)
|
|
||||||
|
|
||||||
Вызов `Promise.resolve(value)` создаёт успешно выполнившийся промис с результатом `value`.
|
|
||||||
|
|
||||||
Он аналогичен конструкции:
|
|
||||||
|
|
||||||
```js
|
|
||||||
new Promise((resolve) => resolve(value))
|
|
||||||
```
|
|
||||||
|
|
||||||
`Promise.resolve`, когда хотят построить асинхронную цепочку, и начальный результат уже есть.
|
|
||||||
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
Promise.resolve(window.location) // начать с этого значения
|
|
||||||
.then(httpGet) // вызвать для него httpGet
|
|
||||||
.then(alert) // и вывести результат
|
|
||||||
```
|
|
||||||
|
|
||||||
## Promise.reject(error)
|
|
||||||
|
|
||||||
Аналогично `Promise.resolve(value)` создаёт уже выполнившийся промис, но не с успешным результатом, а с ошибкой `error`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
Promise.reject(new Error("..."))
|
|
||||||
.catch(alert) // Error: ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Метод `Promise.reject` используется очень редко, гораздо реже чем `resolve`, потому что ошибка возникает обычно не в начале цепочки, а в процессе её выполнения.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Промис -- это специальный объект, который хранит своё состояние, текущий результат (если есть) и коллбэки.</li>
|
|
||||||
<li>При создании `new Promise((resolve, reject) => ...)` автоматически запускается функция-аргумент, которая должна вызвать `resolve(result)` при успешном выполнении и `reject(error)` -- при ошибке.</li>
|
|
||||||
<li>Аргумент `resolve/reject` (только первый, остальные игнорируются) передаётся обработчикам на этом промисе.</li>
|
|
||||||
<li>Обработчики назначаются вызовом `.then/catch`.</li>
|
|
||||||
<li>Для передачи результата от одного обработчика к другому используется чейнинг.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
У промисов есть некоторые ограничения. В частности, стандарт не предусматривает какой-то метод для "отмены" промиса, хотя в ряде ситуаций (http-запросы) это было бы довольно удобно. Возможно, он появится в следующей версии стандарта JavaScript.
|
|
||||||
|
|
||||||
В современной JavaScript-разработке сложные цепочки с промисами используются редко, так как они куда проще описываются при помощи генераторов с библиотекой `co`, которые рассмотрены в [соответствующей главе](/generator). Можно сказать, что промисы лежат в основе более продвинутых способов асинхронной разработки.
|
|
||||||
|
|
||||||
[head]
|
|
||||||
<style>
|
|
||||||
.promise-avatar-example {
|
|
||||||
border-radius: 50%;
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
[/head]
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "guest",
|
|
||||||
"isAdmin": false
|
|
||||||
}
|
|
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 70 KiB |
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "iliakan",
|
|
||||||
"isAdmin": true
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "an-unknown-person-32662",
|
|
||||||
"isAdmin": false
|
|
||||||
}
|
|
Before Width: | Height: | Size: 30 KiB |
|
@ -1,904 +0,0 @@
|
||||||
|
|
||||||
# Генераторы
|
|
||||||
|
|
||||||
Генераторы -- новый вид функций в современном JavaScript. Они отличаются от обычных тем, что могут приостанавливать своё выполнение, возвращать промежуточный результат и далее возобновлять его позже, в произвольный момент времени.
|
|
||||||
|
|
||||||
## Создание генератора
|
|
||||||
|
|
||||||
Для объявления генератора используется новая синтаксическая конструкция: `function*` (функция со звёздочкой).
|
|
||||||
|
|
||||||
Её называют "функция-генератор" (generator function).
|
|
||||||
|
|
||||||
Выглядит это так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function* generateSequence() {
|
|
||||||
yield 1;
|
|
||||||
yield 2;
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
При запуске `generateSequence()` код такой функции не выполняется. Вместо этого она возвращает специальный объект, который как раз и называют "генератором".
|
|
||||||
|
|
||||||
```js
|
|
||||||
// generator function создаёт generator
|
|
||||||
let generator = generateSequence();
|
|
||||||
```
|
|
||||||
|
|
||||||
Правильнее всего будет воспринимать генератор как "замороженный вызов функции":
|
|
||||||
|
|
||||||
<img src="generateSequence-1.png">
|
|
||||||
|
|
||||||
При создании генератора код находится в начале своего выполнения.
|
|
||||||
|
|
||||||
Основным методом генератора является `next()`. При вызове он возобновляет выполнение кода до ближайшего ключевого слова `yield`. По достижении `yield` выполнение приостанавливается, а значение -- возвращается во внешний код:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* generateSequence() {
|
|
||||||
yield 1;
|
|
||||||
yield 2;
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
let generator = generateSequence();
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let one = generator.next();
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(JSON.stringify(one)); // {value: 1, done: false}
|
|
||||||
```
|
|
||||||
|
|
||||||
<img src="generateSequence-2.png">
|
|
||||||
|
|
||||||
Повторный вызов `generator.next()` возобновит выполнение и вернёт результат следующего `yield`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let two = generator.next();
|
|
||||||
|
|
||||||
alert(JSON.stringify(two)); // {value: 2, done: false}
|
|
||||||
```
|
|
||||||
|
|
||||||
<img src="generateSequence-3.png">
|
|
||||||
|
|
||||||
И, наконец, последний вызов завершит выполнение функции и вернёт результат `return`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let three = generator.next();
|
|
||||||
|
|
||||||
alert(JSON.stringify(three)); // {value: 3, *!*done: true*/!*}
|
|
||||||
```
|
|
||||||
|
|
||||||
<img src="generateSequence-4.png">
|
|
||||||
|
|
||||||
|
|
||||||
Функция завершена. Внешний код должен увидеть это из свойства `done:true` и обработать `value:3`, как окончательный результат.
|
|
||||||
|
|
||||||
Новые вызовы `generator.next()` больше не имеют смысла. Впрочем, если они и будут, то не вызовут ошибки, но будут возвращать один и тот же объект: `{done: true}`.
|
|
||||||
|
|
||||||
"Открутить назад" завершившийся генератор нельзя, но можно создать новый ещё одним вызовом `generateSequence()` и выполнить его.
|
|
||||||
|
|
||||||
[smart header="`function* (…)` или `function *(…)`?"]
|
|
||||||
Можно ставить звёздочку как сразу после `function`, так и позже, перед названием. В интернет можно найти обе эти формы записи, они верны:
|
|
||||||
```js
|
|
||||||
function* f() {
|
|
||||||
// звёздочка после function
|
|
||||||
}
|
|
||||||
|
|
||||||
function *f() {
|
|
||||||
// звёздочка перед названием
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Технически, нет разницы, но писать то так то эдак -- довольно странно, надо остановиться на чём-то одном.
|
|
||||||
|
|
||||||
Автор этого текста полагает, что правильнее использовать первый вариант `function*`, так как звёздочка относится к типу объявляемой сущности (`function*` -- "функция-генератор"), а не к её названию. Конечно, это всего лишь рекомендация-мнение, не обязательное к выполнению, работать будет в любом случае.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Генератор -- итератор
|
|
||||||
|
|
||||||
Как вы, наверно, уже догадались по наличию метода `next()`, генератор связан с [итераторами](/iterator). В частности, он является итерируемым объектом.
|
|
||||||
|
|
||||||
Его можно перебирать и через `for..of`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* generateSequence() {
|
|
||||||
yield 1;
|
|
||||||
yield 2;
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
let generator = generateSequence();
|
|
||||||
|
|
||||||
for(let value of generator) {
|
|
||||||
alert(value); // 1, затем 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, однако, существенную особенность такого перебора!
|
|
||||||
|
|
||||||
При запуске примера выше будет выведено значение `1`, затем `2`. Значение `3` выведено не будет. Это потому что стандартные перебор итератора игнорирует `value` на последнем значении, при `done: true`. Так что результат `return` в цикле `for..of` не выводится.
|
|
||||||
|
|
||||||
Соответственно, если мы хотим, чтобы все значения возвращались при переборе через `for..of`, то надо возвращать их через `yield`:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* generateSequence() {
|
|
||||||
yield 1;
|
|
||||||
yield 2;
|
|
||||||
*!*
|
|
||||||
yield 3;
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
let generator = generateSequence();
|
|
||||||
|
|
||||||
for(let value of generator) {
|
|
||||||
alert(value); // 1, затем 2, затем 3
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
...А зачем вообще `return` при таком раскладе, если его результат игнорируется? Он тоже нужен, но в других ситуациях. Перебор через `for..of` -- в некотором смысле "исключение". Как мы увидим дальше, в других контекстах `return` очень даже востребован.
|
|
||||||
|
|
||||||
## Композиция генераторов
|
|
||||||
|
|
||||||
Один генератор может включать в себя другие. Это называется композицией.
|
|
||||||
|
|
||||||
Разберём композицию на примере.
|
|
||||||
|
|
||||||
Пусть у нас есть функция `generateSequence`, которая генерирует последовательность чисел:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* generateSequence(start, end) {
|
|
||||||
|
|
||||||
for (let i = start; i <= end; i++) {
|
|
||||||
yield i;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Используем оператор … для преобразования итерируемого объекта в массив
|
|
||||||
let sequence = [...generateSequence(2,5)];
|
|
||||||
|
|
||||||
alert(sequence); // 2, 3, 4, 5
|
|
||||||
```
|
|
||||||
|
|
||||||
Мы хотим на её основе сделать другую функцию `generateAlphaNumCodes()`, которая будет генерировать коды для буквенно-цифровых символов латинского алфавита:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>`48..57` -- для `0..9`</li>
|
|
||||||
<li>`65..90` -- для `A..Z`</li>
|
|
||||||
<li>`97..122` -- для `a..z`</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Далее этот набор кодов можно превратить в строку и использовать, к примеру, для выбора из него случайного пароля. Только символы пунктуации ещё хорошо бы добавить для надёжности, но в этом примере мы будем без них.
|
|
||||||
|
|
||||||
Естественно, раз в нашем распоряжении есть готовый генератор `generateSequence`, то хорошо бы его использовать.
|
|
||||||
|
|
||||||
Конечно, можно внутри `generateAlphaNum` запустить несколько раз `generateSequence`, объединить результаты и вернуть. Так мы бы сделали с обычными функциями. Но композиция -- это кое-что получше.
|
|
||||||
|
|
||||||
Она выглядит так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* generateSequence(start, end) {
|
|
||||||
for (let i = start; i <= end; i++) yield i;
|
|
||||||
}
|
|
||||||
|
|
||||||
function* generateAlphaNum() {
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// 0..9
|
|
||||||
yield* generateSequence(48, 57);
|
|
||||||
|
|
||||||
// A..Z
|
|
||||||
yield* generateSequence(65, 90);
|
|
||||||
|
|
||||||
// a..z
|
|
||||||
yield* generateSequence(97, 122);
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let str = '';
|
|
||||||
|
|
||||||
for(let code of generateAlphaNum()) {
|
|
||||||
str += String.fromCharCode(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(str); // 0..9A..Za..z
|
|
||||||
```
|
|
||||||
|
|
||||||
Здесь использована специальная форма `yield*`. Она применима только к другому генератору и *делегирует* ему выполнение.
|
|
||||||
|
|
||||||
То есть, при `yield*` интерпретатор переходит внутрь генератора-аргумента, к примеру, `generateSequence(48, 57)`, выполняет его, и все `yield`, которые он делает, выходят из внешнего генератора.
|
|
||||||
|
|
||||||
Получается -- как будто мы вставили код внутреннего генератора во внешний напрямую, вот так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* generateSequence(start, end) {
|
|
||||||
for (let i = start; i <= end; i++) yield i;
|
|
||||||
}
|
|
||||||
|
|
||||||
function* generateAlphaNum() {
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// yield* generateSequence(48, 57);
|
|
||||||
for (let i = 48; i <= 57; i++) yield i;
|
|
||||||
|
|
||||||
// yield* generateSequence(65, 90);
|
|
||||||
for (let i = 65; i <= 90; i++) yield i;
|
|
||||||
|
|
||||||
// yield* generateSequence(97, 122);
|
|
||||||
for (let i = 97; i <= 122; i++) yield i;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let str = '';
|
|
||||||
|
|
||||||
for(let code of generateAlphaNum()) {
|
|
||||||
str += String.fromCharCode(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(str); // 0..9A..Za..z
|
|
||||||
```
|
|
||||||
|
|
||||||
Код выше по поведению полностью идентичен варианту с `yield*`. При этом, конечно, переменные вложенного генератора не попадают во внешний, "делегирование" только выводит результаты `yield` во внешний поток.
|
|
||||||
|
|
||||||
Композиция -- это естественное встраивание одного генератора в поток другого. При композиции значения из вложенного генератора выдаются "по мере готовности". Поэтому она будет работать даже если поток данных из вложенного генератора оказался бесконечным или ожидает какого-либо условия для завершения.
|
|
||||||
|
|
||||||
|
|
||||||
## yield -- дорога в обе стороны
|
|
||||||
|
|
||||||
До этого генераторы наиболее напоминали "итераторы на стероидах". Но, как мы сейчас увидим, это не так, есть фундаментальное различие, генераторы гораздо мощнее и гибче.
|
|
||||||
|
|
||||||
Всё дело в том, что `yield` -- дорога в обе стороны: он не только возвращает результат наружу, но и может передавать значение извне в генератор.
|
|
||||||
|
|
||||||
Вызов `let result = yield value` делает следующее:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Возвращает `value` во внешний код, приостанавливая выполнение генератора.</li>
|
|
||||||
<li>Внешний код может обработать значение, и затем вызвать `next` с аргументом: `generator.next(arg)`.</li>
|
|
||||||
<li>Генератор продолжит выполнение, аргумент `next` будет возвращён как результат `yield` (и записан в `result`).</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Продемонстрируем это на примере:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* gen() {
|
|
||||||
*!*
|
|
||||||
// Передать вопрос во внешний код и подождать ответа
|
|
||||||
let result = yield "Сколько будет 2 + 2?";
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
let generator = gen();
|
|
||||||
|
|
||||||
let question = generator.next().value;
|
|
||||||
// "Сколько будет 2 + 2?"
|
|
||||||
|
|
||||||
setTimeout(() => generator.next(4), 2000);
|
|
||||||
```
|
|
||||||
|
|
||||||
На рисунке ниже прямоугольником изображён генератор, а вокруг него -- "внешний код", который с ним взаимодействует:
|
|
||||||
|
|
||||||
<img src="genYield2.png">
|
|
||||||
|
|
||||||
На этой иллюстрации показано то, что происходит в генераторе:
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Первый вызов `generator.next()` -- всегда без аргумента, он начинает выполнение и возвращает результат первого `yield` ("Сколько будет 2+2?"). На этой точке генератор приостанавливает выполнение.</li>
|
|
||||||
<li>Результат `yield` переходит во внешний код (в `question`). Внешний код может выполнять любые асинхронные задачи, генератор стоит "на паузе".</li>
|
|
||||||
<li>Когда асинхронные задачи готовы, внешний код вызывает `generator.next(4)` с аргументом. Выполнение генератора возобновляется, а `4` выходит из присваивания как результат `let result = yield ...`.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
В примере выше -- только два `next`.
|
|
||||||
|
|
||||||
Увеличим их количество, чтобы стал более понятен общий поток выполнения:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* gen() {
|
|
||||||
let ask1 = yield "Сколько будет 2 + 2?";
|
|
||||||
|
|
||||||
alert(ask1); // 4
|
|
||||||
|
|
||||||
let ask2 = yield "3 * 3?"
|
|
||||||
|
|
||||||
alert(ask2); // 9
|
|
||||||
}
|
|
||||||
|
|
||||||
let generator = gen();
|
|
||||||
|
|
||||||
alert( generator.next().value ); // "...2+2?"
|
|
||||||
|
|
||||||
alert( generator.next(4).value ); // "...3*3?"
|
|
||||||
|
|
||||||
alert( generator.next(9).done ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Взаимодействие с внешним кодом:
|
|
||||||
|
|
||||||
<img src="genYield2-2.png">
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Первый `.next()` начинает выполнение... Оно доходит до первого `yield`.</li>
|
|
||||||
<li>Результат возвращается во внешний код.</li>
|
|
||||||
<li>Второй `.next(4)` передаёт `4` обратно в генератор как результат первого `yield` и возобновляет выполнение.</li>
|
|
||||||
<li>...Оно доходит до второго `yield`, который станет результатом `.next(4)`.</li>
|
|
||||||
<li>Третий `next(9)` передаёт `9` в генератор как результат второго `yield` и возобновляет выполнение, которое завершается окончанием функции, так что `done: true`.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Получается "пинг-понг": каждый `next(value)` передаёт в генератор значение, которое становится результатом текущего `yield`, возобновляет выполнение и получает выражение из следующего `yield`. Исключением является первый вызов `next`, который не может передать значение в генератор, т.к. ещё не было ни одного `yield`.
|
|
||||||
|
|
||||||
На рисунке ниже изображены шаги 3-4 из примера:
|
|
||||||
|
|
||||||
<img src="genYield2-3.png">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## generator.throw
|
|
||||||
|
|
||||||
Как мы видели в примерах выше, внешний код может вернуть генератору в качестве результата `yield` любое значение.
|
|
||||||
|
|
||||||
...Но "вернуть" можно не только результат, но и ошибку!
|
|
||||||
|
|
||||||
Для того, чтобы передать в `yield` ошибку, используется вызов `generator.throw(err)`. При этом на строке с `yield` возникает исключение.
|
|
||||||
|
|
||||||
Например, в коде ниже обращение к внешнему коду `yield "Сколько будет 2 + 2"` завершится с ошибкой:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* gen() {
|
|
||||||
try {
|
|
||||||
// в этой строке возникнет ошибка
|
|
||||||
let result = yield "Сколько будет 2 + 2?"; // (**)
|
|
||||||
|
|
||||||
alert("выше будет исключение ^^^");
|
|
||||||
} catch(e) {
|
|
||||||
alert(e); // выведет ошибку
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let generator = gen();
|
|
||||||
|
|
||||||
let question = generator.next().value;
|
|
||||||
|
|
||||||
*!*
|
|
||||||
generator.throw(new Error("ответ не найден в моей базе данных")); // (*)
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
"Вброшенная" в строке `(*)` ошибка возникает в строке с `yield` `(**)`. Далее она обрабатывается как обычно. В примере выше она перехватывается `try..catch` и выводится.
|
|
||||||
|
|
||||||
Если ошибку не перехватить, то она "выпадет" из генератора. По стеку ближайший вызов, который инициировал выполнение -- это строка с `.throw`. Можно перехватить её там, как и продемонстрировано в примере ниже:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function* gen() {
|
|
||||||
// В этой строке возникнет ошибка
|
|
||||||
let result = yield "Сколько будет 2 + 2?";
|
|
||||||
}
|
|
||||||
|
|
||||||
let generator = gen();
|
|
||||||
|
|
||||||
let question = generator.next().value;
|
|
||||||
|
|
||||||
*!*
|
|
||||||
try {
|
|
||||||
generator.throw(new Error("ответ не найден в моей базе данных"));
|
|
||||||
} catch(e) {
|
|
||||||
alert(e); // выведет ошибку
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Если же ошибка и там не перехвачена, то дальше -- как обычно, либо `try..catch` снаружи, либо она "повалит" скрипт.
|
|
||||||
|
|
||||||
## Плоский асинхронный код
|
|
||||||
|
|
||||||
Одна из основных областей применения генераторов -- написание "плоского" асинхронного кода.
|
|
||||||
|
|
||||||
Общий принцип такой:
|
|
||||||
<ul>
|
|
||||||
<li>Генератор `yield'ит` не просто значения, а промисы.</li>
|
|
||||||
<li>Есть специальная "функция-чернорабочий" `execute(generator)` которая запускает генератор, последовательными вызовами `next` получает из него промисы -- один за другим, и, когда очередной промис выполнится, возвращает его результат в генератор следующим `next`.</li>
|
|
||||||
<li>Последнее значение генератора (`done:true`) `execute` уже обрабатывает как окончательный результат -- например, возвращает через промис куда-то ещё, во внешний код или просто использует, как в примере ниже.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Напишем такой код для получения аватара пользователя с github и его вывода, аналогичный рассмотренному в статье про [промисы](/promise).
|
|
||||||
|
|
||||||
Для AJAX-запросов будем использовать метод [fetch](/fetch), он как раз возвращает промисы.
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// генератор для получения и показа аватара
|
|
||||||
// он yield'ит промисы
|
|
||||||
function* showUserAvatar() {
|
|
||||||
|
|
||||||
let userFetch = yield fetch('/article/generator/user.json');
|
|
||||||
let userInfo = yield userFetch.json();
|
|
||||||
|
|
||||||
let githubFetch = yield fetch(`https://api.github.com/users/${userInfo.name}`);
|
|
||||||
let githubUserInfo = yield githubFetch.json();
|
|
||||||
|
|
||||||
let img = new Image();
|
|
||||||
img.src = githubUserInfo.avatar_url;
|
|
||||||
img.className = "promise-avatar-example";
|
|
||||||
document.body.appendChild(img);
|
|
||||||
|
|
||||||
yield new Promise(resolve => setTimeout(resolve, 3000));
|
|
||||||
|
|
||||||
img.remove();
|
|
||||||
|
|
||||||
return img.src;
|
|
||||||
}
|
|
||||||
|
|
||||||
// вспомогательная функция-чернорабочий
|
|
||||||
// для выполнения промисов из generator
|
|
||||||
function execute(generator, yieldValue) {
|
|
||||||
|
|
||||||
let next = generator.next(yieldValue);
|
|
||||||
|
|
||||||
if (!next.done) {
|
|
||||||
next.value.then(
|
|
||||||
result => execute(generator, result),
|
|
||||||
err => generator.throw(err)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// обработаем результат return из генератора
|
|
||||||
// обычно здесь вызов callback или что-то в этом духе
|
|
||||||
alert(next.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
execute( showUserAvatar() );
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Функция `execute` в примере выше -- универсальная, она может работать с любым генератором, который `yield'ит` промисы.
|
|
||||||
|
|
||||||
Вместе с тем, это -- всего лишь набросок, чтобы было понятно, как такая функция в принципе работает. Есть уже готовые реализации, обладающие большим количеством возможностей.
|
|
||||||
|
|
||||||
Одна из самых известных -- это библиотека [co](https://github.com/tj/co), которую мы рассмотрим далее.
|
|
||||||
|
|
||||||
## Библиотека "co"
|
|
||||||
|
|
||||||
Библиотека `co`, как и `execute` в примере выше, получает генератор и выполняет его.
|
|
||||||
|
|
||||||
Начнём сразу с примера, а потом -- детали и полезные возможности:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
co(function*() {
|
|
||||||
|
|
||||||
let result = yield new Promise(
|
|
||||||
resolve => setTimeout(resolve, 1000, 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
alert(result); // 1
|
|
||||||
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Предполагается, что библиотека `co` подключена к странице , например, отсюда: [](http://cdnjs.com/libraries/co/). В примере выше `function*()` делает `yield` промиса с `setTimeout`, который через секунду возвращает `1`.
|
|
||||||
|
|
||||||
Вызов `co(…)` возвращает промис с результатом генератора. Если в примере выше `function*()` что-то возвратит, то это можно будет получить через `.then` в результате `co`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
co(function*() {
|
|
||||||
|
|
||||||
let result = yield new Promise(
|
|
||||||
resolve => setTimeout(resolve, 1000, 1)
|
|
||||||
);
|
|
||||||
|
|
||||||
*!*
|
|
||||||
return result; // return 1
|
|
||||||
|
|
||||||
}).then(alert); // 1
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
[warn header="Обязательно нужен `catch`"]
|
|
||||||
|
|
||||||
Частая ошибка начинающих -- вообще забывать про обработку результата `co`. Даже если результата нет, ошибки нужно обработать через `catch`, иначе они "подвиснут" в промисе.
|
|
||||||
|
|
||||||
Такой код ничего не выведет:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
co(function*() {
|
|
||||||
throw new Error("Sorry that happened");
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Программист даже не узнает об ошибке. Особенно обидно, когда это опечатка или другая программная ошибка, которую обязательно нужно поправить.
|
|
||||||
|
|
||||||
Правильный вариант:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
co(function*() {
|
|
||||||
throw new Error("Sorry that happened");
|
|
||||||
}).catch(alert); // обработать ошибку как-либо
|
|
||||||
```
|
|
||||||
|
|
||||||
Большинство примеров этого `catch` не содержат, но это лишь потому, что в примерах ошибок нет. А в реальном коде обязательно нужен `catch`.
|
|
||||||
|
|
||||||
[/warn]
|
|
||||||
|
|
||||||
Библиотека `co` умеет выполнять не только промисы. Есть несколько видов значений, которые можно `yield`, и их обработает `co`:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Промис.</li>
|
|
||||||
<li>Объект-генератор.</li>
|
|
||||||
<li>Функция-генератор `function*()` -- `co` её выполнит, затем выполнит полученный генератор.</li>
|
|
||||||
<li>Функция с единственным аргументом вида `function(callback)` -- библиотека `co` её запустит со своей функцией-`callback` и будет ожидать, что при ошибке она вызовет `callback(err)`, а при успешном выполнении -- `callback(null, result)`. То есть, в первом аргументе -- будет ошибка (если есть), а втором -- результат (если нет ошибки). После чего результат будет передан в генератор.</li>
|
|
||||||
<li>Массив или объект из вышеперечисленного. При этом все задачи будут выполнены параллельно, и результат, в той же структуре, будет выдан наружу.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
В примере ниже происходит `yield` всех этих видов значений. Библиотека `co` обеспечивает их выполнение и возврат результата в генератор:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
Object.defineProperty(window, 'result', {
|
|
||||||
// присвоение result=… будет выводить значение
|
|
||||||
set: value => alert(JSON.stringify(value))
|
|
||||||
});
|
|
||||||
|
|
||||||
co(function*() {
|
|
||||||
result = yield function*() { // генератор
|
|
||||||
return 1;
|
|
||||||
}();
|
|
||||||
|
|
||||||
result = yield function*() { // функция-генератор
|
|
||||||
return 2;
|
|
||||||
};
|
|
||||||
|
|
||||||
result = yield Promise.resolve(3); // промис
|
|
||||||
|
|
||||||
result = yield function(callback) { // function(callback)
|
|
||||||
setTimeout(() => callback(null, 4), 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
result = yield { // две задачи выполнит параллельно, как Promise.all
|
|
||||||
one: Promise.resolve(1),
|
|
||||||
two: function*() { return 2; }
|
|
||||||
};
|
|
||||||
|
|
||||||
result = yield [ // две задачи выполнит параллельно, как Promise.all
|
|
||||||
Promise.resolve(1),
|
|
||||||
function*() { return 2 }
|
|
||||||
];
|
|
||||||
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
[smart header="Устаревший `yield function(callback)`"]
|
|
||||||
Отдельно заметим вариант с `yield function(callback)`. Такие функции, с единственным-аргументом callback'ом, в англоязычной литературе называют "thunk".
|
|
||||||
|
|
||||||
Функция обязана выполниться и вызвать (асинхронно) либо `callback(err)` с ошибкой, либо `callback(null, result)` с результатом.
|
|
||||||
|
|
||||||
Использование таких функций в `yield` является устаревшим подходом, так как там, где можно использовать `yield function(callback)`, можно использовать и промисы. При этом промисы мощнее. Но в старом коде его ещё можно встретить.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
|
|
||||||
Посмотрим пример посложнее, с композицией генераторов:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
co(function*() {
|
|
||||||
let result = yield* gen();
|
|
||||||
alert(result); // hello
|
|
||||||
});
|
|
||||||
|
|
||||||
function* gen() {
|
|
||||||
return yield* gen2();
|
|
||||||
}
|
|
||||||
|
|
||||||
function* gen2() {
|
|
||||||
let result = yield new Promise( // (1)
|
|
||||||
resolve => setTimeout(resolve, 1000, 'hello')
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Это -- отличный вариант для библиотеки `co`. Композиция `yield* gen()` вызывает `gen()` в потоке внешнего генератора. Аналогично делает и `yield* gen()`.
|
|
||||||
|
|
||||||
Поэтому `yield new Promise` из строки `(1)` в `gen2()` попадает напрямую в библиотеку `co`, как если бы он был сделан так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
co(function*() {
|
|
||||||
// gen() и затем gen2 встраиваются во внешний генератор
|
|
||||||
let result = yield new Promise(
|
|
||||||
resolve => setTimeout(resolve, 1000, 'hello')
|
|
||||||
);
|
|
||||||
alert(result); // hello
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Пример `showUserAvatar()` можно переписать с использованием `co` вот так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Загрузить данные пользователя с нашего сервера
|
|
||||||
function* fetchUser(url) {
|
|
||||||
let userFetch = yield fetch(url);
|
|
||||||
|
|
||||||
let user = yield userFetch.json();
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загрузить профиль пользователя с github
|
|
||||||
function* fetchGithubUser(user) {
|
|
||||||
let githubFetch = yield fetch(`https://api.github.com/users/${user.name}`);
|
|
||||||
let githubUser = yield githubFetch.json();
|
|
||||||
|
|
||||||
return githubUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Подождать ms миллисекунд
|
|
||||||
function sleep(ms) {
|
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Использовать функции выше для получения аватара пользователя
|
|
||||||
function* fetchAvatar(url) {
|
|
||||||
|
|
||||||
let user = yield* fetchUser(url);
|
|
||||||
|
|
||||||
let githubUser = yield* fetchGithubUser(user);
|
|
||||||
|
|
||||||
return githubUser.avatar_url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Использовать функции выше для получения и показа аватара
|
|
||||||
function* showUserAvatar() {
|
|
||||||
|
|
||||||
let avatarUrl;
|
|
||||||
|
|
||||||
try {
|
|
||||||
avatarUrl = yield* fetchAvatar('/article/generator/user.json');
|
|
||||||
} catch(e) {
|
|
||||||
avatarUrl = '/article/generator/anon.png';
|
|
||||||
}
|
|
||||||
|
|
||||||
let img = new Image();
|
|
||||||
img.src = avatarUrl;
|
|
||||||
img.className = "promise-avatar-example";
|
|
||||||
document.body.appendChild(img);
|
|
||||||
|
|
||||||
yield sleep(2000);
|
|
||||||
|
|
||||||
img.remove();
|
|
||||||
|
|
||||||
return img.src;
|
|
||||||
}
|
|
||||||
|
|
||||||
co(showUserAvatar);
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, что для перехвата ошибок при получении аватара используется `try..catch` вокруг `yield* fetchAvatar`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
try {
|
|
||||||
avatarUrl = yield* fetchAvatar('/article/generator/user.json');
|
|
||||||
} catch(e) {
|
|
||||||
avatarUrl = '/article/generator/anon.png';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Это -- одно из главных удобств использования генераторов. Несмотря на то, что операции `fetch` -- асинхронные, мы можем использовать обычный `try..catch` для обработки ошибок в них.
|
|
||||||
|
|
||||||
## Для генераторов -- только yield*
|
|
||||||
|
|
||||||
Библиотека `co` технически позволяет писать код так:
|
|
||||||
```js
|
|
||||||
let user = yield fetchUser(url);
|
|
||||||
// вместо
|
|
||||||
// let user = yield* fetchUser(url);
|
|
||||||
```
|
|
||||||
|
|
||||||
То есть, можно сделать `yield` генератора, `co()` его выполнит и передаст значение обратно. Как мы видели выше, библиотека `co` -- довольно всеядна. Однако, рекомендуется использовать для вызова функций-генераторов именно `yield*`.
|
|
||||||
|
|
||||||
Причин для этого несколько:
|
|
||||||
<ol>
|
|
||||||
<li>Делегирование генераторов `yield*` -- это встроенный механизм JavaScript. Вместо возвращения значения обратно в `co`, выполнения кода библиотеки... Мы просто используем возможности языка. Это правильнее.</li>
|
|
||||||
<li>Поскольку не происходит лишних вызовов, это быстрее по производительности.</li>
|
|
||||||
<li>И, наконец, пожалуй, самое приятное -- делегирование генераторов сохраняет стек.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Проиллюстрируем последнее на примере:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
co(function*() {
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// при запуске в стеке не будет видно этой строки
|
|
||||||
yield g(); // (*)
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
}).catch(function(err) {
|
|
||||||
alert(err.stack);
|
|
||||||
});
|
|
||||||
|
|
||||||
function* g() {
|
|
||||||
throw new Error("my error");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
При запуске этого кода стек может выглядеть примерно так:
|
|
||||||
```js
|
|
||||||
*!*
|
|
||||||
at g (eval at runJS …, <anonymous>:13:9)
|
|
||||||
*/!*
|
|
||||||
at GeneratorFunctionPrototype.next (native)
|
|
||||||
at onFulfilled (…/co/…/index.min.js:1:1136)
|
|
||||||
at …/co/…/index.min.js:1:1076
|
|
||||||
at co (…/co/…/index.min.js:1:1039)
|
|
||||||
at toPromise (…/co/…/index.min.js:1:1740)
|
|
||||||
at next (…/co/…/index.min.js:1:1351)
|
|
||||||
at onFulfilled (…/co/…/index.min.js:1:1172)
|
|
||||||
at …/co/…/index.min.js:1:1076
|
|
||||||
at co (…/co/…/index.min.js:1:1039)
|
|
||||||
```
|
|
||||||
|
|
||||||
Детали здесь не имеют значения, самое важное -- почти весь стек находится внутри библиотеки `co`.
|
|
||||||
|
|
||||||
Из оригинального скрипта там только одна строка (первая):
|
|
||||||
```js
|
|
||||||
at g (eval at runJS …, <anonymous>:13:9)
|
|
||||||
```
|
|
||||||
|
|
||||||
То есть, стек говорит, что ошибка возникла в строке `13`:
|
|
||||||
```js
|
|
||||||
// строка 13 из кода выше
|
|
||||||
throw new Error("my error");
|
|
||||||
```
|
|
||||||
|
|
||||||
Что ж, спасибо. Но как мы оказались на этой строке? Об этом в стеке нет ни слова!
|
|
||||||
|
|
||||||
Заменим в строке `(*)` вызов `yield` на `yield*`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
co(function*() {
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// заменили yield на yield*
|
|
||||||
yield* g(); // (*)
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
}).catch(function(err) {
|
|
||||||
alert(err.stack);
|
|
||||||
});
|
|
||||||
|
|
||||||
function* g() {
|
|
||||||
throw new Error("my error");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Пример стека теперь:
|
|
||||||
```js
|
|
||||||
*!*
|
|
||||||
at g (eval at runJS …, <anonymous>:13:9)
|
|
||||||
*/!*
|
|
||||||
at GeneratorFunctionPrototype.next (native)
|
|
||||||
*!*
|
|
||||||
at eval (eval at runJS …, <anonymous>:6:10)
|
|
||||||
*/!*
|
|
||||||
at GeneratorFunctionPrototype.next (native)
|
|
||||||
at onFulfilled (…/co/…/index.min.js:1:1136)
|
|
||||||
at …/co/…/index.min.js:1:1076
|
|
||||||
at co (…/co/…/index.min.js:1:1039)
|
|
||||||
*!*
|
|
||||||
at eval (eval at runJS …, <anonymous>:3:1)
|
|
||||||
*/!*
|
|
||||||
at eval (native)
|
|
||||||
at runJS (…)
|
|
||||||
```
|
|
||||||
|
|
||||||
Если очистить от вспомогательных вызовов, то эти строки -- как раз то, что нам надо:
|
|
||||||
```js
|
|
||||||
at g (eval at runJS …, <anonymous>:13:9)
|
|
||||||
at eval (eval at runJS …, <anonymous>:6:10)
|
|
||||||
at eval (eval at runJS …, <anonymous>:3:1)
|
|
||||||
```
|
|
||||||
|
|
||||||
Теперь видно, что (читаем снизу) исходный вызов был в строке `3`, далее -- вложенный в строке `6`, и затем произошла ошибка была в строке `13`.
|
|
||||||
|
|
||||||
Почему вариант с простым `yield` не работает -- достаточно очевидно, если внимательно посмотреть на код и воспроизвести в уме, как он работает. Оставляем это упражнение вдумчивому читателю.
|
|
||||||
|
|
||||||
Итого, рекомендация уже достаточно обоснована -- при запуске вложенных генераторов используем `yield*`.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Генераторы создаются при помощи функций-генераторов `function*(…) {…}`.</li>
|
|
||||||
<li>Внутри генераторов и только них разрешён оператор `yield`. Это иногда создаёт недобства, поскольку в коллбэках `.map/.forEach` сделать `yield` нельзя. Впрочем, можно сделать `yield` массива (при использовании `co`).</li>
|
|
||||||
<li>Внешний код и генератор обмениваются промежуточными результатами посредством вызовов `next/yield`.</li>
|
|
||||||
<li>Генераторы позволяют писать плоский асинхронный код, при помощи библиотки `co`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Что касается кросс-браузерной поддержки -- она стремительно приближается. Пока же можно использовать генераторы вместе с [Babel](https://babeljs.io).
|
|
||||||
|
|
||||||
[head]
|
|
||||||
<style>
|
|
||||||
.promise-avatar-example {
|
|
||||||
border-radius: 50%;
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/co/4.1.0/index.min.js"></script>
|
|
||||||
[/head]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 9 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 29 KiB |
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"name": "iliakan",
|
|
||||||
"isAdmin": true
|
|
||||||
}
|
|
|
@ -1,209 +0,0 @@
|
||||||
# Переменные: let и const
|
|
||||||
|
|
||||||
В ES-2015 предусмотрены новые способы объявления переменных: через `let` и `const` вместо `var`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
```js
|
|
||||||
let a = 5;
|
|
||||||
```
|
|
||||||
|
|
||||||
## let
|
|
||||||
|
|
||||||
У объявлений переменной через `let` есть три основных отличия от `var`:
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>**Область видимости переменной `let` -- блок `{...}`.**
|
|
||||||
|
|
||||||
Как мы помним, переменная, объявленная через `var`, видна везде в функции.
|
|
||||||
|
|
||||||
Переменная, объявленная через `let`, видна только в рамках блока `{...}`, в котором объявлена.
|
|
||||||
|
|
||||||
Это, в частности, влияет на объявления внутри `if`, `while` или `for`.
|
|
||||||
|
|
||||||
Например, переменная через `var`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var apples = 5;
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
var apples = 10;
|
|
||||||
|
|
||||||
alert(apples); // 10 (внутри блока)
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(apples); // 10 (снаружи блока то же самое)
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше `apples` -- одна переменная на весь код, которая модифицируется в `if`.
|
|
||||||
|
|
||||||
То же самое с `let` будет работать по-другому:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let apples = 5; // (*)
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
let apples = 10;
|
|
||||||
|
|
||||||
alert(apples); // 10 (внутри блока)
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
alert(apples); // 5 (снаружи блока значение не изменилось)
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Здесь, фактически, две независимые переменные `apples`, одна -- глобальная, вторая -- в блоке `if`.
|
|
||||||
|
|
||||||
Заметим, что если объявление `let apples` в первой строке `(*)` удалить, то в последнем `alert` будет ошибка: переменная неопределена:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
let apples = 10;
|
|
||||||
|
|
||||||
alert(apples); // 10 (внутри блока)
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
alert(apples); // ошибка!
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Это потому что переменная `let` всегда видна именно в том блоке, где объявлена, и не более.
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li>**Переменная `let` видна только после объявления.**
|
|
||||||
|
|
||||||
Как мы помним, переменные `var` существуют и до объявления. Они равны `undefined`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
alert(a); // undefined
|
|
||||||
|
|
||||||
var a = 5;
|
|
||||||
```
|
|
||||||
|
|
||||||
С переменными `let` всё проще. До объявления их вообще нет.
|
|
||||||
|
|
||||||
Такой доступ приведёт к ошибке:
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
alert(a); // ошибка, нет такой переменной
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
let a = 5;
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим также, что переменные `let` нельзя повторно объявлять. То есть, такой код выведет ошибку:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let x;
|
|
||||||
let x; // ошибка: переменная x уже объявлена
|
|
||||||
```
|
|
||||||
|
|
||||||
Это -- хоть и выглядит ограничением по сравнению с `var`, но на самом деле проблем не создаёт. Например, два таких цикла совсем не конфликтуют:
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// каждый цикл имеет свою переменную i
|
|
||||||
for(let i = 0; i<10; i++) { /* … */ }
|
|
||||||
for(let i = 0; i<10; i++) { /* … */ }
|
|
||||||
|
|
||||||
alert( i ); // ошибка: глобальной i нет
|
|
||||||
```
|
|
||||||
|
|
||||||
При объявлении внутри цикла переменная `i` будет видна только в блоке цикла. Она не видна снаружи, поэтому будет ошибка в последнем `alert`.
|
|
||||||
|
|
||||||
|
|
||||||
</li>
|
|
||||||
<li>**При использовании в цикле, для каждой итерации создаётся своя переменная.**
|
|
||||||
|
|
||||||
Переменная `var` -- одна на все итерации цикла и видна даже после цикла:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
for(var i=0; i<10; i++) { /* … */ }
|
|
||||||
|
|
||||||
alert(i); // 10
|
|
||||||
```
|
|
||||||
|
|
||||||
С переменной `let` -- всё по-другому.
|
|
||||||
|
|
||||||
Каждому повторению цикла соответствует своя независимая переменная `let`. Если внутри цикла есть вложенные объявления функций, то в замыкании каждой будет та переменная, которая была при соответствующей итерации.
|
|
||||||
|
|
||||||
Это позволяет легко решить классическую проблему с замыканиями, описанную в задаче [](/task/make-army).
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function makeArmy() {
|
|
||||||
|
|
||||||
let shooters = [];
|
|
||||||
|
|
||||||
for (*!*let*/!* i = 0; i < 10; i++) {
|
|
||||||
shooters.push(function() {
|
|
||||||
alert( i ); // выводит свой номер
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return shooters;
|
|
||||||
}
|
|
||||||
|
|
||||||
var army = makeArmy();
|
|
||||||
|
|
||||||
army[0](); // 0
|
|
||||||
army[5](); // 5
|
|
||||||
```
|
|
||||||
|
|
||||||
Если бы объявление было `var i`, то была бы одна переменная `i` на всю функцию, и вызовы в последних строках выводили бы `10` (подробнее -- см. задачу [](/task/make-army)).
|
|
||||||
|
|
||||||
А выше объявление `let i` создаёт для каждого повторения блока в цикле свою переменную, которую функция и получает из замыкания в последних строках.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
## const
|
|
||||||
|
|
||||||
Объявление `const` задаёт константу, то есть переменную, которую нельзя менять:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const apple = 5;
|
|
||||||
apple = 10; // ошибка
|
|
||||||
```
|
|
||||||
|
|
||||||
В остальном объявление `const` полностью аналогично `let`.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
|
|
||||||
Переменные `let`:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Видны только после объявления и только в текущем блоке.</li>
|
|
||||||
<li>Нельзя переобъявлять (в том же блоке).</li>
|
|
||||||
<li>При объявлении переменной в цикле `for(let …)` -- она видна только в этом цикле. Причём каждой итерации соответствует своя переменная `let`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Переменная `const` -- это константа, в остальном -- как `let`.
|
|
||||||
|
|
|
@ -1,324 +0,0 @@
|
||||||
|
|
||||||
# Деструктуризация
|
|
||||||
|
|
||||||
*Деструктуризация* (destructuring assignment) -- это особый синтаксис присваивания, при котором можно присвоить массив или объект сразу нескольким переменным, разбив его на части.
|
|
||||||
|
|
||||||
## Массив
|
|
||||||
|
|
||||||
Пример деструктуризации массива:
|
|
||||||
|
|
||||||
```js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let [firstName, lastName] = ["Илья", "Кантор"];
|
|
||||||
|
|
||||||
alert(firstName); // Илья
|
|
||||||
alert(lastName); // Кантор
|
|
||||||
```
|
|
||||||
|
|
||||||
При таком присвоении первое значение массива пойдёт в переменную `firstName`, второе -- в `lastName`, а последующие (если есть) -- будут отброшены.
|
|
||||||
|
|
||||||
Ненужные элементы массива также можно отбросить, поставив лишнюю запятую:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// первый и второй элементы не нужны
|
|
||||||
let [, , title] = "Юлий Цезарь Император Рима".split(" ");
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(title); // Император
|
|
||||||
```
|
|
||||||
|
|
||||||
В коде выше первый и второй элементы массива никуда не записались, они были отброшены. Как, впрочем, и все элементы после третьего.
|
|
||||||
|
|
||||||
### Оператор "spread"
|
|
||||||
|
|
||||||
Если мы хотим получить и последующие значения массива, но не уверены в их числе -- можно добавить ещё один параметр, который получит "всё остальное", при помощи оператора `"..."` ("spread", троеточие):
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let [firstName, lastName, ...rest] = "Юлий Цезарь Император Рима".split(" ");
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(firstName); // Юлий
|
|
||||||
alert(lastName); // Цезарь
|
|
||||||
alert(rest); // Император,Рима (массив из 2х элементов)
|
|
||||||
```
|
|
||||||
|
|
||||||
Значением `rest` будет массив из оставшихся элементов массива. Вместо `rest` можно использовать и другое имя переменной, оператор здесь -- троеточие. Оно должно стоять только последним элементом в списке слева.
|
|
||||||
|
|
||||||
### Значения по умолчанию
|
|
||||||
|
|
||||||
Если значений в массиве меньше, чем переменных -- ошибки не будет, просто присвоится `undefined`:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let [firstName, lastName] = [];
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(firstName); // undefined
|
|
||||||
```
|
|
||||||
|
|
||||||
Впрочем, как правило, в таких случаях задают значение по умолчанию. Для этого нужно после переменной использовать символ `=` со значением, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// значения по умолчанию
|
|
||||||
let [firstName="Гость", lastName="Анонимный"] = [];
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(firstName); // Гость
|
|
||||||
alert(lastName); // Анонимный
|
|
||||||
```
|
|
||||||
|
|
||||||
В качестве значений по умолчанию можно использовать не только примитивы, но и выражения, даже включающие в себя вызовы функций:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function defaultLastName() {
|
|
||||||
return Date.now() + '-visitor';
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// lastName получит значение, соответствующее текущей дате:
|
|
||||||
let [firstName, lastName=defaultLastName()] = ["Вася"];
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(firstName); // Вася
|
|
||||||
alert(lastName); // 1436...-visitor
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, что вызов функции `defaultLastName()` для генерации значения по умолчанию будет осуществлён только при необходимости, то есть если значения нет в массиве.
|
|
||||||
|
|
||||||
## Деструктуризация объекта
|
|
||||||
|
|
||||||
Деструктуризацию можно использовать и с объектами. При этом мы указываем, какие свойства в какие переменные должны "идти".
|
|
||||||
|
|
||||||
Базовый синтаксис:
|
|
||||||
```js
|
|
||||||
let {var1, var2} = {var1:…, var2…}
|
|
||||||
```
|
|
||||||
|
|
||||||
Объект справа -- уже существующий, который мы хотим разбить на переменные. А слева -- список переменных, в которые нужно соответствующие свойства записать.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
title: "Меню",
|
|
||||||
width: 100,
|
|
||||||
height: 200
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let {title, width, height} = options;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(title); // Меню
|
|
||||||
alert(width); // 100
|
|
||||||
alert(height); // 200
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно, свойства `options.title`, `options.width` и `options.height` автоматически присвоились соответствующим переменным.
|
|
||||||
|
|
||||||
Если хочется присвоить свойство объекта в переменную с другим именем, например, чтобы свойство `options.width` пошло в переменную `w`, то можно указать соответствие через двоеточие, вот так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
title: "Меню",
|
|
||||||
width: 100,
|
|
||||||
height: 200
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let {width: w, height: h, title} = options;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(title); // Меню
|
|
||||||
alert(w); // 100
|
|
||||||
alert(h); // 200
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше свойство `width` отправилось в переменную `w`, свойство `height` -- в переменную `h`, а `title` -- в переменную с тем же названием.
|
|
||||||
|
|
||||||
Если каких-то свойств в объекте нет, можно указать значение по умолчанию через знак равенства `=`, вот так;
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
title: "Меню"
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let {width=100, height=200, title} = options;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(title); // Меню
|
|
||||||
alert(width); // 100
|
|
||||||
alert(height); // 200
|
|
||||||
```
|
|
||||||
|
|
||||||
Можно и сочетать одновременно двоеточие и равенство:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
title: "Меню"
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let {width:w=100, height:h=200, title} = options;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(title); // Меню
|
|
||||||
alert(w); // 100
|
|
||||||
alert(h); // 200
|
|
||||||
```
|
|
||||||
|
|
||||||
А что, если в объекте больше значений, чем переменных? Можно ли куда-то присвоить "остаток", аналогично массивам?
|
|
||||||
|
|
||||||
Такой возможности в текущем стандарте нет. Она планируется в будущем стандарте, и выглядеть она будет примерно так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
title: "Меню",
|
|
||||||
width: 100,
|
|
||||||
height: 200
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let {title, ...size} = options;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
// title = "Меню"
|
|
||||||
// size = { width: 100, height: 200} (остаток)
|
|
||||||
```
|
|
||||||
|
|
||||||
Этот код будет работать, например, при использовании Babel со включёнными экспериментальными возможностями, но ещё раз заметим, что в текущий стандарт такая возможность не вошла.
|
|
||||||
|
|
||||||
[smart header="Деструктуризация без объявления"]
|
|
||||||
|
|
||||||
В примерах выше переменные объявлялись прямо перед присваиванием: `let {…} = {…}`. Конечно, можно и без `let`, использовать уже существующие переменные.
|
|
||||||
|
|
||||||
Однако, здесь есть небольшой "подвох". В JavaScript, если в основном потоке кода (не внутри другого выражения) встречается `{...}`, то это воспринимается как блок.
|
|
||||||
|
|
||||||
Например, можно использовать такой блок для ограничения видимости переменных:
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
{
|
|
||||||
// вспомогательные переменные, локальные для блока
|
|
||||||
let a = 5;
|
|
||||||
// поработали с ними
|
|
||||||
alert(a); // 5
|
|
||||||
// больше эти переменные не нужны
|
|
||||||
}
|
|
||||||
alert(a); // ошибка нет такой переменной
|
|
||||||
```
|
|
||||||
|
|
||||||
Конечно, это бывает удобно, но в данном случае это создаст проблему при деструктуризации:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let a, b;
|
|
||||||
{a, b} = {a:5, b:6}; // будет ошибка, оно посчитает, что {a,b} - блок
|
|
||||||
```
|
|
||||||
|
|
||||||
Чтобы избежать интерпретации `{a, b}` как блока, нужно обернуть всё присваивание в скобки:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let a, b;
|
|
||||||
({a, b} = {a:5, b:6}); // внутри выражения это уже не блок
|
|
||||||
```
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
## Вложенные деструктуризации
|
|
||||||
|
|
||||||
Если объект или массив содержат другие объекты или массивы, и их тоже хочется разбить на переменные -- не проблема.
|
|
||||||
|
|
||||||
Деструктуризации можно как угодно сочетать и вкладывать друг в друга.
|
|
||||||
|
|
||||||
В коде ниже `options` содержит подобъект и подмассив. В деструктуризации ниже сохраняется та же структура:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
size: {
|
|
||||||
width: 100,
|
|
||||||
height: 200
|
|
||||||
},
|
|
||||||
items: ["Пончик", "Пирожное"]
|
|
||||||
}
|
|
||||||
|
|
||||||
let { title="Меню", size: {width, height}, items: [item1, item2] } = options;
|
|
||||||
|
|
||||||
// Меню 100 200 Пончик Пирожное
|
|
||||||
alert(title); // Меню
|
|
||||||
alert(width); // 100
|
|
||||||
alert(height); // 200
|
|
||||||
alert(item1); // Пончик
|
|
||||||
alert(item2); // Пирожное
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно, весь объект `options` корректно разбит на переменные.
|
|
||||||
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Деструктуризация позволяет разбивать объект или массив на переменные при присвоении.</li>
|
|
||||||
<li>Синтаксис:
|
|
||||||
```js
|
|
||||||
let {prop : varName = default, ...} = object
|
|
||||||
```
|
|
||||||
|
|
||||||
Здесь двоеточие `:` задаёт отображение свойства `prop` в переменную `varName`, а равенство `=default` задаёт выражение, которое будет использовано, если значение отсутствует (не указано или `undefined`).
|
|
||||||
|
|
||||||
Для массивов имеет значение порядок, поэтому нельзя использовать `:`, но значение по умолчанию -- можно:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let [var1 = default, var2, ...rest] = array
|
|
||||||
```
|
|
||||||
|
|
||||||
Объявление переменной в начале конструкции не обязательно. Можно использовать и существующие переменные. Однако при деструктуризации объекта может потребоваться обернуть выражение в скобки.
|
|
||||||
</li>
|
|
||||||
<li>Вложенные объекты и массивы тоже работают, при деструктуризации нужно лишь сохранить ту же структуру, что и исходный объект/массив.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Как мы увидим далее, деструктуризации особенно пригодятся удобны при чтении объектных параметров функций.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,469 +0,0 @@
|
||||||
|
|
||||||
# Функции
|
|
||||||
|
|
||||||
В функциях основные изменения касаются передачи параметров, плюс введена дополнительная короткая запись через стрелочку `=>`.
|
|
||||||
|
|
||||||
## Параметры по умолчанию
|
|
||||||
|
|
||||||
Можно указывать параметры по умолчанию через равенство `=`, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
function showMenu(title = "Без заголовка", width = 100, height = 200) {
|
|
||||||
alert(`${title} ${width} ${height}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
showMenu("Меню"); // Меню 100 200
|
|
||||||
```
|
|
||||||
|
|
||||||
Параметр по умолчанию используется при отсутствующем аргументе или равном `undefined`, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
function showMenu(title = "Заголовок", width = 100, height = 200) {
|
|
||||||
alert(`title=${title} width=${width} height=${height}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// По умолчанию будут взяты 1 и 3 параметры
|
|
||||||
// title=Заголовок width=null height=200
|
|
||||||
showMenu(undefined, null);
|
|
||||||
```
|
|
||||||
|
|
||||||
При передаче любого значения, кроме `undefined`, включая пустую строку, ноль или `null`, параметр считается переданным, и значение по умолчание не используется.
|
|
||||||
|
|
||||||
**Параметры по умолчанию могут быть не только значениями, но и выражениями.**
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
function sayHi(who = getCurrentUser().toUpperCase()) {
|
|
||||||
alert(`Привет, ${who}!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCurrentUser() {
|
|
||||||
return 'Вася';
|
|
||||||
}
|
|
||||||
|
|
||||||
sayHi(); // Привет, ВАСЯ!
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, что значение выражения `getCurrentUser().toUpperCase()` будет вычислено, и соответствующие функции вызваны -- лишь в том случае, если это необходимо, то есть когда функция вызвана без параметра.
|
|
||||||
|
|
||||||
В частности, выражение по умолчанию не вычисляется при объявлении функции. В примере выше функция `getCurrentUser()` будет вызвана именно в последней строке, так как не передан параметр.
|
|
||||||
|
|
||||||
|
|
||||||
## Оператор spread вместо arguments
|
|
||||||
|
|
||||||
Чтобы получить массив аргументов, можно использовать оператор `…`, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
|
|
||||||
function showName(firstName, lastName, *!*...rest*/!*) {
|
|
||||||
alert(`${firstName} ${lastName} - ${rest}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// выведет: Юлий Цезарь - Император,Рима
|
|
||||||
showName("Юлий", "Цезарь", "Император", "Рима");
|
|
||||||
```
|
|
||||||
|
|
||||||
В `rest` попадёт массив всех аргументов, начиная со второго.
|
|
||||||
|
|
||||||
Заметим, что `rest` -- настоящий массив, с методами `map`, `forEach` и другими, в отличие от `arguments`.
|
|
||||||
|
|
||||||
[warn header="Оператор … должен быть в конце"]
|
|
||||||
|
|
||||||
Оператор `…` собирает "все оставшиеся" аргументы, поэтому такое объявление не имеет смысла:
|
|
||||||
```js
|
|
||||||
function f(arg1, ...rest, arg2) { // arg2 после ...rest ?!
|
|
||||||
// будет ошибка
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Параметр `...rest` должен быть в конце функции.
|
|
||||||
[/warn]
|
|
||||||
|
|
||||||
|
|
||||||
Выше мы увидели использование `...` для чтения параметров в объявлении функции. Но этот же оператор можно использовать и при вызове функции, для передачи массива параметров как списка, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let numbers = [2, 3, 15];
|
|
||||||
|
|
||||||
// Оператор ... в вызове передаст массив как список аргументов
|
|
||||||
// Этот вызов аналогичен Math.max(2, 3, 15)
|
|
||||||
let max = Math.max(*!*...numbers*/!*);
|
|
||||||
|
|
||||||
alert( max ); // 15
|
|
||||||
```
|
|
||||||
|
|
||||||
Формально говоря, эти два вызова делают одно и то же:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Math.max(...numbers);
|
|
||||||
Math.max.apply(Math, numbers);
|
|
||||||
```
|
|
||||||
|
|
||||||
Похоже, что первый -- короче и красивее.
|
|
||||||
|
|
||||||
## Деструктуризация в параметрах
|
|
||||||
|
|
||||||
Если функция получает объект, то она может его тут же разбить в переменные:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
title: "Меню",
|
|
||||||
width: 100,
|
|
||||||
height: 200
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
function showMenu({title, width, height}) {
|
|
||||||
*/!*
|
|
||||||
alert(`${title} ${width} ${height}`); // Меню 100 200
|
|
||||||
}
|
|
||||||
|
|
||||||
showMenu(options);
|
|
||||||
```
|
|
||||||
|
|
||||||
Можно использовать и более сложную деструктуризацию, с соответствиями и значениями по умолчанию:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
title: "Меню"
|
|
||||||
};
|
|
||||||
|
|
||||||
*!*
|
|
||||||
function showMenu({title="Заголовок", width:w=100, height:h=200}) {
|
|
||||||
*/!*
|
|
||||||
alert(`${title} ${w} ${h}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// объект options будет разбит на переменные
|
|
||||||
showMenu(options); // Меню 100 200
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, что в примере выше какой-то аргумент у `showMenu()` обязательно должен быть, чтобы разбить его на переменные.
|
|
||||||
|
|
||||||
Если хочется, чтобы функция могла быть вызвана вообще без аргументов -- нужно добавить ей параметр по умолчанию -- уже не внутрь деструктуризации, а в самом списке аргументов:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function showMenu({title="Заголовок", width:w=100, height:h=200} *!*= {}*/!*) {
|
|
||||||
alert(`${title} ${w} ${h}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
showMenu(); // Заголовок 100 200
|
|
||||||
```
|
|
||||||
|
|
||||||
В коде выше весь объект аргументов по умолчанию равен пустому объекту `{}`, поэтому всегда есть что деструктуризовать.
|
|
||||||
|
|
||||||
## Имя "name"
|
|
||||||
|
|
||||||
В свойстве `name` у функции находится её имя.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function f() {} // f.name == "f"
|
|
||||||
|
|
||||||
let g = function g() {}; // g.name == "g"
|
|
||||||
|
|
||||||
alert(`${f.name} ${g.name}`) // f g
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше показаны Function Declaration и Named Function Expression. В синтаксисе выше довольно очевидно, что у этих функций есть имя `name`. В конце концов, оно указано в объявлении.
|
|
||||||
|
|
||||||
Но современный JavaScript идёт дальше, он старается даже анонимным функциям дать разумные имена.
|
|
||||||
|
|
||||||
Например, при создании анонимной функции с одновременной записью в переменную или свойство -- её имя равно названию переменной (или свойства).
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// свойство g.name = "g"
|
|
||||||
let g = function() {};
|
|
||||||
|
|
||||||
let user = {
|
|
||||||
// свойство user.sayHi.name == "sayHi"
|
|
||||||
sayHi: function() { };
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Функции в блоке
|
|
||||||
|
|
||||||
Объявление функции Function Declaration, сделанное в блоке, видно только в этом блоке.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
if (true) {
|
|
||||||
|
|
||||||
sayHi(); // работает
|
|
||||||
|
|
||||||
function sayHi() {
|
|
||||||
alert("Привет!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
sayHi(); // ошибка, функции не существует
|
|
||||||
```
|
|
||||||
|
|
||||||
То есть, иными словами, такое объявление -- ведёт себя в точности как если бы `let sayHi = function() {…}` было сделано в начале блока.
|
|
||||||
|
|
||||||
## Функции через =>
|
|
||||||
|
|
||||||
Появился новый синтаксис для задания функций через "стрелку" `=>`.
|
|
||||||
|
|
||||||
Его простейший вариант выглядит так:
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let inc = x => x+1;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( inc(1) ); // 2
|
|
||||||
```
|
|
||||||
|
|
||||||
Эти две записи -- примерно аналогичны:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let inc = x => x+1;
|
|
||||||
|
|
||||||
let inc = function(x) { return x + 1; };
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно, `"x => x+1"` -- это уже готовая функция. Слева от `=>` находится аргумент, а справа -- выражение, которое нужно вернуть.
|
|
||||||
|
|
||||||
Если аргументов несколько, то нужно обернуть их в скобки, вот так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let sum = (a,b) => a + b;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
// аналог с function
|
|
||||||
// let inc = function(a, b) { return a + b; };
|
|
||||||
|
|
||||||
alert( sum(1, 2) ); // 3
|
|
||||||
```
|
|
||||||
|
|
||||||
Если нужно задать функцию без аргументов, то также используются скобки, в этом случае -- пустые:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// вызов getTime() будет возвращать текущее время
|
|
||||||
let getTime = () => `${new Date().getHours()} : ${new Date().getMinutes()}`;
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( getTime() ); // текущее время
|
|
||||||
```
|
|
||||||
|
|
||||||
Когда тело функции достаточно большое, то можно его обернуть в фигурные скобки `{…}`:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let getTime = () => {
|
|
||||||
let date = new Date();
|
|
||||||
let hours = date.getHours();
|
|
||||||
let minutes = date.getMinutes();
|
|
||||||
return `${hours}:${minutes}`;
|
|
||||||
};
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert( getTime() ); // текущее время
|
|
||||||
```
|
|
||||||
|
|
||||||
Заметим, что как только тело функции оборачивается в `{…}`, то её результат уже не возвращается автоматически. Такая функция должна делать явный `return`, как в примере выше, если конечно хочет что-либо возвратить.
|
|
||||||
|
|
||||||
Функции-стрелки очень удобны в качестве коллбеков, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
`use strict`;
|
|
||||||
|
|
||||||
let arr = [5, 8, 3];
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let sorted = arr.sort( (a,b) => a - b );
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
alert(sorted); // 3, 5, 8
|
|
||||||
```
|
|
||||||
|
|
||||||
Такая запись -- коротка и понятна. Далее мы познакомимся с дополнительными преимуществами использования функций-стрелок для этой цели.
|
|
||||||
|
|
||||||
## Функции-стрелки не имеют своего this
|
|
||||||
|
|
||||||
Внутри функций-стрелок -- тот же `this`, что и снаружи.
|
|
||||||
|
|
||||||
Это очень удобно в обработчиках событий и коллбэках, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let group = {
|
|
||||||
title: "Наш курс",
|
|
||||||
students: ["Вася", "Петя", "Даша"],
|
|
||||||
|
|
||||||
showList: function() {
|
|
||||||
*!*
|
|
||||||
this.students.forEach(
|
|
||||||
(student) => alert(`${this.title}: ${student}`)
|
|
||||||
)
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group.showList();
|
|
||||||
// Наш курс: Вася
|
|
||||||
// Наш курс: Петя
|
|
||||||
// Наш курс: Даша
|
|
||||||
```
|
|
||||||
|
|
||||||
Здесь в `forEach` была использована функция-стрелка, поэтому `this.title` в коллбэке -- тот же, что и во внешней функции `showList`. То есть, в данном случае -- `group.title`.
|
|
||||||
|
|
||||||
Если бы в `forEach` вместо функции-стрелки была обычная функция, то была бы ошибка:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let group = {
|
|
||||||
title: "Наш курс",
|
|
||||||
students: ["Вася", "Петя", "Даша"],
|
|
||||||
|
|
||||||
showList: function() {
|
|
||||||
*!*
|
|
||||||
this.students.forEach(function(student) {
|
|
||||||
alert(`${this.title}: ${student}`); // будет ошибка
|
|
||||||
})
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group.showList();
|
|
||||||
```
|
|
||||||
|
|
||||||
При запуске будет "попытка прочитать свойство `title` у `undefined`", так как `.forEach(f)` при запуске `f` не ставит `this`. То есть, `this` внутри `forEach` будет `undefined`.
|
|
||||||
|
|
||||||
[warn header="Функции стрелки нельзя запускать с `new`"]
|
|
||||||
Отсутствие у функции-стрелки "своего `this`" влечёт за собой естественное ограничение: такие функции нельзя использовать в качестве конструктора, то есть нельзя вызывать через `new`.
|
|
||||||
[/warn]
|
|
||||||
|
|
||||||
## Функции-стрелки не имеют своего arguments
|
|
||||||
|
|
||||||
В качестве `arguments` используются аргументы внешней "обычной" функции.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function f() {
|
|
||||||
let showArg = () => alert(arguments[0]);
|
|
||||||
showArg();
|
|
||||||
}
|
|
||||||
|
|
||||||
f(1); // 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Вызов `showArg()` выведет `1`, получив его из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` всегда берутся из внешней "обычной" функции.
|
|
||||||
|
|
||||||
Сохранение внешнего `this` и `arguments` удобно использовать для форвардинга вызовов и создания декораторов.
|
|
||||||
|
|
||||||
Например, декоратор `defer(f, ms)` ниже получает функцию `f` и возвращает обёртку вокруг неё, откладывающую вызов на `ms` миллисекунд:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
*!*
|
|
||||||
function defer(f, ms) {
|
|
||||||
return function() {
|
|
||||||
setTimeout(() => f.apply(this, arguments), ms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
function sayHi(who) {
|
|
||||||
alert(`Привет, ${who}!`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sayHiDeferred = defer(sayHi, 2000);
|
|
||||||
sayHiDeferred("Вася"); // Привет, Вася! через 2 секунды
|
|
||||||
```
|
|
||||||
|
|
||||||
Аналогичная реализация без функции-стрелки выглядела бы так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function defer(f, ms) {
|
|
||||||
return function() {
|
|
||||||
*!*
|
|
||||||
let args = arguments;
|
|
||||||
let ctx = this;
|
|
||||||
*/!*
|
|
||||||
setTimeout(function() {
|
|
||||||
return f.apply(ctx, args);
|
|
||||||
}, ms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
В этом коде пришлось создавать дополнительные переменные `args` и `ctx` для передачи внешних аргументов и контекста через замыкание.
|
|
||||||
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
Основные улучшения в функциях:
|
|
||||||
<ul>
|
|
||||||
<li>Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.</li>
|
|
||||||
<li>Оператор spread (троеточие) в объявлении позволяет функции получать оставшиеся аргументы в массив: `function f(arg1, arg2, ...rest)`.</li>
|
|
||||||
<li>Тот же оператор spread в вызове функции позволяет передать её массив как список аргументов (вместо `apply`).</li>
|
|
||||||
<li>У функции есть свойство `name`, оно содержит имя, указанное при объявлении функции, либо, если его нет, то имя свойства или переменную, в которую она записана. Есть и некоторые другие ситуации, в которых интерпретатор подставляет "самое подходящее" имя.</li>
|
|
||||||
<li>Объявление Function Declaration в блоке `{...}` видно только в этом блоке.</li>
|
|
||||||
<li>Появились функции-стрелки:
|
|
||||||
<ul>
|
|
||||||
<li>Без фигурных скобок возвращают выражение `expr`: `(args) => expr`.</li>
|
|
||||||
<li>С фигурными скобками требуют явного `return`.</li>
|
|
||||||
<li>Сохраняют `this` и `arguments` окружающего контекста.</li>
|
|
||||||
<li>Не могут быть использованы как конструкторы, с `new`.</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,332 +0,0 @@
|
||||||
|
|
||||||
# Строки
|
|
||||||
|
|
||||||
Есть ряд улучшений и новых методов для строк.
|
|
||||||
|
|
||||||
Начнём с, пожалуй, самого важного.
|
|
||||||
|
|
||||||
## Строки-шаблоны
|
|
||||||
|
|
||||||
Добавлен новый вид кавычек для строк:
|
|
||||||
```js
|
|
||||||
let str = `обратные кавычки`;
|
|
||||||
```
|
|
||||||
|
|
||||||
Основные отличия от двойных `"…"` и одинарных `'…'` кавычек:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>**В них разрешён перевод строки.**
|
|
||||||
|
|
||||||
Например:
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert(`моя
|
|
||||||
многострочная
|
|
||||||
строка`);
|
|
||||||
```
|
|
||||||
Заметим, что пробелы и, собственно, перевод строки также входят в строку, и будут выведены.
|
|
||||||
</li>
|
|
||||||
<li>**Можно вставлять выражения при помощи `${…}`.**
|
|
||||||
|
|
||||||
Например:
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
let apples = 2;
|
|
||||||
let oranges = 3;
|
|
||||||
|
|
||||||
alert(`${apples} + ${oranges} = ${apples + oranges}`); // 2 + 3 = 5
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно, при помощи `${…}` можно вставлять как и значение переменной `${apples}`, так и более сложные выражения, которые могут включать в себя операторы, вызовы функций и т.п. Такую вставку называют "интерполяцией".
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
## Функции шаблонизации
|
|
||||||
|
|
||||||
Можно использовать свою функцию шаблонизации для строк.
|
|
||||||
|
|
||||||
Название этой функции ставится перед первой обратной кавычкой:
|
|
||||||
```js
|
|
||||||
let str = func`моя строка`;
|
|
||||||
```
|
|
||||||
|
|
||||||
Эта функция будет автоматически вызвана и получит в качестве аргументов строку, разбитую по вхождениям параметров `${…}` и сами эти параметры.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function f(strings, ...values) {
|
|
||||||
alert(JSON.stringify(strings)); // ["Sum of "," + "," =\n ","!"]
|
|
||||||
alert(JSON.stringify(strings.raw)); // ["Sum of "," + "," =\\n ","!"]
|
|
||||||
alert(JSON.stringify(values)); // [3,5,8]
|
|
||||||
}
|
|
||||||
|
|
||||||
let apples = 3;
|
|
||||||
let oranges = 5;
|
|
||||||
|
|
||||||
// | s[0] | v[0] |s[1]| v[1] |s[2] | v[2] |s[3]
|
|
||||||
let str = f`Sum of ${apples} + ${oranges} =\n ${apples + oranges}!`;
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше видно, что строка разбивается по очереди на части: "кусок строки" -- "параметр" -- "кусок строки" -- "параметр".
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Участки строки идут в первый аргумент-массив `strings`.</li>
|
|
||||||
<li>У этого массива есть дополнительное свойство `strings.raw`. В нём находятся строки в точности как в оригинале. Это влияет на спец-символы, например в `strings` символ `\n` -- это перевод строки, а в `strings.raw` -- это именно два символа `\n`.</li>
|
|
||||||
<li>Дальнейший список аргументов функции шаблонизации -- это значения выражений в `${...}`, в данном случае их три.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
[smart header="Зачем `strings.raw`?"]
|
|
||||||
В отличие от `strings`, в `strings.raw` содержатся участки строки в "изначально введённом" виде.
|
|
||||||
|
|
||||||
То есть, если в строке находится `\n` или `\u1234` или другое особое сочетание символов, то оно таким и останется.
|
|
||||||
|
|
||||||
Это нужно в тех случаях, когда функция шаблонизации хочет произвести обработку полностью самостоятельно (свои спец. символы?). Или же когда обработка спец. символов не нужна -- например, строка содержит "обычный текст", набранный непрограммистом без учёта спец. символов.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
Как видно, функция имеет доступ ко всему: к выражениям, к участкам текста и даже, через `strings.raw` -- к оригинально введённому тексту без учёта стандартных спец. символов.
|
|
||||||
|
|
||||||
Функция шаблонизации может как-то преобразовать строку и вернуть новый результат.
|
|
||||||
|
|
||||||
В простейшем случае можно просто "склеить" полученные фрагменты в строку:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// str восстанавливает строку
|
|
||||||
function str(strings, ...values) {
|
|
||||||
let str = "";
|
|
||||||
for(let i=0; i<values.length; i++) {
|
|
||||||
str += strings[i];
|
|
||||||
str += values[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// последний кусок строки
|
|
||||||
str += strings[strings.length-1];
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
let apples = 3;
|
|
||||||
let oranges = 5;
|
|
||||||
|
|
||||||
// Sum of 3 + 5 = 8!
|
|
||||||
alert( str`Sum of ${apples} + ${oranges} = ${apples + oranges}!`);
|
|
||||||
```
|
|
||||||
|
|
||||||
Функция `str` в примере выше делает то же самое, что обычные обратные кавычки. Но, конечно, можно пойти намного дальше. Например, генерировать из HTML-строки DOM-узлы (функции шаблонизации не обязательно возвращать именно строку).
|
|
||||||
|
|
||||||
Или можно реализовать интернационализацию. В примере ниже функция `i18n` осуществляет перевод строки.
|
|
||||||
|
|
||||||
Она подбирает по строке вида `"Hello, ${name}!"` шаблон перевода `"Привет, {0}!"` (где `{0}` -- место для вставки параметра) и возвращает переведённый результат со вставленным именем `name`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let messages = {
|
|
||||||
"Hello, {0}!": "Привет, {0}!"
|
|
||||||
};
|
|
||||||
|
|
||||||
function i18n(strings, ...values) {
|
|
||||||
// По форме строки получим шаблон для поиска в messages
|
|
||||||
// На месте каждого из значений будет его номер: {0}, {1}, …
|
|
||||||
let pattern = "";
|
|
||||||
for(let i=0; i<values.length; i++) {
|
|
||||||
pattern += strings[i] + '{' + i + '}';
|
|
||||||
}
|
|
||||||
pattern += strings[strings.length-1];
|
|
||||||
// Теперь pattern = "Hello, {0}!"
|
|
||||||
|
|
||||||
let translated = messages[pattern]; // "Привет, {0}!"
|
|
||||||
|
|
||||||
// Заменит в "Привет, {0}" цифры вида {num} на values[num]
|
|
||||||
return translated.replace(/\{(\d)\}/g, (s, num) => values[num]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Пример использования
|
|
||||||
*!*
|
|
||||||
let name = "Вася";
|
|
||||||
|
|
||||||
// Перевести строку
|
|
||||||
alert( i18n`Hello, ${name}!` ); // Привет, Вася!
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
Итоговое использование выглядит довольно красиво, не правда ли?
|
|
||||||
|
|
||||||
Разумеется, эту функцию можно улучшить и расширить. Функция шаблонизации -- это своего рода "стандартный синтаксический сахар" для упрощения форматирования и парсинга строк.
|
|
||||||
|
|
||||||
## Улучшена поддержка юникода
|
|
||||||
|
|
||||||
Внутренняя кодировка строк в JavaScript -- это UTF-16, то есть под каждый символ отводится ровно два байта.
|
|
||||||
|
|
||||||
Но под всевозможные символы всех языков мира 2 байт не хватает. Поэтому бывает так, что одному символу языка соответствует два юникодных символа (итого 4 байта). Такое сочетание называют "суррогатной парой".
|
|
||||||
|
|
||||||
Самый частый пример суррогатной пары, который можно встретить в литературе -- это китайские иероглифы.
|
|
||||||
|
|
||||||
Заметим, однако, что не всякий китайский иероглиф -- суррогатная пара. Существенная часть "основного" юникод-диапазона как раз отдана под китайский язык, поэтому некоторые иероглифы -- которые в неё "влезли" -- представляются одним юникод-символом, а те, которые не поместились (реже используемые) -- двумя.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert( '我'.length ); // 1
|
|
||||||
alert( '𩷶'.length ); // 2
|
|
||||||
```
|
|
||||||
|
|
||||||
В тексте выше для первого иероглифа есть отдельный юникод-символ, и поэтому длина строки `1`, а для второго используется суррогатная пара. Соответственно, длина -- `2`.
|
|
||||||
|
|
||||||
Китайскими иероглифами суррогатные пары, естественно, не ограничиваются.
|
|
||||||
|
|
||||||
Ими представлены редкие математические символы, а также некоторые символы для эмоций, к примеру:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert( '𝒳'.length ); // 2, MATHEMATICAL SCRIPT CAPITAL X
|
|
||||||
alert( '😂'.length ); // 2, FACE WITH TEARS OF JOY
|
|
||||||
```
|
|
||||||
|
|
||||||
В современный JavaScript добавлены методы [String.fromCodePoint](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint) и [str.codePointAt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt) -- аналоги `String.fromCharCode` и `str.charCodeAt`, корректно работающие с суррогатными парами.
|
|
||||||
|
|
||||||
Например, `charCodeAt` считает суррогатную пару двумя разными символами и возвращает код каждой:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
// как будто в строке два разных символа (на самом деле один)
|
|
||||||
alert( '𝒳'.charCodeAt(0) + ' ' + '𝒳'.charCodeAt(1) ); // 55349 56499
|
|
||||||
```
|
|
||||||
|
|
||||||
...В то время как `codePointAt` возвращает его Unicode-код суррогатной пары правильно:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
// один символ с "длинным" (более 2 байт) unicode-кодом
|
|
||||||
alert( '𝒳'.codePointAt(0) ); // 119987
|
|
||||||
```
|
|
||||||
|
|
||||||
Метод `String.fromCodePoint(code)` корректно создаёт строку из "длинного кода", в отличие от старого `String.fromCharCode(code)`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
// Правильно
|
|
||||||
alert( String.fromCodePoint(119987) ); // 𝒳
|
|
||||||
// Неверно!
|
|
||||||
alert( String.fromCharCode(119987) ); // 풳
|
|
||||||
```
|
|
||||||
|
|
||||||
Более старый метод `fromCharCode` в последней строке дал неверный результат, так как он берёт только первые два байта от числа `119987` и создаёт символ из них, а остальные отбрасывает.
|
|
||||||
|
|
||||||
|
|
||||||
### \u{длинный код}
|
|
||||||
|
|
||||||
Есть и ещё синтаксическое улучшение для больших Unicode-кодов.
|
|
||||||
|
|
||||||
В JavaScript-строках давно можно вставлять символы по Unicode-коду, вот так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert( "\u2033" ); // ″, символ двойного штриха
|
|
||||||
```
|
|
||||||
|
|
||||||
Синтаксис: `\uNNNN`, где `NNNN` -- четырёхзначный шестнадцатиричный код, причём он должен быть ровно четырёхзначным.
|
|
||||||
|
|
||||||
"Лишние" цифры уже не войдут в код, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert( "\u20331" ); // Два символа: символ двойного штриха ″, а затем 1
|
|
||||||
```
|
|
||||||
|
|
||||||
Чтобы вводить более длинные коды символов, добавили запись `\u{NNNNNNNN}`, где `NNNNNNNN` -- максимально восьмизначный (но можно и меньше цифр) код.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert( "\u{20331}" ); // 𠌱, китайский иероглиф с этим кодом
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unicode-нормализация
|
|
||||||
|
|
||||||
Во многих языках есть символы, которые получаются как сочетание основного символа и какого-то значка над ним или под ним.
|
|
||||||
|
|
||||||
Например, на основе обычного символа `a` существуют символы: `àáâäãåā`. Самые часто встречающиеся подобные сочетания имеют отдельный юникодный код. Но отнюдь не все.
|
|
||||||
|
|
||||||
Для генерации произвольных сочетаний используются несколько юникодных символов: основа и один или несколько значков.
|
|
||||||
|
|
||||||
Например, если после символа `S` идёт символ "точка сверху" (код `\u0307`), то показано это будет как "S с точкой сверху" `Ṡ`.
|
|
||||||
|
|
||||||
Если нужен ещё значок над той же буквой (или под ней) -- без проблем. Просто добавляем соответствующий символ.
|
|
||||||
|
|
||||||
К примеру, если добавить символ "точка снизу" (код `\u0323`), то будет "S с двумя точками сверху и снизу" `Ṩ` .
|
|
||||||
|
|
||||||
Пример этого символа в JavaScript-строке:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert("S\u0307\u0323"); // Ṩ
|
|
||||||
```
|
|
||||||
|
|
||||||
Такая возможность добавить произвольной букве нужные значки, с одной стороны, необходима, а с другой стороны -- возникает проблемка: можно представить одинаковый с точки зрения визуального отображения и интерпретации символ -- разными сочетаниями Unicode-кодов.
|
|
||||||
|
|
||||||
Вот пример:
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert("S\u0307\u0323"); // Ṩ
|
|
||||||
alert("S\u0323\u0307"); // Ṩ
|
|
||||||
|
|
||||||
alert( "S\u0307\u0323" == "S\u0323\u0307" ); // false
|
|
||||||
```
|
|
||||||
|
|
||||||
В первой строке после основы `S` идёт сначала значок "верхняя точка", а потом -- нижняя, во второй -- наоборот. По кодам строки не равны друг другу. Но символ задают один и тот же.
|
|
||||||
|
|
||||||
|
|
||||||
С целью разрешить эту ситуацию, существует *юникодная нормализация*, при которой строки приводятся к единому, "нормальному", виду.
|
|
||||||
|
|
||||||
В современном JavaScript это делает метод [str.normalize()](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/String/normalize).
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Забавно, что в данной конкретной ситуации `normalize()` приведёт последовательность из трёх символов к одному: [\u1e68 (S с двумя точками)](http://www.fileformat.info/info/unicode/char/1e68/index.htm).
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert( "S\u0307\u0323".normalize().length ); // 1, нормализовало в один символ
|
|
||||||
alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Это, конечно, не всегда так, просто в данном случае оказалось, что именно такой символ в юникоде уже есть. Если добавить значков, то нормализация уже даст несколько символов.
|
|
||||||
|
|
||||||
Для большинства практических задач информации, данной выше, должно быть вполне достаточно, но если хочется более подробно ознакомиться с вариантами и правилами нормализации -- они описаны в приложении к стандарту юникод [Unicode Normalization Forms](http://www.unicode.org/reports/tr15/).
|
|
||||||
|
|
||||||
## Полезные методы
|
|
||||||
|
|
||||||
Добавлены ряд полезных методов общего назначения:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>[str.includes(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes) -- проверяет, включает ли одна строка в себя другую, возвращает `true/false`.</li>
|
|
||||||
<li>[str.endsWith(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith) -- возвращает `true`, если строка `str` заканчивается подстрокой `s`.</li>
|
|
||||||
<li>[str.startsWith(s)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith) -- возвращает `true`, если строка `str` начинается со строки `s`.</li>
|
|
||||||
<li>[str.repeat(times)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat) -- повторяет строку `str` `times` раз.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Конечно, всё это можно было сделать при помощи других встроенных методов, но новые методы более удобны.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
Улучшения:
|
|
||||||
<ul>
|
|
||||||
<li>Строки-шаблоны -- для удобного задания строк (многострочных, с переменными), плюс возможность использовать функцию шаблонизации для самостоятельного форматирования.</li>
|
|
||||||
<li>Юникод -- улучшена работа с суррогатными парами.</li>
|
|
||||||
<li>Полезные методы для проверок вхождения одной строки в другую.</li>
|
|
||||||
</ul>
|
|
|
@ -1,368 +0,0 @@
|
||||||
|
|
||||||
# Объекты и прототипы
|
|
||||||
|
|
||||||
В этом разделе мы рассмотрим нововведения, которые касаются именно объектов.
|
|
||||||
|
|
||||||
По классам -- чуть позже, в отдельном разделе, оно того заслуживает.
|
|
||||||
|
|
||||||
## Короткое свойство
|
|
||||||
|
|
||||||
Зачастую у нас есть переменные, например, `name` и `isAdmin`, и мы хотим использовать их в объекте.
|
|
||||||
|
|
||||||
При объявлении объекта в этом случае достаточно указать только имя свойства, а значение будет взято из переменной с таким именем.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let name = "Вася";
|
|
||||||
let isAdmin = true;
|
|
||||||
|
|
||||||
*!*
|
|
||||||
let user = {
|
|
||||||
name,
|
|
||||||
isAdmin
|
|
||||||
};
|
|
||||||
*/!*
|
|
||||||
alert( JSON.stringify(user) ); // {"name": "Вася", "isAdmin": true}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Вычисляемые свойства
|
|
||||||
|
|
||||||
В качестве имени свойства можно использовать выражение, например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let propName = "firstName";
|
|
||||||
|
|
||||||
let user = {
|
|
||||||
*!*
|
|
||||||
[propName]: "Вася"
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( user.firstName ); // Вася
|
|
||||||
```
|
|
||||||
|
|
||||||
Или даже так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let a = "Мой ";
|
|
||||||
let b = "Зелёный ";
|
|
||||||
let c = "Крокодил";
|
|
||||||
|
|
||||||
let user = {
|
|
||||||
*!*
|
|
||||||
[(a + b + c).toLowerCase()]: "Вася"
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( user["мой зелёный крокодил"] ); // Вася
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Геттер-сеттер для прототипа
|
|
||||||
|
|
||||||
В ES5 для прототипа был метод-геттер:
|
|
||||||
<ul>
|
|
||||||
<li>`Object.getPrototypeOf(obj)`</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
В ES-2015 также добавился сеттер:
|
|
||||||
<ul>
|
|
||||||
<li>`Object.setPrototypeOf(obj, newProto)`</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
...А также "узаконено" свойство `__proto__`, которое даёт прямой доступ к прототипу. Его, в качестве "нестандартного", но удобного способа работы с прототипом реализовали почти все браузеры (кроме IE10-), так что было принято решение добавить его в стандарт.
|
|
||||||
|
|
||||||
## Object.assign
|
|
||||||
|
|
||||||
Функция `Object.assign` получает список объектов и копирует в первый `target` свойства из остальных.
|
|
||||||
|
|
||||||
Синтаксис:
|
|
||||||
```js
|
|
||||||
Object.assign(target, src1, src2...)
|
|
||||||
```
|
|
||||||
|
|
||||||
При этом последующие свойства перезаписывают предыдущие.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let user = { name: "Вася" };
|
|
||||||
let visitor = { isAdmin: false, visits: true };
|
|
||||||
let admin = { isAdmin: true };
|
|
||||||
|
|
||||||
Object.assign(user, visitor, admin);
|
|
||||||
|
|
||||||
// user <- visitor <- admin
|
|
||||||
alert( JSON.stringify(user) ); // user: Вася, visits: true, isAdmin: true
|
|
||||||
```
|
|
||||||
|
|
||||||
Его также можно использовать для 1-уровневого клонирования объекта:
|
|
||||||
|
|
||||||
```js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let user = { name: "Вася", isAdmin: false };
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// clone = пустой объект + все свойства user
|
|
||||||
let clone = Object.assign({}, user);
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Object.is(value1, value2)
|
|
||||||
|
|
||||||
Новая функция для проверки равенства значений.
|
|
||||||
|
|
||||||
Возвращает `true`, если значения `value1` и `value2` равны, иначе `false`.
|
|
||||||
|
|
||||||
Она похожа на обычное строгое равенство `===`, но есть отличия:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
|
|
||||||
// Сравнение +0 и -0
|
|
||||||
alert( Object.is(+0, -0)); // false
|
|
||||||
alert( +0 === -0 ); // true
|
|
||||||
|
|
||||||
// Сравнение с NaN
|
|
||||||
alert( Object.is(NaN, NaN) ); // true
|
|
||||||
alert( NaN === NaN ); // false
|
|
||||||
```
|
|
||||||
|
|
||||||
Отличия эти в большинстве ситуаций некритичны, так что непохоже, чтобы эта функция вытеснила обычную проверку `===`. Что интересно -- этот алгоритм сравнения, который называется [SameValue](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-samevalue), применяется во внутренних реализациях различных методов современного стандарта.
|
|
||||||
|
|
||||||
|
|
||||||
## Методы объекта
|
|
||||||
|
|
||||||
Долгое время в JavaScript термин "метод объекта" был просто альтернативным названием для свойства-функции.
|
|
||||||
|
|
||||||
Теперь это уже не так, добавлены именно "методы объекта", которые, по сути, являются свойствами-функциями, привязанными к объекту.
|
|
||||||
|
|
||||||
Их особенности:
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Более короткий синтаксис объявления.</li>
|
|
||||||
<li>Наличие в методах специального внутреннего свойства `[[HomeObject]]` ("домашний объект"), ссылающегося на объект, которому метод принадлежит. Мы посмотрим его использование чуть дальше.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Для объявления метода вместо записи `"prop: function() {…}"` нужно написать просто `"prop() { … }"`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let name = "Вася";
|
|
||||||
let user = {
|
|
||||||
name,
|
|
||||||
*!*
|
|
||||||
// вместо "sayHi: function() {" пишем "sayHi() {"
|
|
||||||
sayHi() {
|
|
||||||
alert(this.name);
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
|
|
||||||
user.sayHi(); // Вася
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно, для создания метода нужно писать меньше букв. Что же касается вызова -- он ничем не отличается от обычной функции. На данном этапе можно считать, что "метод" -- это просто сокращённый синтаксис для свойства-функции. Дополнительные возможности, которые даёт такое объявление, мы рассмотрим позже.
|
|
||||||
|
|
||||||
Также методами станут объявления геттеров `get prop()` и сеттеров `set prop()`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let name = "Вася", surname="Петров";
|
|
||||||
let user = {
|
|
||||||
name,
|
|
||||||
surname,
|
|
||||||
get fullName() {
|
|
||||||
return `${name} ${surname}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( user.fullName ); // Вася Петров
|
|
||||||
```
|
|
||||||
|
|
||||||
Можно задать и метод с вычисляемым названием:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let methodName = "getFirstName";
|
|
||||||
|
|
||||||
let user = {
|
|
||||||
// в квадратных скобках может быть любое выражение,
|
|
||||||
// которое должно вернуть название метода
|
|
||||||
[methodName]() { // вместо [methodName]: function() {
|
|
||||||
return "Вася";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
alert( user.getFirstName() ); // Вася
|
|
||||||
```
|
|
||||||
|
|
||||||
Итак, мы рассмотрели синтаксические улучшения. Если коротко, то не надо писать слово "function". Теперь перейдём к другим отличиям.
|
|
||||||
|
|
||||||
## super
|
|
||||||
|
|
||||||
В ES-2015 появилось новое ключевое слово `super`. Оно предназначено только для использования в методах объекта.
|
|
||||||
|
|
||||||
Вызов `super.parentProperty` позволяет из метода объекта получить свойство его прототипа.
|
|
||||||
|
|
||||||
Например, в коде ниже `rabbit` наследует от `animal`.
|
|
||||||
|
|
||||||
Вызов `super.walk()` из метода объекта `rabbit` обращается к `animal.walk()`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let animal = {
|
|
||||||
walk() {
|
|
||||||
alert("I'm walking");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let rabbit = {
|
|
||||||
__proto__: animal,
|
|
||||||
walk() {
|
|
||||||
*!*
|
|
||||||
alert(super.walk); // walk() { … }
|
|
||||||
super.walk(); // I'm walking
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rabbit.walk();
|
|
||||||
```
|
|
||||||
|
|
||||||
Как правило, это используется в [классах](/class), которые мы рассмотрим в следующем разделе, но важно понимать, что "классы" здесь на самом деле не при чём. Свойство `super` работает через прототип, на уровне методов объекта.
|
|
||||||
|
|
||||||
При обращении через `super` используется `[[HomeObject]]` текущего метода, и от него берётся `__proto__`. Поэтому `super` работает только внутри методов.
|
|
||||||
|
|
||||||
В частности, если переписать этот код, оформив `rabbit.walk` как обычное свойство-функцию, то будет ошибка:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let animal = {
|
|
||||||
walk() {
|
|
||||||
alert("I'm walking");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let rabbit = {
|
|
||||||
__proto__: animal,
|
|
||||||
*!*
|
|
||||||
walk: function() { // Надо: walk() {
|
|
||||||
super.walk(); // Будет ошибка!
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
|
|
||||||
rabbit.walk();
|
|
||||||
```
|
|
||||||
|
|
||||||
Ошибка возникнет, так как `rabbit.walk` теперь обычная функция, и не имеет `[[HomeObject]]`. В ней не работает `super`.
|
|
||||||
|
|
||||||
Исключением из этого правила являются функции-стрелки. В них используется `super` внешней функции. Например, здесь функция-стрелка в `setTimeout` берёт внешний `super`:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let animal = {
|
|
||||||
walk() {
|
|
||||||
alert("I'm walking");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let rabbit = {
|
|
||||||
__proto__: animal,
|
|
||||||
walk() {
|
|
||||||
*!*
|
|
||||||
setTimeout(() => super.walk()); // I'm walking
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
rabbit.walk();
|
|
||||||
```
|
|
||||||
|
|
||||||
Ранее мы говорили о том, что у функций-стрелок нет своего `this`, `arguments`: они используют те, которые во внешней функции. Теперь к этому списку добавился ещё и `super`.
|
|
||||||
|
|
||||||
[smart header="Свойство `[[HomeObject]]` -- не изменяемое"]
|
|
||||||
|
|
||||||
При создании метода -- он привязан к своему объекту навсегда. Технически можно даже скопировать его и запустить отдельно, и `super` продолжит работать:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let animal = {
|
|
||||||
walk() { alert("I'm walking"); }
|
|
||||||
};
|
|
||||||
|
|
||||||
let rabbit = {
|
|
||||||
__proto__: animal,
|
|
||||||
walk() {
|
|
||||||
super.walk();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let walk = rabbit.walk; // скопируем метод в переменную
|
|
||||||
*!*
|
|
||||||
walk(); // вызовет animal.walk()
|
|
||||||
// I'm walking
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше метод `walk()` запускается отдельно от объекта, но всё равно сохраняется через `super` доступ к его прототипу, благодаря `[[HomeObject]]`.
|
|
||||||
|
|
||||||
Это -- скорее технический момент, так как методы объекта, всё же, предназначены для вызова в контексте этого объекта. В частности, правила для `this` в методах -- те же, что и для обычных функций. В примере выше при вызове `walk()` без объекта `this` будет `undefined`.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
Улучшения в описании свойств:
|
|
||||||
<ul>
|
|
||||||
<li>Запись `name: name` можно заменить на просто `name`</li>
|
|
||||||
<li>Если имя свойства находится в переменной или задано выражением `expr`, то его можно указать в квадратных скобках `[expr]`.</li>
|
|
||||||
<li>Свойства-функции можно оформить как методы: `"prop: function() {"` -> `"prop() {"`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
В методах работает обращение к свойствам прототипа через `super.parentProperty`.
|
|
||||||
|
|
||||||
Для работы с прототипом:
|
|
||||||
<ul>
|
|
||||||
<li>`Object.setPrototypeOf(obj, proto)` -- метод для установки прототипа.</li>
|
|
||||||
<li>`obj.__proto__` -- ссылка на прототип.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Дополнительно:
|
|
||||||
<ul>
|
|
||||||
<li>Метод `Object.assign(target, src1, src2...)` -- копирует свойства из всех аргументов в первый объект.</li>
|
|
||||||
<li>Метод `Object.is(value1, value2)` проверяет два значения на равенство. В отличие от `===` считает `+0` и `-0` разными числами. А также считает, что `NaN` равно самому себе.</li>
|
|
||||||
</ul>
|
|
|
@ -1,382 +0,0 @@
|
||||||
|
|
||||||
# Классы
|
|
||||||
|
|
||||||
В современном JavaScript появился новый, "более красивый" синтаксис для классов.
|
|
||||||
|
|
||||||
Новая конструкция `class` -- удобный "синтаксический сахар" для задания конструктора вместе с прототипом.
|
|
||||||
|
|
||||||
## Class
|
|
||||||
|
|
||||||
Синтаксис для классов выглядит так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
class Название [extends Родитель] {
|
|
||||||
constructor
|
|
||||||
методы
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class User {
|
|
||||||
|
|
||||||
constructor(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
sayHi() {
|
|
||||||
alert(this.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = new User("Вася");
|
|
||||||
user.sayHi(); // Вася
|
|
||||||
```
|
|
||||||
|
|
||||||
Функция `constructor` запускается при создании `new User`, остальные методы -- записываются в `User.prototype`.
|
|
||||||
|
|
||||||
Это объявление примерно аналогично такому:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function User(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
User.prototype.sayHi = function() {
|
|
||||||
alert(this.name);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
В обоих случаях `new User` будет создавать объекты. Метод `sayHi` -- также в обоих случаях находится в прототипе.
|
|
||||||
|
|
||||||
Но при объявлении через `class` есть и ряд отличий:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>`User` нельзя вызывать без `new`, будет ошибка.</li>
|
|
||||||
<li>Объявление класса с точки зрения области видимости ведёт себя как `let`. В частности, оно видно только текущем в блоке и только в коде, который находится ниже объявления (Function Declaration видно и до объявления).</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Методы, объявленные внутри `class`, также имеют ряд особенностей:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Метод `sayHi` является именно методом, то есть имеет доступ к `super`.</li>
|
|
||||||
<li>Все методы класса работают в режиме `use strict`, даже если он не указан.</li>
|
|
||||||
<li>Все методы класса не перечислимы. То есть в цикле `for..in` по объекту их не будет.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
## Class Expression
|
|
||||||
|
|
||||||
Так же, как и Function Expression, классы можно задавать "инлайн", в любом выражении и внутри вызова функции.
|
|
||||||
|
|
||||||
Это называется Class Expression:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let User = class {
|
|
||||||
sayHi() { alert('Привет!'); }
|
|
||||||
};
|
|
||||||
|
|
||||||
new User().sayHi();
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше у класса нет имени, что один-в-один соответствует синтаксису функций. Но имя можно дать. Тогда оно, как и в Named Function Expression, будет доступно только внутри класса:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let SiteGuest = class User {
|
|
||||||
sayHi() { alert('Привет!'); }
|
|
||||||
};
|
|
||||||
|
|
||||||
new SiteGuest().sayHi(); // Привет
|
|
||||||
*!*
|
|
||||||
new User(); // ошибка
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
В примере выше имя `User` будет доступно только внутри класса и может быть использовано, например для создания новых объектов данного типа.
|
|
||||||
|
|
||||||
Наиболее очевидная область применения этой возможности -- создание вспомогательного класса прямо при вызове функции.
|
|
||||||
|
|
||||||
Например, функция `createModel` в примере ниже создаёт объект по классу и данным, добавляет ему `_id` и пишет в "реестр" `allModels`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let allModels = {};
|
|
||||||
|
|
||||||
function createModel(Model, ...args) {
|
|
||||||
let model = new Model(...args);
|
|
||||||
|
|
||||||
model._id = Math.random().toString(36).slice(2);
|
|
||||||
allModels[model._id] = model;
|
|
||||||
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = createModel(class User {
|
|
||||||
constructor(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
sayHi() {
|
|
||||||
alert(this.name);
|
|
||||||
}
|
|
||||||
}, "Вася");
|
|
||||||
|
|
||||||
user.sayHi(); // Вася
|
|
||||||
|
|
||||||
alert( allModels[user._id].name ); // Вася
|
|
||||||
```
|
|
||||||
|
|
||||||
## Геттеры, сеттеры и вычисляемые свойства
|
|
||||||
|
|
||||||
В классах, как и в обычных объектах, можно объявлять геттеры и сеттеры через `get/set`, а также использовать `[…]` для свойств с вычисляемыми именами:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class User {
|
|
||||||
constructor(firstName, lastName) {
|
|
||||||
this.firstName = firstName;
|
|
||||||
this.lastName = lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// геттер
|
|
||||||
*/!*
|
|
||||||
get fullName() {
|
|
||||||
return `${this.firstName} ${this.lastName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// сеттер
|
|
||||||
*/!*
|
|
||||||
set fullName(newValue) {
|
|
||||||
[this.firstName, this.lastName] = newValue.split(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
// вычисляемое название метода
|
|
||||||
*/!*
|
|
||||||
["test".toUpperCase()]: true
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = new User("Вася", "Пупков");
|
|
||||||
alert( user.fullName ); // Вася Пупков
|
|
||||||
user.fullName = "Иван Петров";
|
|
||||||
alert( user.fullName ); // Иван Петров
|
|
||||||
alert( user.TEST ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
При чтении `fullName` будет вызван метод `get fullName()`, при присвоении -- метод `set fullName` с новым значением.
|
|
||||||
|
|
||||||
[warn header="`class` не позволяет задавать свойства-значения"]
|
|
||||||
|
|
||||||
В синтаксисе классов, как мы видели выше, можно создавать методы. Они будут записаны в прототип, как например `User.prototype.sayHi`.
|
|
||||||
|
|
||||||
Однако, нет возможности задать в прототипе обычное значение (не функцию), такое как `User.prototype.key = "value"`.
|
|
||||||
|
|
||||||
Конечно, никто не мешает после объявления класса в прототип дописать подобные свойства, однако предполагается, что в прототипе должны быть только методы.
|
|
||||||
|
|
||||||
Если свойство-значение, всё же, необходимо, то, можно создать геттер, который будет нужное значение возвращать.
|
|
||||||
[/warn]
|
|
||||||
|
|
||||||
|
|
||||||
## Статические свойства
|
|
||||||
|
|
||||||
Класс, как и функция, является объектом. Статические свойства класса `User` -- это свойства непосредственно `User`, то есть доступные из него "через точку".
|
|
||||||
|
|
||||||
Для их объявления используется ключевое слово `static`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class User {
|
|
||||||
constructor(firstName, lastName) {
|
|
||||||
this.firstName = firstName;
|
|
||||||
this.lastName = lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
static createGuest() {
|
|
||||||
return new User("Гость", "Сайта");
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
};
|
|
||||||
|
|
||||||
let user = User.createGuest();
|
|
||||||
|
|
||||||
alert( user.firstName ); // Гость
|
|
||||||
|
|
||||||
alert( User.createGuest ); // createGuest ... (функция)
|
|
||||||
```
|
|
||||||
|
|
||||||
Как правило, они используются для операций, не требующих наличия объекта, например -- для фабричных, как в примере выше, то есть как альтернативные варианты конструктора. Или же, можно добавить метод `User.compare`, который будет сравнивать двух пользователей для целей сортировки.
|
|
||||||
|
|
||||||
Также статическими удобно делать константы:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class Menu {
|
|
||||||
static get elemClass() {
|
|
||||||
return "menu"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alert( Menu.elemClass ); // menu
|
|
||||||
```
|
|
||||||
|
|
||||||
## Наследование
|
|
||||||
|
|
||||||
Синтаксис:
|
|
||||||
```js
|
|
||||||
class Child extends Parent {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Посмотрим, как это выглядит на практике. В примере ниже объявлено два класса: `Animal` и наследующий от него `Rabbit`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class Animal {
|
|
||||||
constructor(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
walk() {
|
|
||||||
alert("I walk: " + this.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
class Rabbit extends Animal {
|
|
||||||
*/!*
|
|
||||||
walk() {
|
|
||||||
super.walk();
|
|
||||||
alert("...and jump!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new Rabbit("Вася").walk();
|
|
||||||
// I walk: Вася
|
|
||||||
// and jump!
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видим, в `new Rabbit` доступны как свои методы, так и (через `super`) методы родителя.
|
|
||||||
|
|
||||||
Это потому, что при наследовании через `extends` формируется стандартная цепочка прототипов: методы `Rabbit` находятся в `Rabbit.prototype`, методы `Animal` -- в `Animal.prototype`, и они связаны через `__proto__`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class Animal { }
|
|
||||||
class Rabbit extends Animal { }
|
|
||||||
|
|
||||||
alert( Rabbit.prototype.__proto__ == Animal.prototype ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно из примера выше, методы родителя (`walk`) можно переопределить в наследнике. При этом для обращения к родительскому методу используют `super.walk()`.
|
|
||||||
|
|
||||||
Немного особая история -- с конструктором.
|
|
||||||
|
|
||||||
Конструктор `constructor` родителя наследуется автоматически. То есть, если в потомке не указан свой `constructor`, то используется родительский. В примере выше `Rabbit`, таким образом, использует `constructor` от `Animal`.
|
|
||||||
|
|
||||||
Если же у потомка свой `constructor`, то чтобы в нём вызвать конструктор родителя -- используется синтаксис `super()` с аргументами для родителя.
|
|
||||||
|
|
||||||
Например, вызовем конструктор `Animal` в `Rabbit`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class Animal {
|
|
||||||
constructor(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
walk() {
|
|
||||||
alert("I walk: " + this.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Rabbit extends Animal {
|
|
||||||
*!*
|
|
||||||
constructor() {
|
|
||||||
// вызвать конструктор Animal с аргументом "Кроль"
|
|
||||||
super("Кроль"); // то же, что и Animal.call(this, "Кроль")
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
new Rabbit().walk(); // I walk: Кроль
|
|
||||||
```
|
|
||||||
|
|
||||||
Для такого вызова есть небольшие ограничения:
|
|
||||||
<ul>
|
|
||||||
<li>Вызвать конструктор родителя можно только изнутри конструктора потомка. В частности, `super()` нельзя вызвать из произвольного метода.</li>
|
|
||||||
<li>В конструкторе потомка мы обязаны вызвать `super()` до обращения к `this`. До вызова `super` не существует `this`, так как по спецификации в этом случае именно `super` инициализует `this`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Второе ограничение выглядит несколько странно, поэтому проиллюстрируем его примером:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
class Animal {
|
|
||||||
constructor(name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Rabbit extends Animal {
|
|
||||||
*!*
|
|
||||||
constructor() {
|
|
||||||
alert(this); // ошибка, this не определён!
|
|
||||||
// обязаны вызвать super() до обращения к this
|
|
||||||
super();
|
|
||||||
// а вот здесь уже можно использовать this
|
|
||||||
}
|
|
||||||
*/!*
|
|
||||||
}
|
|
||||||
|
|
||||||
new Rabbit();
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Классы можно объявлять как в основном потоке кода, так и "инлайн", по аналогии с Function Declaration и Expression.</li>
|
|
||||||
<li>В объявлении классов можно использовать методы, геттеры/сеттеры и вычислимые названия методов.</li>
|
|
||||||
<li>При наследовании вызов конструктора родителя осуществлятся через `super(...args)`, вызов родительских методов -- через `super.method(...args)`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Концепция классов, которая после долгих обсуждений получилась в стандарте EcmaScript, носит название "максимально минимальной". То есть, в неё вошли только те возможности, которые уж точно необходимы.
|
|
||||||
|
|
||||||
В частности, не вошли "приватные" и "защищённые" свойства. То есть, все свойства и методы класса технически доступны снаружи. Возможно, они появятся в будущих редакциях стандарта.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,210 +0,0 @@
|
||||||
|
|
||||||
# Тип данных Symbol
|
|
||||||
|
|
||||||
Новый примитивный тип данных Symbol служит для создания уникальных идентификаторов.
|
|
||||||
|
|
||||||
Мы вначале рассмотрим объявление и особенности символов, а затем -- их использование.
|
|
||||||
|
|
||||||
## Объявление
|
|
||||||
|
|
||||||
Синтаксис:
|
|
||||||
```js
|
|
||||||
let sym = Symbol();
|
|
||||||
```
|
|
||||||
|
|
||||||
Обратим внимание, не `new Symbol`, а просто `Symbol`, так как это -- примитив.
|
|
||||||
|
|
||||||
У символов есть и соответствующий `typeof`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let sym = Symbol();
|
|
||||||
alert( typeof sym ); // symbol
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Каждый символ -- уникален. У функции `Symbol` есть необязательный аргумент "имя символа". Можно его использовать для описания символа, в целях отладки:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let sym = Symbol("name");
|
|
||||||
alert( sym.toString() ); // Symbol(name)
|
|
||||||
```
|
|
||||||
|
|
||||||
...Но при этом если у двух символов одинаковое имя, то это не значит, что они равны:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
alert( Symbol("name") == Symbol("name") ); // false
|
|
||||||
```
|
|
||||||
|
|
||||||
Если хочется из разных частей программы использовать именно одинаковый символ, то можно передавать между ними объект символа или же -- использовать "глобальные символы" и "реестр глобальных символов", которые мы рассмотрим далее.
|
|
||||||
|
|
||||||
## Глобальные символы
|
|
||||||
|
|
||||||
Существует "глобальный реестр" символов, который позволяет, при необходимости, разделять символы между частями программы.
|
|
||||||
|
|
||||||
Для чтения (или создания, при отсутствии) "глобального" символа служит вызов `Symbol.for(имя)`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// создание символа в реестре
|
|
||||||
let name = Symbol.for("name");
|
|
||||||
|
|
||||||
// символ уже есть, чтение из реестра
|
|
||||||
alert( Symbol.for("name") == name ); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Вызов `Symbol.for` возвращает символ по имени. Обратным для него является вызов `Symbol.keyFor(sym)` позволяет получить по глобальному символу его имя:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// создание символа в реестре
|
|
||||||
let name = Symbol.for("name");
|
|
||||||
|
|
||||||
// получение имени символа
|
|
||||||
alert( Symbol.keyFor(name) ); // name
|
|
||||||
```
|
|
||||||
[warn header="`Symbol.keyFor` возвращает `undefined`, если символ не глобальный"]
|
|
||||||
Заметим, что `Symbol.keyFor` работает *только для глобальных символов*, для остальных будет возвращено `undefined`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
alert( Symbol.keyFor(Symbol.for("name")) ); // name, глобальный
|
|
||||||
alert( Symbol.keyFor(Symbol("name2")) ); // undefined, обычный символ
|
|
||||||
```
|
|
||||||
|
|
||||||
Таким образом, имя символа, если этот символ не глобальный, не имеет особого применения, оно полезно лишь в целях вывода и отладки.
|
|
||||||
[/warn]
|
|
||||||
|
|
||||||
## Использование символов
|
|
||||||
|
|
||||||
Символы можно использовать в качестве имён для свойств объекта, вот так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let isAdmin = Symbol("isAdmin");
|
|
||||||
|
|
||||||
let user = {
|
|
||||||
name: "Вася",
|
|
||||||
[isAdmin]: true
|
|
||||||
};
|
|
||||||
|
|
||||||
alert(user[isAdmin]); // true
|
|
||||||
```
|
|
||||||
|
|
||||||
Особенность символов -- в том, что если в объект записать свойство-символ, то оно не участвует в итерации:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let user = {
|
|
||||||
name: "Вася",
|
|
||||||
age: 30,
|
|
||||||
[Symbol.for("isAdmin")]: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// в цикле for..in также не будет символа
|
|
||||||
alert( Object.keys(user) ); // name, age
|
|
||||||
|
|
||||||
// доступ к свойству через глобальный символ — работает
|
|
||||||
alert( user[Symbol.for("isAdmin")] );
|
|
||||||
```
|
|
||||||
|
|
||||||
Кроме того, свойство-символ недоступно, если обратиться к его названию: `user.isAdmin` не существует.
|
|
||||||
|
|
||||||
Зачем всё это, почему не просто использовать строки?
|
|
||||||
|
|
||||||
Резонный вопрос. На ум могут прийти соображения производительности, так как символы -- это по сути специальные идентификаторы, они компактнее, чем строка. Но при современных оптимизациях объектов это редко имеет значение.
|
|
||||||
|
|
||||||
Самое широкое применение символов предусмотрено внутри самого стандарта JavaScript. В современном стандарте есть много системных символов. Их список есть в спецификации, в таблице [Well-known Symbols](http://www.ecma-international.org/ecma-262/6.0/index.html#table-1). В спецификации принято символы для краткости обозначать их как '@@имя', например `@@iterator`, но доступны они как свойства `Symbol`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
<ul>
|
|
||||||
<li>`Symbol.toPrimitive` -- идентификатор для свойства, задающего функцию преобразования объекта в примитив.</li>
|
|
||||||
<li>`Symbol.iterator` -- идентификатор для свойства, задающего функцию итерации по объекту.</li>
|
|
||||||
<li>...и т.п.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
**Мы легко поймём смысл введения нового типа "символ", если поставим себя на место создателей языка JavaScript.**
|
|
||||||
|
|
||||||
Допустим, в новом стандарте нам надо добавить к объекту "особый" функционал, например, функцию, которая задаёт преобразование объекта к примитиву. Как `obj.toString`, но для преобразования в примитивы.
|
|
||||||
|
|
||||||
Мы ведь не можем просто сказать, что "свойство obj.toPrimitive теперь будет задавать преобразование к примитиву и автоматически вызываться в таких-то ситуациях". Это опасно. Мы не можем так просто взять и придать особый смысл свойству. Мало ли, вполне возможно, что свойство с таким именем уже используется в существующем коде, и если сделать его особым, то он сломается.
|
|
||||||
|
|
||||||
Нельзя просто взять и зарезервировать какие-то свойства существующих объектов для нового функционала.
|
|
||||||
|
|
||||||
Поэтому ввели целый тип "символы". Их можно использовать для задания таких свойств, так как они:
|
|
||||||
<ul>
|
|
||||||
<li>а) уникальны,</li>
|
|
||||||
<li>б) не участвуют в циклах,</li>
|
|
||||||
<li>в) заведомо не сломают старый код, который о них слыхом не слыхивал.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Продемонстрируем отсутствие конфликта для нового системного свойства `Symbol.iterator`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let obj = {
|
|
||||||
iterator: 1,
|
|
||||||
[Symbol.iterator]() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
alert(obj.iterator); // 1
|
|
||||||
alert(obj[Symbol.iterator]) // function, символ не конфликтует
|
|
||||||
```
|
|
||||||
|
|
||||||
Выше мы использовали системный символ `Symbol.iterator`, поскольку он один из самых широко поддерживаемых. Мы подробно разберём его смысл в главе про [итераторы](/iterator), пока же -- это просто пример символа.
|
|
||||||
|
|
||||||
Чтобы получить все символы объекта, есть особый вызов [Object.getOwnPropertySymbols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertySymbols).
|
|
||||||
|
|
||||||
Эта функция возвращает все символы в объекте (и только их). Заметим, что старая функция `getOwnPropertyNames` символы не возвращает, что опять же гарантирует отсутствие конфликтов со старым кодом.
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let obj = {
|
|
||||||
iterator: 1,
|
|
||||||
[Symbol.iterator]: function() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// один символ в объекте
|
|
||||||
alert( Object.getOwnPropertySymbols(obj) ); // Symbol(Symbol.iterator)
|
|
||||||
|
|
||||||
// и одно обычное свойство
|
|
||||||
alert( Object.getOwnPropertyNames(obj) ); // iterator
|
|
||||||
```
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Символы -- новый примитивный тип, предназначенный для уникальных идентификаторов.</li>
|
|
||||||
<li>Все символы уникальны, символы с одинаковым именем не равны друг другу.</li>
|
|
||||||
<li>Существует глобальный реестр символов, доступных через метод `Symbol.for(name)`. Для глобального символа можно получить имя вызовом и `Symbol.keyFor(sym)`.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Основная область использования символов -- это системные свойства объектов, которые задают разные аспекты их поведения. Поддержка у них пока небольшая, но она растёт. Системные символы позволяют разработчикам стандарта добавлять новые "особые" свойства объектов, при этом не резервируя соответствующие строковые значения.
|
|
||||||
|
|
||||||
Системные символы доступны как свойства функции `Symbol`, например `Symbol.iterator`.
|
|
||||||
|
|
||||||
Мы можем создавать и свои символы, использовать их в объектах. Записывать их как свойства `Symbol`, разумеется, нельзя, если нужен глобально доступный символ, то используется `Symbol.for(имя)`.
|
|
||||||
|
|
|
@ -1,230 +0,0 @@
|
||||||
|
|
||||||
# Итераторы
|
|
||||||
|
|
||||||
В современный JavaScript добавлена новая концепция "итерируемых" (iterable) объектов.
|
|
||||||
|
|
||||||
Итерируемые или, иными словами, "перебираемые" объекты -- это те, содержимое которых можно перебрать в цикле.
|
|
||||||
|
|
||||||
Например, перебираемым объектом является массив. Но не только он. В браузере существует множество объектов, которые не являются массивами, но содержимое которых можно перебрать (к примеру, список DOM-узлов).
|
|
||||||
|
|
||||||
Для перебора таких объектов добавлен новый синтаксис цикла: `for..of`.
|
|
||||||
|
|
||||||
Например:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let arr = [1, 2, 3]; // массив — пример итерируемого объекта
|
|
||||||
|
|
||||||
for(let value of arr) {
|
|
||||||
alert(value); // 1, затем 2, затем 3
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Также итерируемой является строка:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
for(let char of "Привет") {
|
|
||||||
alert(char); // Выведет по одной букве: П, р, и, в, е, т
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Итераторы -- расширяющая понятие "массив" концепция, которая пронизывает современный стандарт JavaScript сверху донизу.
|
|
||||||
|
|
||||||
Практически везде, где нужен перебор, он осуществляется через итераторы. Это включает в себя не только строки, массивы, но и вызов функции с оператором spread `f(...args)`, и многое другое.
|
|
||||||
|
|
||||||
В отличие от массивов, "перебираемые" объекты могут не иметь "длины" `length`. Как мы увидим далее, итераторы дают возможность сделать "перебираемыми" любые объекты.
|
|
||||||
|
|
||||||
## Свой итератор
|
|
||||||
|
|
||||||
Допустим, у нас есть некий объект, который надо "умным способом" перебрать.
|
|
||||||
|
|
||||||
Например, `range` -- диапазон чисел от `from` до `to`, и мы хотим, чтобы `for(let num of range)` "перебирал", этот объект. При этом под перебором мы подразумеваем перечисление чисел от `from` до `to`.
|
|
||||||
|
|
||||||
Объект `range` без итератора:
|
|
||||||
|
|
||||||
```js
|
|
||||||
let range = {
|
|
||||||
from: 1,
|
|
||||||
to: 5
|
|
||||||
};
|
|
||||||
|
|
||||||
// хотим сделать перебор
|
|
||||||
// for(let num of range) ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Для возможности использовать объект в `for..of` нужно создать в нём свойство с названием `Symbol.iterator` (системный символ).
|
|
||||||
|
|
||||||
При вызове метода `Symbol.iterator` перебираемый объект должен возвращать другой объект ("итератор"), который умеет осуществлять перебор.
|
|
||||||
|
|
||||||
По стандарту у такого объекта должен быть метод `next()`, который при каждом вызове возвращает очередное значение и окончен ли перебор.
|
|
||||||
|
|
||||||
Так это выглядит в коде:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let range = {
|
|
||||||
from: 1,
|
|
||||||
to: 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// сделаем объект range итерируемым
|
|
||||||
range[Symbol.iterator] = function() {
|
|
||||||
|
|
||||||
let current = this.from;
|
|
||||||
let last = this.to;
|
|
||||||
|
|
||||||
// метод должен вернуть объект с next()
|
|
||||||
return {
|
|
||||||
next() {
|
|
||||||
if (current <= last) {
|
|
||||||
return {
|
|
||||||
done: false,
|
|
||||||
value: current++
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
done: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let num of range) {
|
|
||||||
alert(num); // 1, затем 2, 3, 4, 5
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Как видно из кода выше, здесь имеет место разделение сущностей:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Перебираемый объект `range` сам не реализует методы для своего перебора.</li>
|
|
||||||
<li>Для этого создаётся другой объект, который хранит текущее состояние перебора и возвращает значение. Этот объект называется итератором и возвращается при вызове метода `range[Symbol.iterator]`.</li>
|
|
||||||
<li>У итератора должен быть метод `next()`, который при каждом вызове возвращает объект со свойствами:
|
|
||||||
<ul>
|
|
||||||
<li>`value` -- очередное значение,
|
|
||||||
<li>`done` -- равно `false`, если есть ещё значения, и `true` -- в конце.</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Конструкция `for..of` в начале своего выполнения автоматически вызывает `Symbol.iterator()`, получает итератор и далее вызывает метод `next()` до получения `done: true`. Такова внутренняя механика. Внешний код при переборе через `for..of` видит только значения.
|
|
||||||
|
|
||||||
Такое отделение функционала перебора от самого объекта даёт дополнительную гибкость, например, объект может возвращать разные итераторы в зависимости от своего настроения и времени суток. Однако, бывают ситуации, когда оно не нужно.
|
|
||||||
|
|
||||||
Если функционал по перебору (метод `next`) предоставляется самим объектом, то можно вернуть `this` в качестве итератора:
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let range = {
|
|
||||||
from: 1,
|
|
||||||
to: 5,
|
|
||||||
|
|
||||||
*!*
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
*/!*
|
|
||||||
|
|
||||||
next() {
|
|
||||||
if (this.current === undefined) {
|
|
||||||
// инициализация состояния итерации
|
|
||||||
this.current = this.from;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.current <= this.to) {
|
|
||||||
return {
|
|
||||||
done: false,
|
|
||||||
value: this.current++
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// очистка текущей итерации
|
|
||||||
delete this.current;
|
|
||||||
return {
|
|
||||||
done: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let num of range) {
|
|
||||||
alert(num); // 1, затем 2, 3, 4, 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// Произойдёт вызов Math.max(1,2,3,4,5);
|
|
||||||
alert( Math.max(...range) ); // 5 (*)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
При таком подходе сам объект и хранит состояние итерации (текущий перебираемый элемент).
|
|
||||||
|
|
||||||
В данном случае это работает, но для большей гибкости и понятности кода рекомендуется, всё же, выделять итератор в отдельный объект со своим состоянием и кодом.
|
|
||||||
|
|
||||||
[smart header="Оператор spread `...` и итераторы"]
|
|
||||||
В последней строке `(*)` примера выше можно видеть, что итерируемый объект передаётся через spread для `Math.max`.
|
|
||||||
|
|
||||||
При этом `...range` автоматически превращает итерируемый объект в массив. То есть произойдёт цикл `for..of` по `range`, и его результаты будут использованы в качестве списка аргументов.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
[smart header="Бесконечные итераторы"]
|
|
||||||
Возможны и бесконечные итераторы. Например, пример выше при `range.to = Infinity` будет таковым. Или можно сделать итератор, генерирующий бесконечную последовательность псевдослучайных чисел. Тоже полезно.
|
|
||||||
|
|
||||||
Нет никаких ограничений на `next`, он может возвращать всё новые и новые значения, и это нормально.
|
|
||||||
|
|
||||||
Разумеется, цикл `for..of` по такому итератору тоже будет бесконечным, нужно его прерывать, например, через `break`.
|
|
||||||
[/smart]
|
|
||||||
|
|
||||||
## Встроенные итераторы
|
|
||||||
|
|
||||||
Встроенные в JavaScript итераторы можно получить и явным образом, без `for..of`, прямым вызовом `Symbol.iterator`.
|
|
||||||
|
|
||||||
Например, этот код получает итератор для строки и вызывает его полностью "вручную":
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
let str = "Hello";
|
|
||||||
|
|
||||||
// Делает то же, что и
|
|
||||||
// for(var letter of str) alert(letter);
|
|
||||||
|
|
||||||
let iterator = str[Symbol.iterator]();
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
let result = iterator.next();
|
|
||||||
if (result.done) break;
|
|
||||||
alert(result.value); // Выведет все буквы по очереди
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
То же самое будет работать и для массивов.
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>*Итератор* -- объект, предназначенный для перебора другого объекта.</li>
|
|
||||||
<li>У итератора должен быть метод `next()`, возвращающий `{done: Boolean, value: any}`, где `value` -- очередное значение, а `done: true` в конце.</li>
|
|
||||||
<li>Метод `Symbol.iterator` предназначен для получения итератора из объекта. Цикл `for..of` делает это автоматически, но можно и вызвать его напрямую.</li>
|
|
||||||
<li>В современном стандарте есть много мест, где вместо массива используются более абстрактные "итерируемые" (со свойством `Symbol.iterator`) объекты, например оператор spread `...`.</li>
|
|
||||||
<li>Встроенные объекты, такие как массивы и строки, являются итерируемыми, в соответствии с описанным выше.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
# Современные возможности ES-2015
|
|
||||||
|
|
||||||
Современный стандарт ES-2015 и его расширения для JavaScript.
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ In this chapter we'll create a simple script and see it working.
|
||||||
## The "script" tag
|
## The "script" tag
|
||||||
|
|
||||||
[smart header="What if I want to move faster?"]
|
[smart header="What if I want to move faster?"]
|
||||||
In the case if an impatient reader has developed with JavaScript already or has a lot of experience in another language, he can skip detailed explanatins and jump to [](/javascript-specials). There he can find an essense of important features.
|
In the case if the reader of these lines has developed with JavaScript already or has a lot of experience in another language, he can skip detailed explanatins and jump to [](/javascript-specials). There he can find an essense of important features.
|
||||||
|
|
||||||
If you have enough time and want to learn things in details then read on :)
|
If you have enough time and want to learn things in details then read on :)
|
||||||
[/smart]
|
[/smart]
|
||||||
|
@ -37,21 +37,23 @@ For instance:
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[online]
|
||||||
You can run the example clicking on a "Play" button in it's right-top corner.
|
You can run the example clicking on a "Play" button in it's right-top corner.
|
||||||
|
[/online]
|
||||||
|
|
||||||
The `<script>` tag contains JavaScript code which is automatically executed when the browser meets the tag.
|
The `<script>` tag contains JavaScript code which is automatically executed when the browser meets the tag.
|
||||||
|
|
||||||
Please note the sequence:
|
Please note the execution sequence:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Browser starts to parse/show the document, makes it up to the `<script>`.</li>
|
<li>Browser starts to parse the document and display the page.</li>
|
||||||
<li>When the browser meets `<script>`, it switches to the JavaScript execution mode. In this mode it executes the script. In this case `alert` command is used which shows a message.</li>
|
<li>When the browser meets `<script>`, it switches to the JavaScript execution mode. In this mode it executes the script.</li>
|
||||||
|
<li>The `alert` command shows a message and pauses the execution.</li>
|
||||||
|
<li>*Note that a part of the page before the script is shown already at this moment.*</li>
|
||||||
<li>When the script is finished, it gets back to the HTML-mode, and *only then* it shows the rest of the document.</li>
|
<li>When the script is finished, it gets back to the HTML-mode, and *only then* it shows the rest of the document.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
So, a visitor won't see the content after the script until the script finishes to execute.
|
A visitor won't see the content after the script until it is executed. In other words, a `<script>` tag blocks rendering.
|
||||||
|
|
||||||
People say about that: "a `<script>` tag blocks rendering".
|
|
||||||
|
|
||||||
|
|
||||||
## The modern markup
|
## The modern markup
|
||||||
|
@ -67,11 +69,9 @@ We can find the following in the old code:
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt>The `language` attribute: <code><script <u>language</u>=...></code></dt>
|
<dt>The `language` attribute: <code><script <u>language</u>=...></code></dt>
|
||||||
<dd>This attribute was meant to show the language of the script. Certain outdated browsers supported other languages at that time. As of now, this attribute makes no sense, the language is JavaScript by default. No need to use it.</dd>
|
<dd>This attribute was meant to show the language of the script. As of now, this attribute makes no sense, the language is JavaScript by default. No need to use it.</dd>
|
||||||
<dt>Comments before and after scripts.</dt>
|
<dt>Comments before and after scripts.</dt>
|
||||||
<dd>In the most pre-historic books and guides, `<script>` may have comments inside.
|
<dd>In really ancient books and guides, one may find comments inside `<script>`, like this:
|
||||||
|
|
||||||
Like this:
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!--+ no-beautify -->
|
<!--+ no-beautify -->
|
||||||
|
@ -80,9 +80,14 @@ Like this:
|
||||||
//--></script>
|
//--></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
These comments were supposed to hide the code from an old browser that did't understand a `<script>` tag. But all browsers from the past 15 years know about `<script>`, so that's a really ancient theme. Giving this for the sake of completeness only. So if you see such code somewhere you know the guide is really ancient and not worth looking into.
|
These comments were supposed to hide the code from an old browser that did't understand a `<script>` tag. But all browsers born in the past 15 years know about `<script>`, so that's not an issue. So if you see such code somewhere you know the guide is really old and probably not worth looking into.
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
In summary, we just use `<script>` to add some code to the page, without additional attributes and comments.
|
## Summary
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>We can use a `<script>` tag without attributes to add JavaScript code to the page.</li>
|
||||||
|
<li>A `<script>` tag blocks page rendering. Can be bad. Later we'll see how to evade that.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -11,14 +11,16 @@ Syntax:
|
||||||
alert(message)
|
alert(message)
|
||||||
```
|
```
|
||||||
|
|
||||||
`alert` shows a message and pauses the script execution until the user presses "OK".
|
This shows a message and pauses the script execution until the user presses "OK".
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
alert( "Hello" );
|
alert( "Hello" );
|
||||||
```
|
```
|
||||||
|
|
||||||
The small special 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 he deals with the window. In this case -- until he presses "OK".
|
The small 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 he deals with the window. In this case -- until he presses "OK".
|
||||||
|
|
||||||
## prompt
|
## prompt
|
||||||
|
|
||||||
|
@ -31,14 +33,14 @@ result = prompt(title, default);
|
||||||
|
|
||||||
It shows a modal window with the given `title`, a field for text, initially filled with the `default` string and buttons OK/CANCEL.
|
It shows a modal window with the given `title`, a field for text, initially filled with the `default` string and buttons OK/CANCEL.
|
||||||
|
|
||||||
The visitor may type something in the field and press OK or cancel the input by pressing a CANCEL button or the [key Esc] key.
|
The visitor may type something in the field and press OK. Or he can cancel the input by pressing a CANCEL button or the [key Esc] key.
|
||||||
|
|
||||||
**The call to `prompt` returns the text from the field or `null` if CANCEL is pressed.**
|
The call to `prompt` returns the text from the field or `null` if te input is canceled.
|
||||||
|
|
||||||
[warn header="Safari 5.1+ does not return `null`"]
|
[warn header="Safari 5.1+ does not return `null`"]
|
||||||
Safari is the only browser which does not return `null` when the input was canceled. It returns an empty string instead.
|
Safari is the only browser which does not return `null` when the input was canceled. It returns an empty string instead.
|
||||||
|
|
||||||
So actually we should treat empty line the same as `null`, that is: consider it a cancellation.
|
To make the code compatible with that browser, we should consider both an empty line and `null` as a cancellation.
|
||||||
[/warn]
|
[/warn]
|
||||||
|
|
||||||
As with `alert`, the `prompt` window is modal.
|
As with `alert`, the `prompt` window is modal.
|
||||||
|
@ -47,11 +49,11 @@ As with `alert`, the `prompt` window is modal.
|
||||||
//+ run
|
//+ run
|
||||||
let age = prompt('How old are you?', 100);
|
let age = prompt('How old are you?', 100);
|
||||||
|
|
||||||
alert(`You are ${age} years old!`)
|
alert(`You are ${age} years old!`); // You are 100 years old! (for default prompt value)
|
||||||
```
|
```
|
||||||
|
|
||||||
[warn header="Always put `default`"]
|
[warn header="Always supply a `default`"]
|
||||||
The second parameter is optional. But if we don't supply it, IE would insert the text `"undefined"` into the prompt.
|
The second parameter is optional. But if we don't supply it, Internet Explorer would insert the text `"undefined"` into the prompt.
|
||||||
|
|
||||||
Run this code in Internet Explorer to see that:
|
Run this code in Internet Explorer to see that:
|
||||||
|
|
||||||
|
@ -71,7 +73,7 @@ let test = prompt("Test", ''); // <-- for IE
|
||||||
|
|
||||||
## confirm
|
## confirm
|
||||||
|
|
||||||
Синтаксис:
|
The syntax:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
result = confirm(question);
|
result = confirm(question);
|
||||||
|
@ -90,16 +92,6 @@ let isBoss = confirm("Are you the boss?");
|
||||||
alert( isBoss ); // true is OK is pressed
|
alert( isBoss ); // true is OK is pressed
|
||||||
```
|
```
|
||||||
|
|
||||||
## The limitations
|
|
||||||
|
|
||||||
There are two limitations shared by all the methods:
|
|
||||||
<ol>
|
|
||||||
<li>The exact location of the modal window is determined by the browser. Usually it's in the center.</li>
|
|
||||||
<li>The exact look of the window also depends on the browser. We can't modify it.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
That is the price for simplicity. There are other means to show windows and interact with the visitor, but if the "bells and whistles" do not matter much, these methods are just fine.
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -107,3 +99,10 @@ That is the price for simplicity. There are other means to show windows and inte
|
||||||
<li>`prompt` shows a message asking the user to input text. It returns the text or, if CANCEL or [key Esc] is clicked, all browsers except Safari return `null`.</li>
|
<li>`prompt` shows a message asking the user to input text. It returns the text or, if CANCEL or [key Esc] is clicked, all browsers except Safari return `null`.</li>
|
||||||
<li>`confirm` shows a message and waits the user to press "OK" or "CANCEL". It returns `true` for OK and `false` for CANCEL/[key Esc].</li>
|
<li>`confirm` shows a message and waits the user to press "OK" or "CANCEL". It returns `true` for OK and `false` for CANCEL/[key Esc].</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
There are two limitations shared by all the methods above:
|
||||||
|
<ol>
|
||||||
|
<li>The exact location of the modal window is determined by the browser. Usually it's in the center.</li>
|
||||||
|
<li>The exact look of the window also depends on the browser. We can't modify it.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
That is the price for simplicity. There are other ways to show nicer windows and interact with the visitor, but if "bells and whistles" do not matter much, these methods work just fine.
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
Rewrite `if..else` using multiple ternary operators `'?'`.
|
Rewrite `if..else` using multiple ternary operators `'?'`.
|
||||||
|
|
||||||
For readability, please let the code span several lines.
|
For readability, it's recommended to split the code span over lines.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let message;
|
let message;
|
||||||
|
|
|
@ -25,8 +25,8 @@ If there's more than one command to execute -- we can use a code block in figure
|
||||||
|
|
||||||
```js
|
```js
|
||||||
if (year == 2015) {
|
if (year == 2015) {
|
||||||
alert( 'You're so smart!' );
|
alert( "That's correct!" );
|
||||||
alert( 'Exactly so!' );
|
alert( "You're so smart!" );
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -36,9 +36,7 @@ It is recommended to use figure brackets every time with `if`, even if there's o
|
||||||
|
|
||||||
The `if (…)` operator evaluates the condition in brackets and converts it to boolean type.
|
The `if (…)` operator evaluates the condition in brackets and converts it to boolean type.
|
||||||
|
|
||||||
Let's remember the rules.
|
Let's recall the rules. In the logical context:
|
||||||
|
|
||||||
In the logical context:
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>A number `0`, an empty string `""`, `null`, `undefined` and `NaN` are `false`,</li>
|
<li>A number `0`, an empty string `""`, `null`, `undefined` and `NaN` are `false`,</li>
|
||||||
<li>Other values -- `true`.</li>
|
<li>Other values -- `true`.</li>
|
||||||
|
@ -52,7 +50,7 @@ if (0) { // 0 is falsy
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
...And this condition -- always works out:
|
...And inside this condition -- always works:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
if (1) { // 1 is truthy
|
if (1) { // 1 is truthy
|
||||||
|
@ -60,7 +58,7 @@ if (1) { // 1 is truthy
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
We can also pass a pre-evaluated logical value to `if`. For example, in a variable like here:
|
We can also pass a pre-evaluated logical value to `if`, like here:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let cond = (year == 2015); // equality evaluates to true or false
|
let cond = (year == 2015); // equality evaluates to true or false
|
||||||
|
@ -132,10 +130,10 @@ alert(hasAccess);
|
||||||
|
|
||||||
The so called "ternary" or "question mark" operator allows to do that shorter and simpler.
|
The so called "ternary" or "question mark" operator allows to do that shorter and simpler.
|
||||||
|
|
||||||
The operator is represented by a question mark `"?"`. The formal term "ternary" means that the operator has 3 arguments. It is actually the one and only operator in JavaScript which has 3 arguments.
|
The operator is represented by a question mark `"?"`. The formal term "ternary" means that the operator has 3 operands. It is actually the one and only operator in JavaScript which has that many.
|
||||||
|
|
||||||
The syntax is:
|
The syntax is:
|
||||||
```
|
```js
|
||||||
let result = condition ? value1 : value2
|
let result = condition ? value1 : value2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -163,9 +161,10 @@ In the described case it is possible to evade the question mark operator, becaus
|
||||||
// the same
|
// the same
|
||||||
let hasAccess = age > 14;
|
let hasAccess = age > 14;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
But that's only in this case. Generally, the question mark can return any value.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
As we can see, the question mark operator has indeed 3 arguments: a condition and two values.
|
|
||||||
## Multiple '?'
|
## Multiple '?'
|
||||||
|
|
||||||
A sequence of question mark `"?"` operators allows to return a value depending on more than one condition.
|
A sequence of question mark `"?"` operators allows to return a value depending on more than one condition.
|
||||||
|
@ -185,7 +184,7 @@ alert( message );
|
||||||
|
|
||||||
It may be difficult at first to grasp what's going on. But looking more carefully we note that it's just an ordinary sequence of tests.
|
It may be difficult at first to grasp what's going on. But looking more carefully we note that it's just an ordinary sequence of tests.
|
||||||
|
|
||||||
The question mark first tests `age < 3`, if true -- returns `'Hi, baby!'`, otherwise -- goes beyound the colon `":"` and tests `age < 18`. If that's true -- returns `'Hello!'`, otherwise tests `age < 100` and `'Greetings!' if that is so`... At last, if all tests are falsy, the `message` becomes `'What an unusual age!'`.
|
The question mark first checks for `age < 3`. If true -- returns `'Hi, baby!'`, otherwise -- goes to the right side of the colon `":"` and checks for `age < 18`. If that's true -- returns `'Hello!'`, otherwise checks for `age < 100` and returns `'Greetings!'` if that is so... At last, if all checks are falsy, the `message` becomes `'What an unusual age!'`.
|
||||||
|
|
||||||
The same with `if..else`:
|
The same with `if..else`:
|
||||||
|
|
||||||
|
@ -215,7 +214,7 @@ let company = prompt('Which company created JavaScript?', '');
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
Depending on the condition `company == 'Netscape'`, either the first or the second part after `"?"` gets evaluated and shows the alert.
|
Depending on the condition `company == 'Netscape'`, either the first or the second part after `"?"` gets executed and shows the alert.
|
||||||
|
|
||||||
We don't assign a result to a variable here, the `alert` doesn't return anything anyway.
|
We don't assign a result to a variable here, the `alert` doesn't return anything anyway.
|
||||||
|
|
||||||
|
@ -238,7 +237,7 @@ if (company == 'Netscape') {
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
Our eyes browse the code vertically. The constructs which span several lines are easier to understand than a long horizontal reading here.
|
Our eyes scan the code vertically. The constructs 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's `if` to execute different branches of the code.
|
The idea of a question mark `'?'` is to return one or another value depending on the condition. Please use it for exactly that. There's `if` to execute different branches of the code.
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ The answer: `1`, and then `undefined`.
|
||||||
alert( alert(1) && alert(2) );
|
alert( alert(1) && alert(2) );
|
||||||
```
|
```
|
||||||
|
|
||||||
The call to `alert` does not return a value or, in other words, returns `undefined`.
|
The call to `alert` returns `undefined` (it just shows a message, so there's no meaningful return).
|
||||||
|
|
||||||
Because of that, `&&` evaluates the left operand (outputs `1`), and immediately stops, because `undefined` is a falsy value. And `&&` looks for a falsy value and returns it, so it's done.
|
Because of that, `&&` evaluates the left operand (outputs `1`), and immediately stops, because `undefined` is a falsy value. And `&&` looks for a falsy value and returns it, so it's done.
|
||||||
|
|
||||||
|
|
|
@ -30,9 +30,9 @@ alert( true || false ); // true
|
||||||
alert( false || false ); // false
|
alert( false || false ); // false
|
||||||
```
|
```
|
||||||
|
|
||||||
As we can see, most results are truthy except for the case when `false` is at both sides.
|
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 sake of evaluation.
|
If an operand is not boolean, then it's converted to boolean for the evaluation.
|
||||||
|
|
||||||
For instance, a number `1` is treated as `true`, a number `0` -- as `false`:
|
For instance, a number `1` is treated as `true`, a number `0` -- as `false`:
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ if (1 || 0) { // works just like if( true || false )
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Mainly, OR is used in the `if` expression to test for *any* of given conditions.
|
Mainly, OR is used in the `if` expression to test if *any* of given conditions is correct.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
@ -72,7 +72,9 @@ if (hour < 10 || hour > 18 || isWeekend) {
|
||||||
|
|
||||||
## OR seeks the first truthy value
|
## OR seeks the first truthy value
|
||||||
|
|
||||||
The logic described above is somewhat classical. Now let's see reconsider the logic of OR to cover nice features of JavaScript.
|
The logic described above is somewhat classical. Now let's bring in the "extra" features of JavaScipt.
|
||||||
|
|
||||||
|
The extended algorithm works as follows.
|
||||||
|
|
||||||
Given multiple OR'ed values:
|
Given multiple OR'ed values:
|
||||||
|
|
||||||
|
@ -84,10 +86,12 @@ The OR `"||"` operator is doing the following:
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>Evalutes operands from left to right.</li>
|
<li>Evalutes operands from left to right.</li>
|
||||||
<li>Returns the first value that would be truthy as a boolean, or the last one if all are falsy.</li>
|
<li>For each value converts it to boolean and stops immediately if it's true.</li>
|
||||||
<li>The value is returned "as is", without the conversion.</li>
|
<li>Returns the value where it stopped. The value is returned in it's original form, without the conversion.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
In other words, it returns the first truthy value or the last one if no such value found.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@ -95,8 +99,9 @@ For instance:
|
||||||
alert( 1 || 0 ); // 1 (is truthy)
|
alert( 1 || 0 ); // 1 (is truthy)
|
||||||
alert( true || 'no matter what' ); // (true is truthy)
|
alert( true || 'no matter what' ); // (true is truthy)
|
||||||
|
|
||||||
alert( null || 1 ); // 1 (null is falsy, so 1)
|
alert( null || 1 ); // 1 (1 is the first truthy)
|
||||||
alert( undefined || 0 ); // 0 (all falsy, so the last one)
|
alert( null || 0 || 1 ); // 1 (the first truthy)
|
||||||
|
alert( undefined || null || 0 ); // 0 (all falsy, returns the last value)
|
||||||
```
|
```
|
||||||
|
|
||||||
This logic does not contradict to what was spoken above. If you check this behavior with the boolean table, you see that it still works the same.
|
This logic does not contradict to what was spoken above. If you check this behavior with the boolean table, you see that it still works the same.
|
||||||
|
@ -104,9 +109,9 @@ This logic does not contradict to what was spoken above. If you check this behav
|
||||||
But there leads to some interesting usages compared to a "pure, classical, boolean-only OR".
|
But there leads to some interesting usages compared to a "pure, classical, boolean-only OR".
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>**Getting the first truthy value from the list.**
|
<li>**Getting the first truthy value from the list of variables or expressions.**
|
||||||
|
|
||||||
Imagine, we have several variables, which can be either set or non-set. And we need to choose the first one with data.
|
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.
|
||||||
|
|
||||||
Using OR for that:
|
Using OR for that:
|
||||||
|
|
||||||
|
@ -119,16 +124,16 @@ let defaultUser = "John";
|
||||||
let name = currentUser || defaultUser || "unnamed";
|
let name = currentUser || defaultUser || "unnamed";
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
alert( name ); // outputs "John" -- the first truthy value
|
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 then `"unnamed"` would be the result.
|
||||||
</li>
|
</li>
|
||||||
<li>**Short-circuit evaluation.**
|
<li>**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".
|
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.
|
||||||
|
|
||||||
This is especially notable when the expression given as the second argument has a side effect. Like 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` will not get assigned:
|
If we run the example below, `x` will not get assigned:
|
||||||
|
|
||||||
|
@ -141,7 +146,7 @@ let x;
|
||||||
alert(x); // undefined, (x = 1) not evaluated
|
alert(x); // undefined, (x = 1) not evaluated
|
||||||
```
|
```
|
||||||
|
|
||||||
...And here the first argument is `false`, so `OR` goes on and evaluates the second one thus running the assignment:
|
...And if the first argument were `false`, then `OR` would goes on and evaluate the second one thus running the assignment:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
//+ run no-beautify
|
||||||
|
@ -151,6 +156,10 @@ let x;
|
||||||
|
|
||||||
alert(x); // 1
|
alert(x); // 1
|
||||||
```
|
```
|
||||||
|
An assignment is a simple case, other side effects can be involved.
|
||||||
|
|
||||||
|
As we can see, such use case is a "shorter way to `if`". The first operand is converted to boolean and if it's false then the second one is evaluated. It's recommended to use `if` for that for code clarity.
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
@ -163,7 +172,7 @@ The AND operator is represented with two ampersands `&&`:
|
||||||
result = a && b;
|
result = a && b;
|
||||||
```
|
```
|
||||||
|
|
||||||
In classic programming AND returns `true` if both arguments are truthy and `false` -- otherwise:
|
In classic programming AND returns `true` if both operands are truthy and `false` -- otherwise:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -177,15 +186,15 @@ An example with `if`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
let hour = 12,
|
let hour = 12;
|
||||||
minute = 30;
|
let minute = 30;
|
||||||
|
|
||||||
if (hour == 12 && minute == 30) {
|
if (hour == 12 && minute == 30) {
|
||||||
alert( 'Time is 12:30' );
|
alert( 'Time is 12:30' );
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Just as in OR, any value is allowed in AND:
|
Just as for OR, any value is allowed as an operand of AND and gets converted to a boolean in the process:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -203,11 +212,13 @@ result = value1 && value2 && value3;
|
||||||
The AND `"&&"` operator is doing the following:
|
The AND `"&&"` operator is doing the following:
|
||||||
<ul>
|
<ul>
|
||||||
<li>Evalutes operands from left to right.</li>
|
<li>Evalutes operands from left to right.</li>
|
||||||
<li>Returns the first value that would be falsy as a boolean, or the last one if all are truthy.</li>
|
<li>For each value converts it to a boolean. If the result is `false`, stops.</li>
|
||||||
<li>The value is returned "as is", without the conversion.</li>
|
<li>Returns the value where it stopped "as is", without the conversion.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
The rules above are all-in-all similar to OR. The difference is that AND returns the first *falsy* value while OR returns the first *truthy* one.
|
In other words, AND returns the first falsy value or the last one if all are truthy.
|
||||||
|
|
||||||
|
The rules above are similar to OR. The difference is that AND returns the first *falsy* value while OR returns the first *truthy* one.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
|
@ -224,15 +235,21 @@ alert( null && 5 ); // null
|
||||||
alert( 0 && "no matter what" ); // 0
|
alert( 0 && "no matter what" ); // 0
|
||||||
```
|
```
|
||||||
|
|
||||||
If we pass several values in a row, the first falsy one is returned (or the last one if all of them are truthy):
|
We can also pass several values in a row. The first falsy one is returned:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
alert( 1 && 2 && null && 3 ); // null
|
alert( 1 && 2 && null && 3 ); // null
|
||||||
|
|
||||||
alert( 1 && 2 && 3 ); // 3
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
...Or the last one if all of them are truthy:
|
||||||
|
|
||||||
|
```js
|
||||||
|
//+ run
|
||||||
|
alert( 1 && 2 && 3 ); // 3, all truthy
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
[smart header="AND `&&` executes before OR `||`"]
|
[smart header="AND `&&` executes before OR `||`"]
|
||||||
The precedence of the AND `&&` operator is higher than OR `||`, so it executes before OR.
|
The precedence of the AND `&&` operator is higher than OR `||`, so it executes before OR.
|
||||||
|
|
||||||
|
@ -244,8 +261,7 @@ alert( 5 || 1 && 0 ); // 5
|
||||||
```
|
```
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
[warn header="Don't use `&&` instead of `if`"]
|
Just like OR, the AND `&&` operator can sometimes replace `if`.
|
||||||
The AND `&&` operator can sometimes replace `if`.
|
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
|
@ -269,8 +285,9 @@ if (x > 0) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The variant with `&&` appears to be shorter. Although `if` is more obvious and tends to be a little bit more readable. So it is recommended to use `if` if we want if. And use `&&` if we want AND.
|
The variant with `&&` appears to be shorter. But `if` is more obvious and tends to be a little bit more readable.
|
||||||
[/warn]
|
|
||||||
|
So it is recommended to use every construct for it's purpose. Use `if` if we want if. And use `&&` if we want AND.
|
||||||
|
|
||||||
## ! (NOT)
|
## ! (NOT)
|
||||||
|
|
||||||
|
@ -279,13 +296,13 @@ The boolean NOT operator is represented with an exclamation `"!"`.
|
||||||
The syntax is one of the simplest:
|
The syntax is one of the simplest:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let result = !value;
|
result = !value;
|
||||||
```
|
```
|
||||||
|
|
||||||
The operator accepts a single argument and does the following:
|
The operator accepts a single argument and does the following:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Converts the operand to logical type: `true/false`.</li>
|
<li>Converts the operand to boolean type: `true/false`.</li>
|
||||||
<li>Returns an inverse value.</li>
|
<li>Returns an inverse value.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
@ -297,7 +314,7 @@ alert( !true ); // false
|
||||||
alert( !0 ); // true
|
alert( !0 ); // true
|
||||||
```
|
```
|
||||||
|
|
||||||
A double NOT is sometimes used for converting a value to boolean:
|
A double NOT is sometimes used for converting a value to boolean type:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
|
|
@ -11,7 +11,7 @@ while (i) {
|
||||||
|
|
||||||
Every loop iteration decreases `i` by `1`. The check `while(i)` stops the loop when `i = 0`.
|
Every loop iteration decreases `i` by `1`. The check `while(i)` stops the loop when `i = 0`.
|
||||||
|
|
||||||
Hence, the steps of the loop make the following sequence ("unrolled"):
|
Hence, the steps of the loop make the following sequence ("loop unrolled"):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let i = 3;
|
let i = 3;
|
||||||
|
|
|
@ -9,4 +9,4 @@ for (let i = 2; i <= 10; i++) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
We use the "modulo" `%` to get the remainder and check for the evenness here.
|
We use the "modulo" operator `%` to get the remainder and check for the evenness here.
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
We often have a need to perform similar actions many times in a row.
|
We often have a need to perform similar actions many times in a row.
|
||||||
|
|
||||||
For example, when we need to output goods from the list one after another. Or just write the same code for each number from 1 to 10.
|
For example, when we need to output goods from the list one after another. Or just run the same code for each number from 1 to 10.
|
||||||
|
|
||||||
*Loops* are a way to repeat the same part code multiple times.
|
*Loops* are a way to repeat the same part of code multiple times.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
## The "while" loop
|
## The "while" loop
|
||||||
|
@ -13,18 +13,18 @@ The `while` loop has the following syntax:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
while (condition) {
|
while (condition) {
|
||||||
// code, loop body
|
// code ("loop body")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
While the `condition` is true -- the `code` from the loop body is executed.
|
While the `condition` is `true` -- the `code` from the loop body is executed.
|
||||||
|
|
||||||
For instance, the loop below outputs `i` while `i<3`:
|
For instance, the loop below outputs `i` while `i<3`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (i < 3) {
|
while (i < 3) { // shows 0, then 1, then 2
|
||||||
alert( i );
|
alert( i );
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,11 @@ while (i < 3) {
|
||||||
|
|
||||||
There's a special term *iteration* for each loop run. The loop in the example above makes 3 iterations.
|
There's a special term *iteration* for each loop run. The loop in the example above makes 3 iterations.
|
||||||
|
|
||||||
If there were no `i++` in the example above, the loop would repeat (in theory) forever. In practice, the browser would show a message about a "hanging" script and let the user stop it.
|
If there were no `i++` in the example above, the loop would repeat (in theory) forever, eating 100% CPU. In practice, the browser would show a message about a "hanging" script and let the user stop it.
|
||||||
|
|
||||||
The `condition` is treated as a logical value, so instead of `while (i!=0)` we usually write `while (i)`:
|
The `while` converts `condition` to a logical value. It can be any expression, not just a comparison.
|
||||||
|
|
||||||
|
For instance, the shorter way to write `while (i!=0)` could be `while (i)`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -83,7 +85,7 @@ do {
|
||||||
} while (i < 3);
|
} while (i < 3);
|
||||||
```
|
```
|
||||||
|
|
||||||
This form of syntax is rarely used, because the ordinary `while` is more obvious. We don't need to eye down the code looking for the condition. Neither we need to ask ourselves a question why it is checked at the bottom.
|
This form of syntax is rarely used, because the ordinary `while` is more obvious. We don't need to scroll down the code looking for the condition.
|
||||||
|
|
||||||
## The "for" loop
|
## The "for" loop
|
||||||
|
|
||||||
|
@ -97,7 +99,7 @@ for (begin; condition; step) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
An example of the loop which runs `alert(i)` for `i` from `0` to `3` not including `3`:
|
Let's see these parts in an example. The loop below runs `alert(i)` for `i` from `0` up to (but not including) `3`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -108,7 +110,7 @@ for (i = 0; i < 3; i++) { // shows 0, then 1, then 2
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Here:
|
Here the parts are:
|
||||||
<ul>
|
<ul>
|
||||||
<li>**Begin:** `i=0`.</li>
|
<li>**Begin:** `i=0`.</li>
|
||||||
<li>**Condition:** `i<3`.</li>
|
<li>**Condition:** `i<3`.</li>
|
||||||
|
@ -116,17 +118,23 @@ Here:
|
||||||
<li>**Body:** `alert(i)`, the code inside figure brackets. Brackets not required for a single statement.</li>
|
<li>**Body:** `alert(i)`, the code inside figure brackets. Brackets not required for a single statement.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
The loop execution follows these steps:
|
The `for` loop execution follows these steps:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Begin: `i=0` executes only once upon entering the loop.</li>
|
<li>**Begin**: `i=0` executes only once upon entering the loop.</li>
|
||||||
<li>Condition: `i<3` is checked before every iteration including the first one. If it fails, the loop stops.</li>
|
<li>**Condition**: `i<3` is checked before every iteration. If it fails, the loop stops.</li>
|
||||||
<li>Body: `alert(i)` runs is the condition is truthy.</li>
|
<li>**Body**: `alert(i)` runs is the condition is truthy.</li>
|
||||||
<li>Step: `i++` executes after the `body` on each iteration, but before the `condition` check.</li>
|
<li>**Step**: `i++` executes after the `body` on each iteration, but before the `condition` check.</li>
|
||||||
<li>Continue to step 2.</li>
|
<li>Continue to step 2.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
In other words, the execution flow is: `begin` -> (if `condition` -> run `body` and `step`) -> (if `condition` -> run `body` and `step`) -> ... and so on while the `condition` is truthy.
|
In other words, the execution flow is:
|
||||||
|
```
|
||||||
|
Begin
|
||||||
|
→ (if condition → run body and step)
|
||||||
|
→ (if condition → run body and step)
|
||||||
|
→ ... repeat until the condition is falsy.
|
||||||
|
```
|
||||||
|
|
||||||
[smart header="Inline variable declaration"]
|
[smart header="Inline variable declaration"]
|
||||||
We can declare a "counter" variable right in the beginning of the loop.
|
We can declare a "counter" variable right in the beginning of the loop.
|
||||||
|
@ -144,7 +152,9 @@ for (*!*let*/!* i = 0; i < 3; i++) {
|
||||||
|
|
||||||
Any part of the `for` can be skipped.
|
Any part of the `for` can be skipped.
|
||||||
|
|
||||||
For example, we can remove `begin`, or move it before the actual `for`, like here:
|
For example, we can omit `begin` if we don't need to do anything at the loop start.
|
||||||
|
|
||||||
|
Like here:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -177,7 +187,7 @@ for (;;) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Although the semicolons `;` must present, otherwise it would be a syntax error.
|
Please note that the semicolons `;` must present, otherwise it would be a syntax error.
|
||||||
|
|
||||||
[smart header="`for..in`"]
|
[smart header="`for..in`"]
|
||||||
There is also a special construct `for..in` to iterate over object properties.
|
There is also a special construct `for..in` to iterate over object properties.
|
||||||
|
@ -190,9 +200,9 @@ We'll get to it later while [talking about objects](#for..in).
|
||||||
|
|
||||||
Normally the loop exists when the condition becomes falsy.
|
Normally the loop exists when the condition becomes falsy.
|
||||||
|
|
||||||
But we can force the exit any moment. There's a special `break` directive for that.
|
But we can force the exit at any moment. There's a special `break` directive for that.
|
||||||
|
|
||||||
For example, this code calculates the sum of numbers until the user keeps entering them. And then outputs it:
|
For example, this code below asks user for numbers and breaks if no number entered:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
|
@ -217,9 +227,9 @@ Actually, the composition: "an infinite loop + break" is a great thing for situa
|
||||||
|
|
||||||
## Continue to the next iteration [#continue]
|
## Continue to the next iteration [#continue]
|
||||||
|
|
||||||
The `continue` is a younger sister of `break`. It doesn't break the whole loop, just the current body execution, as if it finished.
|
The `continue` directive is a younger sister of `break`. It doesn't stop the whole loop. Instead if 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 to the next one.
|
We can use it if we're done on the current iteration and would like to move on to the next.
|
||||||
|
|
||||||
The loop above uses `continue` to output only odd values:
|
The loop above uses `continue` to output only odd values:
|
||||||
|
|
||||||
|
@ -233,7 +243,7 @@ for (let i = 0; i < 10; i++) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For even `i` the `continue` directive stops body execution, passing the control to the next iteration of `for` (with the next number).
|
For even `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.
|
||||||
|
|
||||||
[smart header="`continue` allows to decrease nesting level"]
|
[smart header="`continue` allows to decrease nesting level"]
|
||||||
A loop for odd-only values could look like this:
|
A loop for odd-only values could look like this:
|
||||||
|
@ -248,42 +258,32 @@ for (let i = 0; i < 10; i++) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
From the technical point of view it's identical. Really, we can just wrap the code in the `if` block instead of `continue`.
|
From the technical point of view it's identical. Surely, we can just wrap the code in the `if` block instead of `continue`.
|
||||||
|
|
||||||
But we got one more figure brackets nesting level. If the code inside `if` is longer than a few lines, that lowers the overall readability.
|
But as a side-effect we got one more figure brackets nesting level. If the code inside `if` is longer than a few lines, that may decrease the overall readability.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
[warn header="No `break/continue` to the right side of '?'"]
|
[warn header="No `break/continue` to the right side of '?'"]
|
||||||
Usually it is possible to replace `if` with a question mark operator `'?'`.
|
Please note that syntax constructs that are not expressions cannot be used in `'?'`. In particular, directives `break/continue` are disallowed there.
|
||||||
|
|
||||||
The code:
|
For example, if one would rewrite an `if` like that into a question mark:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
if (condition) {
|
if (i > 5) {
|
||||||
a();
|
alert(i);
|
||||||
} else {
|
} else {
|
||||||
b();
|
continue;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
...Is identical to:
|
...Then the code like this will give a syntax error:
|
||||||
|
|
||||||
```js
|
|
||||||
condition ? a() : b();
|
|
||||||
```
|
|
||||||
|
|
||||||
In both cases either `a()` or `b()` is executed depending on the `condition`.
|
|
||||||
|
|
||||||
Please note that syntax constructs that do not return a value cannot be used in `'?'`. Directives `break/continue` are not expressions. They are disallowed here.
|
|
||||||
|
|
||||||
The code below will give a syntax error:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ no-beautify
|
//+ no-beautify
|
||||||
(i > 5) ? alert(i) : *!*continue*/!*;
|
(i > 5) ? alert(i) : *!*continue*/!*; // continue not allowed here
|
||||||
```
|
```
|
||||||
|
|
||||||
That's one more reason not to use a question mark operator `'?'` instead of `if`.
|
That's just another reason not to use a question mark operator `'?'` instead of `if`.
|
||||||
[/warn]
|
[/warn]
|
||||||
|
|
||||||
## Labels for break/continue
|
## Labels for break/continue
|
||||||
|
@ -300,7 +300,8 @@ for (let i = 0; i < 3; i++) {
|
||||||
for (let j = 0; j < 3; j++) {
|
for (let j = 0; j < 3; j++) {
|
||||||
|
|
||||||
let input = prompt(`Value at coords (${i},${j})`, '');
|
let input = prompt(`Value at coords (${i},${j})`, '');
|
||||||
// do something with the value...
|
|
||||||
|
// what if I want to exit from here?
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -310,7 +311,7 @@ alert('Done!');
|
||||||
|
|
||||||
Let's say we need a way to stop the process. Like if we user decides to cancel the input.
|
Let's say we need a way to stop the process. Like if we user decides to cancel the input.
|
||||||
|
|
||||||
The ordinary `break` after `input` would only break the iterations over `j`. That's not sufficient.
|
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:
|
A *label* is an identifier with a colon before a loop:
|
||||||
```js
|
```js
|
||||||
|
@ -354,7 +355,7 @@ for (let i = 0; i < 3; i++) { ... }
|
||||||
|
|
||||||
The `continue` directive can also be used with a label. In this case the execution would jump onto the next iteration of the labelled loop.
|
The `continue` directive can also be used with a label. In this case the execution would jump onto the next iteration of the labelled loop.
|
||||||
|
|
||||||
[warn header="Labels are not Goto"]
|
[warn header="Labels are not a \"goto\""]
|
||||||
Labels do not allow to jump into an arbitrary place of code.
|
Labels do not allow to jump into an arbitrary place of code.
|
||||||
|
|
||||||
For example, it is impossible to do like this:
|
For example, it is impossible to do like this:
|
||||||
|
@ -379,7 +380,7 @@ There are 3 types of loops in JavaScript:
|
||||||
|
|
||||||
To make in "infinite" loop, usually the `while(true)` construct is used. Such a loop, just like any other, can be stopped with the `break` directive.
|
To make in "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 more on this iteration and would like to forward on to the next one -- use the `continue` directive.
|
If we don't want to do anything more on this iteration and would like to forward on to the next one -- the `continue` directive does it.
|
||||||
|
|
||||||
Both these directives support labels before the loop. A label is the only way for `break/continue` to go up the nesting and affect the outer loop.
|
`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.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
|
||||||
# Let, const and var revisited
|
# Let, const and var revisited
|
||||||
|
|
||||||
|
**KILL ME**
|
||||||
|
|
||||||
Now as we know most language syntax constructs, let's discuss the subtle features and differences between variable definitions: `let`, `var` and `const`.
|
Now as we know most language syntax constructs, let's discuss the subtle features and differences between variable definitions: `let`, `var` and `const`.
|
||||||
|
|
||||||
## Let
|
## Let
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Оба варианта функции работают одинаково, отличий нет.
|
No difference.
|
|
@ -1,9 +1,10 @@
|
||||||
# Обязателен ли "else"?
|
# Is "else" required?
|
||||||
|
|
||||||
[importance 4]
|
[importance 4]
|
||||||
|
|
||||||
Следующая функция возвращает `true`, если параметр `age` больше `18`.
|
The following function returns `true` if the parameter `age` is greater than `18`.
|
||||||
В ином случае она задаёт вопрос посредством вызова `confirm` и возвращает его результат.
|
|
||||||
|
Otherwise it asks for a confirmation and returns its result:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function checkAge(age) {
|
function checkAge(age) {
|
||||||
|
@ -12,13 +13,13 @@ function checkAge(age) {
|
||||||
*!*
|
*!*
|
||||||
} else {
|
} else {
|
||||||
// ...
|
// ...
|
||||||
return confirm('Родители разрешили?');
|
return confirm('Did parents allow you?');
|
||||||
}
|
}
|
||||||
*/!*
|
*/!*
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Будет ли эта функция работать как-то иначе, если убрать `else`?
|
Will the function work differently if `else` is removed?
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function checkAge(age) {
|
function checkAge(age) {
|
||||||
|
@ -27,9 +28,9 @@ function checkAge(age) {
|
||||||
}
|
}
|
||||||
*!*
|
*!*
|
||||||
// ...
|
// ...
|
||||||
return confirm('Родители разрешили?');
|
return confirm('Did parents allow you?');
|
||||||
*/!*
|
*/!*
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Есть ли хоть одно отличие в поведении этого варианта?
|
Is there any difference in the bahavior of these two variants?
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
Используя оператор `'?'`:
|
Using a question mark operator `'?'`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function checkAge(age) {
|
function checkAge(age) {
|
||||||
return (age > 18) ? true : confirm('Родители разрешили?');
|
return (age > 18) ? true : confirm('Did parents allow you?');
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Используя оператор `||` (самый короткий вариант):
|
Using OR `||` (the shortest variant):
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function checkAge(age) {
|
function checkAge(age) {
|
||||||
return (age > 18) || confirm('Родители разрешили?');
|
return (age > 18) || confirm('Did parents allow you?');
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that the brackets around `age > 18` are not required here. They exist for better readabilty.
|
||||||
|
|
|
@ -1,23 +1,26 @@
|
||||||
# Перепишите функцию, используя оператор '?' или '||'
|
# Rewrite the function using '?' or '||'
|
||||||
|
|
||||||
[importance 4]
|
[importance 4]
|
||||||
|
|
||||||
Следующая функция возвращает `true`, если параметр `age` больше `18`.
|
|
||||||
В ином случае она задаёт вопрос `confirm` и возвращает его результат.
|
The following function returns `true` if the parameter `age` is greater than `18`.
|
||||||
|
|
||||||
|
Otherwise it asks for a confirmation and returns its result.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function checkAge(age) {
|
function checkAge(age) {
|
||||||
if (age > 18) {
|
if (age > 18) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return confirm('Родители разрешили?');
|
return confirm('Did parents allow you?');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Перепишите функцию, чтобы она делала то же самое, но без `if`, в одну строку.
|
Rewrite it, to perform the same, but without `if`, in a single line.
|
||||||
Сделайте два варианта функции `checkAge`:
|
|
||||||
|
Make two variants of `checkAge`:
|
||||||
<ol>
|
<ol>
|
||||||
<li>Используя оператор `'?'`</li>
|
<li>Using a question mark operator `'?'`</li>
|
||||||
<li>Используя оператор `||`</li>
|
<li>Using OR `||`</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Вариант решения с использованием `if`:
|
A solution using `if`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function min(a, b) {
|
function min(a, b) {
|
||||||
|
@ -10,7 +10,7 @@ function min(a, b) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Вариант решения с оператором `'?'`:
|
A solution with a question mark operator `'?'`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function min(a, b) {
|
function min(a, b) {
|
||||||
|
@ -18,4 +18,4 @@ function min(a, b) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
P.S. Случай равенства `a == b` здесь отдельно не рассматривается, так как при этом неважно, что возвращать.
|
P.S. In the case of an equality `a == b` it does not matter what to return.
|
|
@ -1,12 +1,10 @@
|
||||||
# Функция min
|
# Function min(a, b)
|
||||||
|
|
||||||
[importance 1]
|
[importance 1]
|
||||||
|
|
||||||
Задача "Hello World" для функций :)
|
Write a function `min(a,b)` which returns the least of two numbers `a` and `b`.
|
||||||
|
|
||||||
Напишите функцию `min(a,b)`, которая возвращает меньшее из чисел `a,b`.
|
For instance:
|
||||||
|
|
||||||
Пример вызовов:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
min(2, 5) == 2
|
min(2, 5) == 2
|
||||||
|
|
|
@ -1,19 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run demo
|
//+ run demo
|
||||||
/**
|
|
||||||
* Возводит x в степень n (комментарий JSDoc)
|
|
||||||
*
|
|
||||||
* @param {number} x число, которое возводится в степень
|
|
||||||
* @param {number} n степень, должна быть целым числом больше 1
|
|
||||||
*
|
|
||||||
* @return {number} x в степени n
|
|
||||||
*/
|
|
||||||
function pow(x, n) {
|
function pow(x, n) {
|
||||||
let result = x;
|
let result = x;
|
||||||
|
|
||||||
for let i = 1; i < n; i++) {
|
for (let i = 1; i < n; i++) {
|
||||||
result *= x;
|
result *= x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,9 +15,8 @@ let x = prompt("x?", '');
|
||||||
let n = prompt("n?", '');
|
let n = prompt("n?", '');
|
||||||
|
|
||||||
if (n <= 1) {
|
if (n <= 1) {
|
||||||
alert('Степень ' + n +
|
alert(`Power ${n} is not supported,
|
||||||
'не поддерживается, введите целую степень, большую 1'
|
use an integer greater than 0`);
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
alert( pow(x, n) );
|
alert( pow(x, n) );
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Функция pow(x,n)
|
# Function pow(x,n)
|
||||||
|
|
||||||
[importance 4]
|
[importance 4]
|
||||||
|
|
||||||
Напишите функцию `pow(x,n)`, которая возвращает `x` в степени `n`. Иначе говоря, умножает `x` на себя `n` раз и возвращает результат.
|
Write a function `pow(x,n)` that returns `x` in power `n`. Or, in other words, multiplies `x` by itself `n` times and returns the result.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
pow(3, 2) = 3 * 3 = 9
|
pow(3, 2) = 3 * 3 = 9
|
||||||
|
@ -10,8 +10,8 @@ pow(3, 3) = 3 * 3 * 3 = 27
|
||||||
pow(1, 100) = 1 * 1 * ...*1 = 1
|
pow(1, 100) = 1 * 1 * ...*1 = 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Создайте страницу, которая запрашивает `x` и `n`, а затем выводит результат `pow(x,n)`.
|
Create a web-page that prompts for `x` and `n`, and then shows the result of `pow(x,n)`.
|
||||||
|
|
||||||
[demo /]
|
[demo /]
|
||||||
|
|
||||||
P.S. В этой задаче функция обязана поддерживать только натуральные значения `n`, т.е. целые от `1` и выше.
|
P.S. In this task the function is allowed to support only natural values of `n`: integers up from `1`.
|
|
@ -107,18 +107,18 @@ alert( userName ); // Bob, the value was modified by the function
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
Of course if we had `let userName = ...` in the line (1) then the function would have a local variable `userName` and use it instead of the outer one:
|
If we had `let` before `userName` in the line (1) then the function would have a local variable `userName` and use it instead of the outer one:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
let *!*userName*/!* = 'John';
|
let userName = 'John';
|
||||||
|
|
||||||
function showMessage() {
|
function showMessage() {
|
||||||
*!*
|
*!*
|
||||||
let userName = "Bob"; // declare a local variable
|
let userName = "Bob"; // declare a local variable
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
let message = 'Hello, my name is ' + *!*userName*/!*;
|
let message = 'Hello, my name is ' + userName;
|
||||||
alert(message);
|
alert(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,21 +126,24 @@ function showMessage() {
|
||||||
showMessage();
|
showMessage();
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
alert( userName ); // John, the outer variable is not modified
|
alert( userName ); // John, unmodified
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
**Variables declared on the script level, outside of any function, are called *global*.**
|
## Global variables
|
||||||
|
|
||||||
|
Variables declared on the script level, outside of any function, are called *global*.
|
||||||
|
|
||||||
Global variables are visible from any function. They are used to store the data of a project-wide importance. Variables needed by specific tasks should reside in the corresponding functions.
|
Global variables are visible from any function. They are used to store the data of a project-wide importance. Variables needed by specific tasks should reside in the corresponding functions.
|
||||||
|
|
||||||
[warn header="Attention: implicit global declaration!"]
|
Without strict mode, for compatibility with the old scripts, it is possible to create a variable by an assignment, without a declaration.
|
||||||
Without strict mode, for compatibility with the old scripts, it is possible to create a variable by an assignment, without a declaration:
|
|
||||||
|
Consider the code below as an example. The variable `message` becomes global.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run no-strict
|
||||||
function showMessage() {
|
function showMessage() {
|
||||||
message = 'Hello'; // pure assignment, no declaration
|
message = 'Hello'; // (*) assignment without declaration!
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage();
|
showMessage();
|
||||||
|
@ -148,14 +151,11 @@ showMessage();
|
||||||
alert( message ); // Hello
|
alert( message ); // Hello
|
||||||
```
|
```
|
||||||
|
|
||||||
In the code above, most probably, the programmer simply forgot to write `let`. As a result, he created a global variable `message`.
|
With `"use strict"` there will be an error in line `(*)`. Without it, there won't be.
|
||||||
|
|
||||||
With `"use strict"` there will be an error.
|
In the code above, most probably, the programmer simply forgot to write `let`, so the error message is a good thing. A one more reason to `"use strict"` all the time.
|
||||||
|
|
||||||
Modern editors and tools for code quality checking like [jshint](http://jshint.com/) allow to see and fix "missed declarations" early while coding.
|
Modern editors and tools for code quality checking like [jshint](http://jshint.com/) allow to see and fix "missed declarations" early while coding.
|
||||||
[/warn]
|
|
||||||
|
|
||||||
Later, after we deal with the basics and data structures, in the chapter [](/closures) we will go deeper in the internals of functions and variables.
|
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ showMessage(from, "Hello");
|
||||||
alert( from ); // Ann
|
alert( from ); // Ann
|
||||||
```
|
```
|
||||||
|
|
||||||
## Default arguments
|
## Default parameter values
|
||||||
|
|
||||||
A function can be *called* with any number of arguments. If a parameter is not provided, but listed in the declaration, then its value becomes `undefined`.
|
A function can be *called* with any number of arguments. If a parameter is not provided, but listed in the declaration, then its value becomes `undefined`.
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ For instance, a function `showMessage(from, text)` can actually be called with a
|
||||||
showMessage("Ann");
|
showMessage("Ann");
|
||||||
```
|
```
|
||||||
|
|
||||||
Normally, such call would output `**Ann**: undefined`, because `text === undefined`.
|
Normally, such call would output `"Ann: undefined"`, because `text === undefined`.
|
||||||
|
|
||||||
But we can modify the function to detect missed parameter and assign a "default value" to it:
|
But we can modify the function to detect missed parameter and assign a "default value" to it:
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ function showMessage(from, text) {
|
||||||
|
|
||||||
This way is shorter, but the argument is considered missing also if it's falsy, like an empty line, `0` or `null`.
|
This way is shorter, but the argument is considered missing also if it's falsy, like an empty line, `0` or `null`.
|
||||||
</li>
|
</li>
|
||||||
<li>ES-2015 introduces an neater syntax for default values:
|
<li>ES-2015 introduced an neater syntax for default values:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -267,13 +267,11 @@ This syntax is not yet widely supported in the browsers, but available with the
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
A call may also provide more arguments than listed, for instance: `showMessage("Ann", "Hello", 1, 2, 3)`. Later we'll see how to read such extra arguments.
|
|
||||||
|
|
||||||
## Returning a value
|
## Returning a value
|
||||||
|
|
||||||
A function can do return a value into the calling code.
|
A function can return a value into the calling code as the result.
|
||||||
|
|
||||||
For instance, let's make a maths function `calcD` to calculate a [discriminant of a quadratic polynomial] using the formula <code>b<sup>2</sup> - 4ac</code>:
|
For instance, let's make a mathematical function `calcD` to calculate a [discriminant of a quadratic polynomial](https://en.wikipedia.org/wiki/Discriminant#Quadratic_formula) using the formula <code>b<sup>2</sup> - 4ac</code>:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
//+ run no-beautify
|
||||||
|
@ -330,16 +328,16 @@ In the code above, if `checkAge(age)` returns `false`, then `showMovie` won't pr
|
||||||
|
|
||||||
|
|
||||||
[smart header="A result with an empty or absent `return` returns `undefined`"]
|
[smart header="A result with an empty or absent `return` returns `undefined`"]
|
||||||
If a function does not return a value, it is considered to return `undefined`:
|
If a function does not return a value, it is the same as if it returns `undefined`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
function doNothing() { /* пусто */ }
|
function doNothing() { /* empty */ }
|
||||||
|
|
||||||
alert( doNothing() ); // undefined
|
alert( doNothing() ); // undefined
|
||||||
```
|
```
|
||||||
|
|
||||||
The same happens when the `return` has no argument:
|
A `return` with no argument is also the same as `return undefined`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -358,44 +356,44 @@ Functions are actions. So their name is usually a verb.
|
||||||
|
|
||||||
Usually, function names have verbal prefixes which vaguely describe the action.
|
Usually, function names have verbal prefixes which vaguely describe the action.
|
||||||
|
|
||||||
Functions that shart with `"show"` -- usually show something:
|
There is an agreement within the team on the terms here. For instance, functions that start with `"show"` -- usually show something:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ no-beautify
|
//+ no-beautify
|
||||||
showMessage(..) // shows a message
|
showMessage(..) // shows a message
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Function starting with...
|
||||||
<ul>
|
<ul>
|
||||||
<li>Functions starting with `"get"` -- allow to get something,</li>
|
<li>`"get"` -- allow to get something,</li>
|
||||||
<li>with `"calc"` -- calculate something,</li>
|
<li>`"calc"` -- calculate something,</li>
|
||||||
<li>with `"create"` -- create something,</li>
|
<li>`"create"` -- create something,</li>
|
||||||
<li>with `"check"` -- check something and return a boolean, etc.</li>
|
<li>`"check"` -- check something and return a boolean, etc.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
Examples of such names:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ no-beautify
|
//+ no-beautify
|
||||||
getAge(..) // returns the age (gets it somehow)
|
getAge(..) // return the age (get it somehow)
|
||||||
calcD(..) // calculate a discriminant and return the result
|
calcD(..) // calculate a discriminant and return the result
|
||||||
createForm(..) // create a form, usually returns it
|
createForm(..) // create a form, usually returns it
|
||||||
checkPermission(..) // check a permission, return true/false
|
checkPermission(..) // check a permission, return true/false
|
||||||
```
|
```
|
||||||
|
|
||||||
Most prefixes are reused across multiple projects and understood by the community.
|
That's very convenient. The prefix itself is a great hint. A glance on a function name gives an understanding what it does and what kind of value it returns.
|
||||||
|
|
||||||
That's very convenient: a glance on a function name gives an understanding what it does and what kind of value it returns.
|
|
||||||
|
|
||||||
[smart header="One function -- one action"]
|
[smart header="One function -- one action"]
|
||||||
A function should do exactly what is suggested by its name. If many subactions are involved and the code becomes large -- maybe it's worth to separate them in their functions.
|
A function should do exactly what is suggested by its name.
|
||||||
|
|
||||||
By all means, two independant actions deserve two functions, even if they are related.
|
Two independant actions usually deserve two functions, even if they are usually called together (in that case we can make a 3rd function calling those two).
|
||||||
|
|
||||||
A few examples of "shouldn't" to see what I mean here:
|
Few examples of "shouldn't do" for the names listed above:
|
||||||
<ul>
|
<ul>
|
||||||
<li>`getAge` should not show update `age` or change it or show anything to the visitor.</li>
|
<li>`getAge` -- should not change `age`, also it should not show anything to the visitor.</li>
|
||||||
<li>`calcD` should not save a calculated discriminant "for future reuse". It should only calculate it.</li>
|
<li>`calcD` -- should not store a calculated discriminant "for future reuse" anywhere. It should only calculate and return it.</li>
|
||||||
<li>`createForm` should not show the form to the user or modify something in the document. It should only create it.</li>
|
<li>`createForm` -- should not show the form to the user or modify something in the document. It should only create it and return.</li>
|
||||||
<li>`checkPermission` should only perform the check and return the result. Not display the `access granted/access denied` message.</li>
|
<li>`checkPermission` -- should only perform the check and return the result. It should not display the `access granted/denied` message.</li>
|
||||||
</ul>
|
</ul>
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
@ -427,7 +425,7 @@ It is possible for a function to access variables defined outside of it.
|
||||||
|
|
||||||
But to make the code cleaner and easier to understand, it's recommended to use local variables and parameters instead as much as possible.
|
But to make the code cleaner and easier to understand, it's recommended to use local variables and parameters instead as much as possible.
|
||||||
|
|
||||||
It is always easier to understand a function which gets parameters `a, b, c`, works with them and returns a result, than a function which gets no parameters, but modifies outer `a, b, c` somewhere in it's code.
|
It is always easier to understand a function which gets parameters, works with them and returns a result than a function which gets no parameters, but modifies outer variables as a side-effect.
|
||||||
|
|
||||||
Function naming:
|
Function naming:
|
||||||
|
|
||||||
|
|
|
@ -1,290 +1,212 @@
|
||||||
# Функциональные выражения
|
# Function expressions
|
||||||
|
|
||||||
В JavaScript функция является значением, таким же как строка или число.
|
In JavaScript a function is a value. Just like a string or a number.
|
||||||
|
|
||||||
Как и любое значение, объявленную функцию можно вывести, вот так:
|
Let's output it using `alert`:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
function sayHi() {
|
function sayHi() {
|
||||||
alert( "Привет" );
|
alert( "Hello" );
|
||||||
}
|
}
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
alert( sayHi ); // выведет код функции
|
alert( sayHi ); // shows the function code
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
Обратим внимание на то, что в последней строке после `sayHi` нет скобок. То есть, функция не вызывается, а просто выводится на экран.
|
Note that there are no brackets after `sayHi` in the last line. The function is not called there.
|
||||||
|
|
||||||
**Функцию можно скопировать в другую переменную:**
|
The code above only shows the string representation of the function, that is it's source code.
|
||||||
|
|
||||||
|
[cut]
|
||||||
|
|
||||||
|
|
||||||
|
As the function is a value, we can copy it to another variable:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
//+ run no-beautify
|
||||||
function sayHi() { // (1)
|
function sayHi() { // (1)
|
||||||
alert( "Привет" );
|
alert( "Hello" );
|
||||||
}
|
}
|
||||||
|
|
||||||
let func = sayHi; // (2)
|
let func = sayHi; // (2)
|
||||||
func(); // Привет // (3)
|
func(); // Hello // (3)
|
||||||
|
|
||||||
sayHi = null;
|
sayHi = null; // (4)
|
||||||
sayHi(); // ошибка (4)
|
sayHi(); // error
|
||||||
```
|
```
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>Объявление `(1)` как бы говорит интерпретатору "создай функцию и помести её в переменную `sayHi`</li>
|
<li>Function declaration `(1)` creates the function and puts it into the variable `sayHi`"</li>
|
||||||
<li>В строке `(2)` мы копируем функцию в новую переменную `func`. Ещё раз обратите внимание: после `sayHi` нет скобок. Если бы они были, то вызов `let func = sayHi()` записал бы в `func` *результат* работы `sayHi()` (кстати, чему он равен? правильно, `undefined`, ведь внутри `sayHi` нет `return`).</li>
|
<li>Line `(2)` copies it into variable `func`.
|
||||||
<li>На момент `(3)` функцию можно вызывать и как `sayHi()` и как `func()`</li>
|
|
||||||
<li>...Однако, в любой момент значение переменной можно поменять. При этом, если оно не функция, то вызов `(4)` выдаст ошибку.</li>
|
Please note again: there are no brackets after `sayHi`. If they were, then the call `let func = sayHi()` would write a *result* of `sayHi()` into `func`, not the function itself.</li>
|
||||||
|
<li>At the moment `(3)` the function can be called both as `sayHi()` and `func()`.</li>
|
||||||
|
<li>...We can overwrite `sayHi` easily. As well as `func`, they are normal variables. Naturally, the call attempt would fail in the case `(4)`.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
Обычные значения, такие как числа или строки, представляют собой *данные*. А функцию можно воспринимать как *действие*.
|
[smart header="A function is an \"action value\""]
|
||||||
|
Regular values like strings or numbers represent the *data*.
|
||||||
|
|
||||||
Это действие можно запустить через скобки `()`, а можно и скопировать в другую переменную, как было продемонстрировано выше.
|
A function can be perceived as an *action*.
|
||||||
|
|
||||||
|
A function declaration creates that action and puts it into a variable of the given name. Then we can run it via brackets `()` or copy into another variable.
|
||||||
|
[/smart]
|
||||||
|
|
||||||
## Объявление Function Expression [#function-expression]
|
## Function Expression [#function-expression]
|
||||||
|
|
||||||
Существует альтернативный синтаксис для объявления функции, который ещё более наглядно показывает, что функция -- это всего лишь разновидность значения переменной.
|
There is an alternative syntax for creating a function. It much more clearly shows that a function is just a kind of a value.
|
||||||
|
|
||||||
Он называется "Function Expression" (функциональное выражение) и выглядит так:
|
It is called "Function Expression" and looks like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
let f = function(параметры) {
|
let func = function(parameters) {
|
||||||
// тело функции
|
// body
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Например:
|
For instance:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
let sayHi = function(person) {
|
let sayHi = function(person) {
|
||||||
alert( "Привет, " + person );
|
alert( `Hello, ${person}` );
|
||||||
};
|
};
|
||||||
|
|
||||||
sayHi('Вася');
|
sayHi('John'); // Hello, John
|
||||||
```
|
```
|
||||||
|
|
||||||
## Сравнение с Function Declaration
|
The function created in the example above is fully functional and identical to:
|
||||||
|
|
||||||
"Классическое" объявление функции, о котором мы говорили до этого, вида `function имя(параметры) {...}`, называется в спецификации языка "Function Declaration".
|
```js
|
||||||
|
function sayHi(person) {
|
||||||
|
alert( `Hello, ${person}` );
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Comparison with Function Declaration
|
||||||
|
|
||||||
|
The "classic" syntax of the function that looks like `function name(params) {...}` is called a "Function Declaration".
|
||||||
|
|
||||||
|
We can formulate the following distinction:
|
||||||
<ul>
|
<ul>
|
||||||
<li>*Function Declaration* -- функция, объявленная в основном потоке кода.</li>
|
<li>*Function Declaration* -- is a function, declared as a separate code statement.
|
||||||
<li>*Function Expression* -- объявление функции в контексте какого-либо выражения, например присваивания.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Несмотря на немного разный вид, по сути две эти записи делают одно и то же:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// Function Declaration
|
// Function Declaration
|
||||||
function sum(a, b) {
|
function sum(a, b) {
|
||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>*Function Expression* -- is a function, created in the context of an another expression, for example, an assignment.
|
||||||
|
|
||||||
|
```js
|
||||||
// Function Expression
|
// Function Expression
|
||||||
let sum = function(a, b) {
|
let sum = function(a, b) {
|
||||||
return a + b;
|
return a + b;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
Оба этих объявления говорят интерпретатору: "объяви переменную `sum`, создай функцию с указанными параметрами и кодом и сохрани её в `sum`".
|
The main difference between them is the creation time.
|
||||||
|
|
||||||
**Основное отличие между ними: функции, объявленные как Function Declaration, создаются интерпретатором до выполнения кода.**
|
**Function Declarations are processed before the code begins to execute.**
|
||||||
|
|
||||||
Поэтому их можно вызвать *до* объявления, например:
|
In other words, when JavaScript prepares to run the code block, it looks for Function Declarations in it and creates the functions. We can think of it as an "initialization stage". Then it runs the code.
|
||||||
|
|
||||||
|
As a side-effect, functions declared as Function Declaration can be called before they are defined.
|
||||||
|
|
||||||
|
For instance, this works:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run refresh untrusted
|
//+ run refresh untrusted
|
||||||
*!*
|
*!*
|
||||||
sayHi("Вася"); // Привет, Вася
|
sayHi("John"); // Hello, John
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
function sayHi(name) {
|
function sayHi(name) {
|
||||||
alert( "Привет, " + name );
|
alert( `Hello, ${name}` );
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
А если бы это было объявление Function Expression, то такой вызов бы не сработал:
|
...And if there were Function Expression, then it wouldn't work:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run refresh untrusted
|
//+ run refresh untrusted
|
||||||
*!*
|
*!*
|
||||||
sayHi("Вася"); // ошибка!
|
sayHi("John"); // error!
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
let sayHi = function(name) {
|
let sayHi = function(name) { // (*)
|
||||||
alert( "Привет, " + name );
|
alert( `Hello, ${name}` );
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Это из-за того, что JavaScript перед запуском кода ищет в нём Function Declaration (их легко найти: они не являются частью выражений и начинаются со слова `function`) и обрабатывает их.
|
Function Expressions are created in the process of evaluation of the expression with them.
|
||||||
|
|
||||||
А Function Expression создаются в процессе выполнении выражения, в котором созданы, в данном случае -- функция будет создана при операции присваивания `sayHi = function...`
|
So, in the code above, the function is created and assigned to sayHi only when the execution reaches line `(*)`.
|
||||||
|
|
||||||
Как правило, возможность Function Declaration вызвать функцию до объявления -- это удобно, так как даёт больше свободы в том, как организовать свой код.
|
Usually this is viewed as a bonus of the Function Declaration. Convenient, isn't it? Gives more freedom in how to organize our code.
|
||||||
|
|
||||||
Можно расположить функции внизу, а их вызов -- сверху или наоборот.
|
## Anonymous functions
|
||||||
|
|
||||||
### Условное объявление функции [#bad-conditional-declaration]
|
As a function is a value, it can be created on-demand and passed to another place of the code.
|
||||||
|
|
||||||
В некоторых случаях "дополнительное удобство" Function Declaration может сослужить плохую службу.
|
For instance, let's consider the following task, coming from a real-life.
|
||||||
|
|
||||||
Например, попробуем, в зависимости от условия, объявить функцию `sayHi` по-разному:
|
Function `ask(question, yes, no)` should accept a question and two other functions: `yes` and `no`. It asks a question and, if the user responds positively, executes `yes()`, otherwise `no()`.
|
||||||
|
|
||||||
|
It could look like this:
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
let age = +prompt("Сколько вам лет?", 20);
|
|
||||||
|
|
||||||
if (age >= 18) {
|
|
||||||
function sayHi() {
|
|
||||||
alert( 'Прошу вас!' );
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
function sayHi() {
|
|
||||||
alert( 'До 18 нельзя' );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sayHi();
|
|
||||||
```
|
|
||||||
|
|
||||||
При вводе `20` в примере выше в любом браузере, кроме Firefox, мы увидим, что условное объявление не работает. Срабатывает `"До 18 нельзя"`, несмотря на то, что `age = 20`.
|
|
||||||
|
|
||||||
В чём дело? Чтобы ответить на этот вопрос -- вспомним, как работают функции.
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Function Declaration обрабатываются перед запуском кода. Интерпретатор сканирует код и создает из таких объявлений функции. При этом второе объявление перезапишет первое.
|
|
||||||
</li>
|
|
||||||
<li>Дальше, во время выполнения, объявления Function Declaration игнорируются (они уже были обработаны). Это как если бы код был таким:
|
|
||||||
|
|
||||||
```js
|
|
||||||
function sayHi() {
|
|
||||||
alert( 'Прошу вас!' );
|
|
||||||
}
|
|
||||||
|
|
||||||
function sayHi() {
|
|
||||||
alert( 'До 18 нельзя' );
|
|
||||||
}
|
|
||||||
|
|
||||||
let age = 20;
|
|
||||||
|
|
||||||
if (age >= 18) {
|
|
||||||
/* объявление было обработано ранее */
|
|
||||||
} else {
|
|
||||||
/* объявление было обработано ранее */
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
sayHi(); // "До 18 нельзя", сработает всегда вторая функция
|
|
||||||
*/!*
|
|
||||||
```
|
|
||||||
|
|
||||||
...То есть, от `if` здесь уже ничего не зависит. По-разному объявить функцию, в зависимости от условия, не получилось.
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
Такое поведение соответствует современному стандарту. На момент написания этого раздела ему следуют все браузеры, кроме, как ни странно, Firefox.
|
|
||||||
|
|
||||||
**Вывод: для условного объявления функций Function Declaration не годится.**
|
|
||||||
|
|
||||||
А что, если использовать Function Expression?
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
let age = prompt('Сколько вам лет?');
|
|
||||||
|
|
||||||
let sayHi;
|
|
||||||
|
|
||||||
if (age >= 18) {
|
|
||||||
sayHi = function() {
|
|
||||||
alert( 'Прошу Вас!' );
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sayHi = function() {
|
|
||||||
alert( 'До 18 нельзя' );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sayHi();
|
|
||||||
```
|
|
||||||
|
|
||||||
Или даже так:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run no-beautify
|
|
||||||
let age = prompt('Сколько вам лет?');
|
|
||||||
|
|
||||||
let sayHi = (age >= 18) ?
|
|
||||||
function() { alert('Прошу Вас!'); } :
|
|
||||||
function() { alert('До 18 нельзя'); };
|
|
||||||
|
|
||||||
sayHi();
|
|
||||||
```
|
|
||||||
|
|
||||||
Оба этих варианта работают правильно, поскольку, в зависимости от условия, создаётся именно та функция, которая нужна.
|
|
||||||
|
|
||||||
### Анонимные функции
|
|
||||||
|
|
||||||
Взглянем ещё на один пример.
|
|
||||||
|
|
||||||
Функция `ask(question, yes, no)` предназначена для выбора действия в зависимости от результата `f`.
|
|
||||||
|
|
||||||
Она выводит вопрос на подтверждение `question` и, в зависимости от согласия пользователя, вызывает `yes` или `no`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
//+ run
|
|
||||||
*!*
|
|
||||||
function ask(question, yes, no) {
|
function ask(question, yes, no) {
|
||||||
if (confirm(question)) yes()
|
if (confirm(question)) yes()
|
||||||
else no();
|
else no();
|
||||||
}
|
}
|
||||||
*/!*
|
```
|
||||||
|
|
||||||
|
In real-life `ask` is usually much more complex. It draws a nice windows instead of the simple `confirm` to ask a question. But that would make it much longer, so here we skip this part.
|
||||||
|
|
||||||
|
So, how do we use it?
|
||||||
|
|
||||||
|
If we had only Function Declarations in our toolbox, we could declare `showOk/showCancel` as actions and pass them:
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
function showOk() {
|
function showOk() {
|
||||||
alert( "Вы согласились." );
|
alert( "Ok, proceeding." );
|
||||||
}
|
}
|
||||||
|
|
||||||
function showCancel() {
|
function showCancel() {
|
||||||
alert( "Вы отменили выполнение." );
|
alert( "Execution canceled." );
|
||||||
}
|
}
|
||||||
|
|
||||||
// использование
|
// usage
|
||||||
ask("Вы согласны?", showOk, showCancel);
|
ask("Should we proceed?", showOk, showCancel);
|
||||||
```
|
```
|
||||||
|
|
||||||
Какой-то очень простой код, не правда ли? Зачем, вообще, может понадобиться такая `ask`?
|
...But Function Expressions allow us to solve the task much more elegantly:
|
||||||
|
|
||||||
...Но при работе со страницей такие функции как раз очень востребованы, только вот спрашивают они не простым `confirm`, а выводят более красивое окно с вопросом и могут интеллектуально обработать ввод посетителя. Но это всё в своё время.
|
|
||||||
|
|
||||||
Здесь обратим внимание на то, что то же самое можно написать более коротко:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
ask("Should we proceed?",
|
||||||
function ask(question, yes, no) {
|
function() { alert( "Ok, proceeding." ); },
|
||||||
if (confirm(question)) yes()
|
function() { alert( "Execution canceled." ); },
|
||||||
else no();
|
|
||||||
}
|
|
||||||
|
|
||||||
*!*
|
|
||||||
ask(
|
|
||||||
"Вы согласны?",
|
|
||||||
function() { alert("Вы согласились."); },
|
|
||||||
function() { alert("Вы отменили выполнение."); }
|
|
||||||
);
|
);
|
||||||
*/!*
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Здесь функции объявлены прямо внутри вызова `ask(...)`, даже без присвоения им имени.
|
So, we can declare a function in-place, right when we need it.
|
||||||
|
|
||||||
**Функциональное выражение, которое не записывается в переменную, называют [анонимной функцией](http://ru.wikipedia.org/wiki/%D0%90%D0%BD%D0%BE%D0%BD%D0%B8%D0%BC%D0%BD%D0%B0%D1%8F_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F).**
|
Such functions are sometimes called "anonymous" meaning that they are defined without a name.
|
||||||
|
|
||||||
Действительно, зачем нам записывать функцию в переменную, если мы не собираемся вызывать её ещё раз? Можно просто объявить непосредственно там, где функция нужна.
|
And here, if we don't plan to call the function again, why should we give it a name? Let's just declare it where needed and pass on.
|
||||||
|
|
||||||
Такого рода код возникает естественно, он соответствует "духу" JavaScript.
|
That's very natural and in the spirit of JavaScript.
|
||||||
|
|
||||||
## new Function
|
## new Function
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# External scripts
|
# External scripts
|
||||||
|
|
||||||
If we have a lot of JavaScript code, it can be put into a separate file.
|
If we have a lot of JavaScript code, we can it put it into a separate file.
|
||||||
|
|
||||||
The script file is attached to HTML like this:
|
The script file is attached to HTML like this:
|
||||||
|
|
||||||
|
@ -10,13 +10,14 @@ The script file is attached to HTML like this:
|
||||||
|
|
||||||
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 file with the script (from the site root).
|
||||||
|
|
||||||
We can give a full URL too, for instance:
|
It is also possible to provide a path relative to the current page. For instance, `src="script.js"` would mean a file `"script.js"` from the current folder.
|
||||||
|
|
||||||
|
We can give a full URL al well, for instance:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
It is also possible to provide a path relative to the current page. For instance, `src="lodash.js"` means a file from the current folder.
|
|
||||||
|
|
||||||
To attach several scripts, use multiple tags:
|
To attach several scripts, use multiple tags:
|
||||||
|
|
||||||
|
@ -29,16 +30,16 @@ To attach several scripts, use multiple tags:
|
||||||
[smart]
|
[smart]
|
||||||
As a rule, only simplest scripts are put into HTML. More complex ones reside in separate files.
|
As a rule, only simplest scripts are put into HTML. More complex ones reside in separate files.
|
||||||
|
|
||||||
The benefit is that the browser will download it only once, and then store in its [cache](https://en.wikipedia.org/wiki/Web_cache).
|
The benefit of a separate file is that the browser will download it and then store in its [cache](https://en.wikipedia.org/wiki/Web_cache).
|
||||||
|
|
||||||
After it, other pages which want the same script will take it from the cache instead of downloading it. So the file is actually downloaded only once.
|
After it, other pages which want 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 the future pages faster.
|
That saves traffic and makes pages faster.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
|
||||||
[warn header="If `src` is set, the script content is ignored."]
|
[warn header="If `src` is set, the script content is ignored."]
|
||||||
A single `<script>` tag may not both contain an `src` and the code.
|
A single `<script>` tag may not have both an `src` and the code inside.
|
||||||
|
|
||||||
This won't work:
|
This won't work:
|
||||||
|
|
||||||
|
@ -48,7 +49,9 @@ This won't work:
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
One needs to choose: either `<script src="…">` or `<script>` with code. The example above should be split into two scripts: with `src` and with the code.
|
We must choose: either it's an external `<script src="…">` or a regular `<script>` with code.
|
||||||
|
|
||||||
|
The example above can be split into two scripts to work:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="file.js"></script>
|
<script src="file.js"></script>
|
||||||
|
@ -62,7 +65,7 @@ One needs to choose: either `<script src="…">` or `<script>` with code. The ex
|
||||||
|
|
||||||
Browser loads and shows HTML gradually as it comes. That's clearly noticeable on the slow internet connection. The browser doesn't wait for the page to load fully. It shows the part that has been loaded already, and then adds content to it as it loads.
|
Browser loads and shows HTML gradually as it comes. That's clearly noticeable on the slow internet connection. The browser doesn't wait for the page to load fully. It shows the part that has been loaded already, and then adds content to it as it loads.
|
||||||
|
|
||||||
When the browser meets a `<script>` tag, it must execute it first and after that show the rest of the page.
|
As we noted before, when the browser meets a `<script>` tag, it must execute it first and after that show the rest of the page.
|
||||||
|
|
||||||
For example, in the code below -- until all rabbits are counted, the bottom `<p>` is not shown:
|
For example, in the code below -- until all rabbits are counted, the bottom `<p>` is not shown:
|
||||||
|
|
||||||
|
@ -107,18 +110,30 @@ So, in this document, until `big.js` loads and executes, the `<body>` content is
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
|
||||||
The question is -- do we really want that?
|
The question is -- do we really want to hide the body until the script finishes?
|
||||||
|
|
||||||
Most of time, we don't.
|
Most of time, we don't.
|
||||||
|
|
||||||
Sometimes, the script contains a very important code that really must be loaded before the rest of the page is parsed (and the scripts below executed). But most of time we'd like to let the visitor see the content while the script is loading.
|
Sometimes, a script may contain a very important code that really must be loaded before the rest of the page is parsed (and the scripts below executed). But that's an exception.
|
||||||
|
|
||||||
|
Usually it's ok that a visitor can see the page content while the script is loading.
|
||||||
|
|
||||||
[warn header="Blocking is dangerous"]
|
[warn header="Blocking is dangerous"]
|
||||||
There are situations when such blocking is even dangerous.
|
There are situations when such blocking is even dangerous.
|
||||||
|
|
||||||
Let's say we attach a script from the banner system, or a 3rd-party integration code.
|
Let's say we attach a script from the banner system, or a 3rd-party integration code.
|
||||||
|
|
||||||
It's just wrong that the rest of the page is not shown until the banner system initialized. The banner is not that important.
|
Like this:
|
||||||
|
|
||||||
|
```html
|
||||||
|
Information below is not shown until the script loads and executes.
|
||||||
|
|
||||||
|
<script src="https://ad.service/path/to/banner"></script>
|
||||||
|
|
||||||
|
<p>…Important information!</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
It's just wrong that the rest of the page is not shown until the banner is loaded. The banner is not that important.
|
||||||
|
|
||||||
And what if their server is overloaded and responds slowly? Our visitors will wait even more.
|
And what if their server is overloaded and responds slowly? Our visitors will wait even more.
|
||||||
|
|
||||||
|
@ -126,9 +141,9 @@ Here's an example of such "slow" script (the delay is artificial here):
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!--+ run height=100 -->
|
<!--+ run height=100 -->
|
||||||
<p>Important information below is not shown until the script loads and executes.</p>
|
Wait. The text belown will shown up only after the script executes.
|
||||||
|
|
||||||
<script src="https://en.js.cx/hello/ads.js?speed=0"></script>
|
<script src="/article/external-script/banner.js?speed=0"></script>
|
||||||
|
|
||||||
<p>…Important information!</p>
|
<p>…Important information!</p>
|
||||||
```
|
```
|
||||||
|
@ -141,8 +156,8 @@ Our first attempt could be to put all such scripts to the bottom of the `<body>`
|
||||||
But the solution is not perfect:
|
But the solution is not perfect:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>The script won't start loading until the whole page loads. If the page is large, then the delay may be significant. We'd like the browser to start loading a script early.</li>
|
<li>The script won't start loading until the whole page loads. If the page is large, then the delay may be significant. We'd like the browser to start loading a script early, but still do not block the page.</li>
|
||||||
<li>If there is more than one scripts at the bottom of the page, and the first script is slow, then the second one will still has wait for it. Scripts still depend one on another, that's not always welcome. Ads and counter can run independently.</li>
|
<li>If there is more than one script at the bottom of the page, and the first script is slow, then the second one will have to wait for it. Browser executes only one `<script>` tag in one moment. So scripts queue one after another. That's not always welcome: ads and counter should run independently.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
And here come the attributes `async` and `defer`.
|
And here come the attributes `async` and `defer`.
|
||||||
|
@ -151,43 +166,13 @@ And here come the attributes `async` and `defer`.
|
||||||
<dt>The `async` attribute.</dt>
|
<dt>The `async` attribute.</dt>
|
||||||
<dd>The script is executed asynchronously. In other words, when the browser meets `<script async src="...">`, it does not stop showing the page. It just initiates script loading and goes on. When the script loads -- it runs.</dd>
|
<dd>The script is executed asynchronously. In other words, when the browser meets `<script async src="...">`, it does not stop showing the page. It just initiates script loading and goes on. When the script loads -- it runs.</dd>
|
||||||
<dt>The `defer` attribute.</dt>
|
<dt>The `defer` attribute.</dt>
|
||||||
<dd>The script with `defer` also executes asynchronously, like async. But there are two essential differences.
|
<dd>The script with `defer` also executes asynchronously, like async. But there are two essential differences:
|
||||||
|
|
||||||
First -- the browser guarantees to keep the relative order of scripts with `defer`.
|
<ol>
|
||||||
|
<li>The browser guarantees to keep the relative order of "deferred" scripts.</li>
|
||||||
For example, in the code below (with `async`) there are two scripts. The one which loads first will run first.
|
<li>A "deferred" script always executes after HTML-document is fully loaded.</li>
|
||||||
|
</ol>
|
||||||
```html
|
We'll discuss them more in-depth further in this chapter.
|
||||||
<script src="1.js" async></script>
|
|
||||||
<script src="2.js" async></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
With `async` it may happen that `2.js` will run before `1.js`. Scripts are totally independent.
|
|
||||||
|
|
||||||
And in the other code `defer` is used, which forces browser to keeps execution order. Even if `2.js` loads first, it will execute after `1.js`:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="1.js" defer></script>
|
|
||||||
<script src="2.js" defer></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
So `defer` is used when the second script `2.js` depends on the first one `1.js`, say uses something described in the first script.
|
|
||||||
|
|
||||||
The second difference -- script with `defer` always works when the HTML-document is fully processed by the browser.
|
|
||||||
|
|
||||||
For example, when the document is large...
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="async.js" async></script>
|
|
||||||
<script src="defer.js" defer></script>
|
|
||||||
|
|
||||||
Too long text. Didn't read. Many words.
|
|
||||||
```
|
|
||||||
|
|
||||||
...Then `async.js` executes when it runs -- possibly, before the text is fully loaded. In contrast, `defer.js` always waits for the full document to be ready.
|
|
||||||
|
|
||||||
It's great to have the choice here. Sometimes a script doesn't need the document at all (like a counter), then `async` is superb. And if we need the whole document to process it, then `defer` will work nice.
|
|
||||||
</dd>
|
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
[smart header="`async` together with `defer`"]
|
[smart header="`async` together with `defer`"]
|
||||||
|
@ -202,25 +187,83 @@ On a script without `src` like <code><script>...</script></code>, th
|
||||||
|
|
||||||
Let's modify the "blocking script" example that we've seen before, adding `async`:
|
Let's modify the "blocking script" example that we've seen before, adding `async`:
|
||||||
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!--+ run height=100 -->
|
<!--+ run height=100 -->
|
||||||
<p>Important information below is not shown until the script loads and executes.</p>
|
Wait. The text belown will shown up only after the script executes.
|
||||||
|
|
||||||
<script *!*async*/!* src="https://en.js.cx/hello/ads.js?speed=0"></script>
|
<script *!*async*/!* src="/article/external-script/banner.js?speed=0"></script>
|
||||||
|
|
||||||
<p>…Important information!</p>
|
<p>…Important information!</p>
|
||||||
```
|
```
|
||||||
|
|
||||||
Now if we run it, we'll see that the whole document is displayed immediately, and the external script runs when it loads.
|
Now if we run it, we'll see that the whole document is displayed immediately, and the external script runs when it loads.
|
||||||
|
|
||||||
|
|
||||||
|
## Defer vs Async: order
|
||||||
|
|
||||||
|
Let's discuss these differences in more detail.
|
||||||
|
|
||||||
|
For example, in the code below (with `async`) there are two scripts. The one which loads first will run first.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="1.js" async></script>
|
||||||
|
<script src="2.js" async></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
If `2.js` is bigger than `1.js`, it may happen that `2.js` will run before `1.js`. That's normal. Async scripts are totally independent.
|
||||||
|
|
||||||
|
And in the code below `defer` is used, which forces browser to keeps execution order. Even if `2.js` loads first, it waits and executes after `1.js`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="1.js" defer></script>
|
||||||
|
<script src="2.js" defer></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
This feature of "deferred" scripts is important when `2.js` relies on the result of `1.js` and we must be sure that the order is determined.
|
||||||
|
|
||||||
|
## Defer vs Async: page
|
||||||
|
|
||||||
|
A script with `defer` always works when the HTML-document is fully processed by the browser.
|
||||||
|
|
||||||
|
That feature comes into play when the document is large, like:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="async.js" async></script>
|
||||||
|
<script src="defer.js" defer></script>
|
||||||
|
|
||||||
|
Too long text. Didn't read. Many words.
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
...Here `async.js` executes when it loads -- possibly, before the document is fully loaded.
|
||||||
|
|
||||||
|
In contrast, `defer.js` always waits for the full document to be ready.
|
||||||
|
|
||||||
|
The choice between `defer` and `async` here depends on our intentions. Sometimes a script doesn't need the document at all (like a counter), it should execute ASAP. In this case `async` is superb.
|
||||||
|
|
||||||
|
And in another case a script may need the whole document to do some work with it. Then `defer` is preferable.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Scripts in an external file can be inserted on the page via `<script src="path"></script>`.</li>
|
||||||
|
<li>Normally, the browser doesn't show the document after the script until it executes. Unless the script has `async` or `defer` attributes.</li>
|
||||||
|
<li>Both `async` and `defer` allow the browser to start script loading and then continue to parse/show the page. They only work on external scripts.</li>
|
||||||
|
<li>The difference is that `defer` keeps the relative script order and always executes after the document is fully loaded. In contrast, `async` script executes when it loads, without any conditions.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Before inserting an external `<script src="…">` tag, we should always consider the side-effect of blocking the page rendering. Especially if it's a 3rd-party script. And if we don't want that, then `defer/async` can come in handy.
|
||||||
|
|
||||||
|
|
||||||
[smart header="Running ahead..."]
|
[smart header="Running ahead..."]
|
||||||
For an advanced reader who knows that new tags can be added on page dynamically, we'd like to note that the `<script>` tags added in such a way behave as if they have `async`.
|
For an advanced reader who knows that new tags can be added on page dynamically, we'd like to note that dynamic `<script>` tags behave as if they have `async` by default.
|
||||||
|
|
||||||
In other words, they run as they load without an order.
|
In other words, they run as they load without an order.
|
||||||
|
|
||||||
If we'd like to add several `<script>` tags on the page and keep their execution order, it is possible via `script.async = false`.
|
We can ensure that dynamic `<script>` tags run in the order of insertion by setting `script.async` to `false`.
|
||||||
|
|
||||||
Like this:
|
The code example:
|
||||||
```js
|
```js
|
||||||
function addScript(src){
|
function addScript(src){
|
||||||
let script = document.createElement('script');
|
let script = document.createElement('script');
|
||||||
|
@ -236,21 +279,8 @@ addScript('2.js'); // but execute in the order of insertion
|
||||||
addScript('3.js'); // that is: 1 -> 2 -> 3
|
addScript('3.js'); // that is: 1 -> 2 -> 3
|
||||||
```
|
```
|
||||||
|
|
||||||
We'll cover page manipulation in detail later, in the second part of the tutorial.
|
We'll cover page dynamic tags and page manipulation in detail later, in the second part of the tutorial.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Scripts in an external file can be inserted on the page via `<script src="path"></script>`.</li>
|
|
||||||
<li>Normally, the browser doesn't show the document after the script until it executes. Unless the script has `async` or `defer` attributes.</li>
|
|
||||||
<li>Both `async` and `defer` allow the browser to start script loading and then continue to parse/show the page. They both only work on external scripts.</li>
|
|
||||||
<li>The difference is that `defer` keeps the relative script order and always executes after the document is fully loaded. In contrast, `async` script executes when it loads, without any conditions.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Most modern systems that provide scripts know about these attributes and use them.
|
|
||||||
|
|
||||||
Before inserting an external `<script>` tag, one should always check if it should block the page or not. Especially if it's a 3rd-party script. And if not, then `defer/async` can come in handy.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
1
1-js/2-first-steps/2-external-script/banner.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
alert("Banner loaded!");
|
|
@ -5,11 +5,11 @@ In this section we explore the code structure and statements.
|
||||||
[cut]
|
[cut]
|
||||||
## Statements
|
## Statements
|
||||||
|
|
||||||
We've already seen an example of a statement: `alert('Hello, world!')` shows the message.
|
We've already seen a statement: `alert('Hello, world!')`, which shows the message.
|
||||||
|
|
||||||
To add one more statement to the code, it can be separated with a semicolon.
|
Another statement can be separated with a semicolon.
|
||||||
|
|
||||||
Let's make split the message into two messages:
|
For example, here we split the message into two:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
//+ run no-beautify
|
||||||
|
@ -38,7 +38,7 @@ alert( 'World' )
|
||||||
|
|
||||||
In this case JavaScript interprets the line break as a splitter and automatically assumes a "virtual" semicolon between them.
|
In this case JavaScript interprets the line break as a splitter and automatically assumes a "virtual" semicolon between them.
|
||||||
|
|
||||||
**But it's important that "in most cases" does not mean "always"!**
|
**But it's important that "in most cases" does not mean "always" here!**
|
||||||
|
|
||||||
Consider this code as an example:
|
Consider this code as an example:
|
||||||
|
|
||||||
|
@ -51,9 +51,9 @@ alert(3 +
|
||||||
|
|
||||||
It outputs `6`.
|
It outputs `6`.
|
||||||
|
|
||||||
JavaScript does not insert semicolons here. It is intuitively obvious that the reason is the "uncomplete expression". JavaScript notes that the line ends with a plus `+` and awaits the expression to continue on the next line. And that is actually fine and comfortable here.
|
JavaScript does not insert semicolons here. It is intuitively obvious that the first lines are an "uncomplete expression". JavaScript notes that and awaits the rest of the expression on the next line. And in this case that's actually fine and comfortable.
|
||||||
|
|
||||||
**But there are situations where "fails" to assume a semicolon where it is really needed.**
|
**But there are situations where JavaScript "fails" to assume a semicolon where it is really needed.**
|
||||||
|
|
||||||
Errors which come appear in such cases are quite hard to find and fix.
|
Errors which come appear in such cases are quite hard to find and fix.
|
||||||
|
|
||||||
|
@ -65,23 +65,23 @@ For a curious reader who might be interested in a concrete example, check this c
|
||||||
[1, 2].forEach(alert)
|
[1, 2].forEach(alert)
|
||||||
```
|
```
|
||||||
|
|
||||||
It shows `1` then `2`. How it works -- does not matter now, we'll get deal with it later.
|
It shows `1` then `2`. What `[...].forEach` means -- does not matter here (we'll get it later). Now let's insert an `alert` without a semicolon before it:
|
||||||
|
|
||||||
But let's insert an `alert` without a semicolon before it:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
//+ run no-beautify
|
||||||
alert( "Without a semicolon we get an error here" )
|
alert( "Without a semicolon we get an error here" ) // printed
|
||||||
[1, 2].forEach(alert)
|
[1, 2].forEach(alert) // not printed
|
||||||
```
|
```
|
||||||
|
|
||||||
Now only the first `alert` is shown, not the numbers. And we can see an error in the browser console. That's actually because JavaScript does not insert a semicolon before square brackets `[...]` and misinterprets the code.
|
Now only the phrase is shown, not the numbers. And we can see an error in the developer console.
|
||||||
|
|
||||||
|
That's exactly because JavaScript engine did not insert a semicolon before square brackets `[...]` and misunderstood the code.
|
||||||
|
|
||||||
Everything's fine if we add a semicolon:
|
Everything's fine if we add a semicolon:
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
alert( "With the semicolon everything works" );
|
alert( "With the semicolon everything works" ); // printed
|
||||||
[1, 2].forEach(alert)
|
[1, 2].forEach(alert) // printed too
|
||||||
```
|
```
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
@ -91,17 +91,19 @@ As a conclusion, it's recommended to put semicolons between statements even if t
|
||||||
|
|
||||||
As the time goes, the program becomes more and more complex. It becomes necessary to add *comments* which describe what happens and why.
|
As the time goes, the program becomes more and more complex. It becomes necessary to add *comments* which describe what happens and why.
|
||||||
|
|
||||||
Comments can be put into any place of the script. They don't affect it's execution. The JavaScript interpreter simply ignores them.
|
Comments can be put into any place of the script. They don't affect it's execution. The JavaScript engine simply ignores them.
|
||||||
|
|
||||||
*One-line comments* start with a double slash `//`. The text after them till the end of line is considered a comment.
|
*One-line comments* start with a double slash `//`. The text after them till the end of line is considered a comment.
|
||||||
|
|
||||||
It may occupy a full line of it's own or follow a statement, like this:
|
It may occupy a full line of it's own or follow a statement.
|
||||||
|
|
||||||
|
Like this:
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
// The statement below outputs a word "Hello"
|
// This says "Hello" (the comment occupies a line of it's own)
|
||||||
alert( 'Hello' );
|
alert( 'Hello' );
|
||||||
|
|
||||||
alert( 'World' ); // ...The second word is shown separately.
|
alert( 'World' ); // ...this says "World" (the comment follows a statement)
|
||||||
```
|
```
|
||||||
|
|
||||||
*Multiline comments* start with a slash and a star <code>"/*"</code> and end with a star and a slash <code>"*/"</code>, like this:
|
*Multiline comments* start with a slash and a star <code>"/*"</code> and end with a star and a slash <code>"*/"</code>, like this:
|
||||||
|
@ -115,14 +117,16 @@ alert( 'Hello' );
|
||||||
alert( 'World' );
|
alert( 'World' );
|
||||||
```
|
```
|
||||||
|
|
||||||
All contents of comments is ignored. If we put a code inside <code>/* ... */</code> or after `//` it won't execute.
|
The content of comments is ignored, so if we put a code inside <code>/* ... */</code> or after `//` it won't execute.
|
||||||
|
|
||||||
|
Sometimes it's used to temporarily disable a part of the code.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
/* Commenting out the code
|
/* Commenting out the code
|
||||||
alert( 'Привет' );
|
alert( 'Hello' );
|
||||||
*/
|
*/
|
||||||
alert( 'Мир' );
|
alert( 'World' );
|
||||||
```
|
```
|
||||||
|
|
||||||
[smart header="Use hotkeys!"]
|
[smart header="Use hotkeys!"]
|
||||||
|
@ -147,5 +151,21 @@ Don't hesitate to comment. They more code is in the project -- the more helpful
|
||||||
|
|
||||||
Comments increase the overall code footprint, but that's not a problem at all, because there are many tools which minify the code before publishing to production server and remove comments in the process.
|
Comments increase the overall code footprint, but that's not a problem at all, because there are many tools which minify the code before publishing to production server and remove comments in the process.
|
||||||
|
|
||||||
In our next sections we'll talk about other structure elements of JavaScript like blocks, variables and more.
|
There are various types of comments, answering different questions:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>What the code does?</li>
|
||||||
|
<li>Why the code is written like that?</li>
|
||||||
|
<li>Which counter-intuitive or implicit connections it has with other parts of the script?</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
All these comments are highly valuable.
|
||||||
|
|
||||||
|
[smart header="The good code is inherently readable and self-commenting"]
|
||||||
|
Please note that the first type of comments ("what the code does") should be used to describe a "high-level" action, like the overall architecture, a function or a chunk of code. It's purpose is to give an overview, so a reader doesn't need to delve into the code and figure out.
|
||||||
|
|
||||||
|
Novice programmers sometimes tend to elaborate too much. Please don't. The good code is inherently readable. No need to describe what few lines do. Unless it's something hard to grasp, and *then* it's worth to consider rewriting the code at the first place rather than commenting it.
|
||||||
|
[/smart]
|
||||||
|
|
||||||
|
Further in the tutorial we'll make more notes about how to write the code better, easier to read and maintain.
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
# The modern mode, "use strict"
|
# The modern mode, "use strict"
|
||||||
|
|
||||||
For a long time JavaScript was evolving without compatibility issues. New features got added to the language, but the old ones did not change.
|
For a long time JavaScript was evolving without compatibility issues. New features were added to the language, but the old functionality did not change.
|
||||||
|
|
||||||
That had the benefit of never breaking the existing code. But the back side is that any mistake or imprefect decision made by JavaScript creators got stuck in the language forever.
|
That had the benefit of never breaking the existing code. But the back side is that any mistake or an imprefect decision made by JavaScript creators got stuck in the language forever.
|
||||||
|
|
||||||
It had been so before EcmaScript 5 (ES5) appeared with added new features to the language and modified some of the existing ones.
|
It had been so before EcmaScript 5 (ES5) appeared which added new features to the language and modified some of the existing ones.
|
||||||
|
|
||||||
To keep old code working, by default most modifications of the pre-existing features are off. We need to enable them manually with a special directive `use strict`.
|
To keep the old code working, most modifications of the pre-existing features are off by default. One need to enable them explicitly with a special directive `"use strict"`.
|
||||||
|
|
||||||
[cut]
|
[cut]
|
||||||
|
|
||||||
## "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 on the top of the script, then the whole script works the "modern" way.
|
||||||
|
|
||||||
For example
|
For example
|
||||||
|
|
||||||
|
@ -30,26 +30,29 @@ Once we enter the strict mode, there's no return.
|
||||||
[/warn]
|
[/warn]
|
||||||
|
|
||||||
[smart header="`use strict` for functions"]
|
[smart header="`use strict` for functions"]
|
||||||
We will learn [functions](/function-basics) very soon. For the future let's note that `use strict` can be put at the start of a function instead of the whole script. Then the strict mode is enabled in this function only.
|
We will learn [functions](/function-basics) very soon. Looking ahead let's just note that `"use strict"` can be put at the start of a function instead of the whole script. Then the strict mode is enabled in this function only.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
## Do I really need "use strict"?
|
## Do I really need "use strict"?
|
||||||
|
|
||||||
Actually, yes. All modern browsers support it.
|
Actually, yes. All modern browsers support it.
|
||||||
|
|
||||||
More than that, there are several JavaScript features that enable strict mode implicitly, "by default". So if we use for example [classes](/classes) (covered later), they implicitly switch the interpreter to "strict mode".
|
More than that, there are several JavaScript features that enable strict mode implicitly, "by default". Namely, "classes" and "modules" automatically switch the interpreter to "strict mode".
|
||||||
|
|
||||||
So there's no way to stay modern and evade `"use strict"`. Let's put it everywhere.
|
So there's no way to stay modern and evade `"use strict"`.
|
||||||
|
|
||||||
The only downside is Internet Explorer prior to version 10. Those browsers do not support `use strict`, so we need to make sure that our code is compatible with the old behavior. That won't cause any problems for us.
|
The only downside is Internet Explorer prior to version 10. Those browsers do not support `"use strict"`, so if we plan to support them, then our code must be compatible with the old behavior. But that's an easy thing to do if we keep it in mind.
|
||||||
|
|
||||||
Another minor problem is the 3rd party libraries, few of them are written without `use strict` in mind and do not work correctly when the calling code is in the strict mode. But that happens very rarely.
|
Sometimes we can pass by a 3rd party library that do not work correctly when the calling code is in the strict mode. But that happens very rarely, and can usually be fixed on a per-library basis.
|
||||||
|
|
||||||
All code in the tutorial works correctly in `use strict`.
|
Here in the tutorial all code works correctly in `"use strict"`.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Here we got acquanted with the notion of a "strict mode".
|
<ul>
|
||||||
|
<li>JavaScript without `"use strict"` may execute differently in some cases. Further in the tutorial we'll see what's different. Thankfully, not so much.</li>
|
||||||
|
<li>Several modern features of the language enable `"use strict"` implicitly, so there's just no way to evade it.</li>
|
||||||
|
<li>It is strongly advised to `"use strict"` everywhere, but keep in mind compability if we are to support old IE.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
It is strongly advised to use it everywhere. Very soon, in the next chapter we'll see the differences of the strict mode in examples.
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# Variables
|
# Variables
|
||||||
|
|
||||||
Depending on our aims, the script needs to work with the information.
|
Most of the time, script needs to work with the information.
|
||||||
|
|
||||||
If it's an online-shop -- that's going to be the goods and the card. If it's a chat -- visitors, messages and so on.
|
If it's an online-shop -- that's going to be the goods and a shopping cart. If it's a chat -- visitors, messages and so on.
|
||||||
|
|
||||||
Variables are used to store the information.
|
Variables are used to store the information.
|
||||||
|
|
||||||
|
@ -10,9 +10,9 @@ Variables are used to store the information.
|
||||||
|
|
||||||
## A variable
|
## A variable
|
||||||
|
|
||||||
A [variable]("https://en.wikipedia.org/wiki/Variable_(computer_science)") is a "named storage" for the information.
|
A [variable]("https://en.wikipedia.org/wiki/Variable_(computer_science)") is defined as a "named storage" for the information. We can use variables to store the goods, visitors etc.
|
||||||
|
|
||||||
To declare a variable in JavaScript, we need to use the `let` keyword.
|
To create a variable in JavaScript, we need to use the `let` keyword.
|
||||||
|
|
||||||
The statement below creates (in other words: *declares* or *defines*) the variable with the name "message":
|
The statement below creates (in other words: *declares* or *defines*) the variable with the name "message":
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ The statement below creates (in other words: *declares* or *defines*) the variab
|
||||||
let message;
|
let message;
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we can assign some data into it:
|
Now we can store some data in it:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let message;
|
let message;
|
||||||
|
@ -30,14 +30,16 @@ message = 'Hello'; // store the string
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
|
||||||
The string is now saved into the associated memory area. We can access it using the variable name:
|
The string is now saved into the memory area assosiated with that variable. We can access it using the variable name:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
let message;
|
let message;
|
||||||
message = 'Hello!';
|
message = 'Hello!';
|
||||||
|
|
||||||
|
*!*
|
||||||
alert( message ); // shows the variable content
|
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 merge the variable declaration and assignment into a single line:
|
||||||
|
@ -55,7 +57,7 @@ let user = 'John', age = 25, message = 'Hello';
|
||||||
|
|
||||||
That might seem shorter, but it's recommended, for the sake of beter readability, to use a single line per variable.
|
That might seem shorter, but it's recommended, for the sake of beter readability, to use a single line per variable.
|
||||||
|
|
||||||
This code is a bit longer, but easier to read:
|
The rewritten code is a bit longer, but easier to read:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ no-beautify
|
//+ no-beautify
|
||||||
|
@ -73,14 +75,14 @@ In older scripts you may also find another keyword: `var` instead of `let`:
|
||||||
|
|
||||||
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" fashion.
|
||||||
|
|
||||||
The subtle differences does not matter yet, but we'll cover them in detail later when going deeper into the language.
|
The subtle differences does not matter for us yet. We'll cover them in detail later when going deeper into the language.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
## Real-life analogy
|
## Real-life analogy
|
||||||
|
|
||||||
We can easily grasp the concept of a "variable" if we imagine it as a "box" for the data, with the unique-named sticker on it.
|
We can easily grasp the concept of a "variable" if we imagine it as a "box" for the data, with the unique-named sticker on it.
|
||||||
|
|
||||||
For instance, the variable `message` is a box with the value `"Hello!" labelled `"message"`:
|
For instance, the variable `message` is a box with the value `"Hello!"` labelled `"message"`:
|
||||||
|
|
||||||
<img src="variable.png">
|
<img src="variable.png">
|
||||||
|
|
||||||
|
@ -103,7 +105,7 @@ When the value is changed, the old data is removed from the variable:
|
||||||
|
|
||||||
<img src="variable-change.png">
|
<img src="variable-change.png">
|
||||||
|
|
||||||
We can also declare two variables and copy data from one into another.
|
We can also declare two variables and copy the data from one into the other.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -116,37 +118,36 @@ let message;
|
||||||
message = hello;
|
message = hello;
|
||||||
*/!*
|
*/!*
|
||||||
|
|
||||||
|
// now two variables have the same data
|
||||||
alert( hello ); // Hello world!
|
alert( hello ); // Hello world!
|
||||||
alert( message ); // Hello world!
|
alert( message ); // Hello world!
|
||||||
```
|
```
|
||||||
|
|
||||||
[smart]
|
[smart]
|
||||||
It may seem that the ability to change a value is natural, but it's really not so.
|
It may be interesting to know that there also exist [functional](http://ru.wikipedia.org/wiki/%D0%AF%D0%B7%D1%8B%D0%BA_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F) programming languages that forbid to change a variable value. For example, [Scala](http://www.scala-lang.org/) or [Erlang](http://www.erlang.org/).
|
||||||
|
|
||||||
There also exist [functional](http://ru.wikipedia.org/wiki/%D0%AF%D0%B7%D1%8B%D0%BA_%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F) programming languages that forbid to change a variable once it's assigned. For example, [Scala](http://www.scala-lang.org/) and [Erlang](http://www.erlang.org/).
|
|
||||||
|
|
||||||
In such languages, once the value is in the box -- it's there forever. If we need to store something else -- please create a new box (declare a new variable), can't reuse the old one.
|
In such languages, once the value is in the box -- it's there forever. If we need to store something else -- please create a new box (declare a new variable), can't reuse the old one.
|
||||||
|
|
||||||
Though it may seem a little bit odd at the first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation infers certain benefits. Studying of such a language (even if not planning to use soon) is recommended to broaden the mind.
|
Though it may seem a little bit odd at the first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation infers certain benefits. Studying of such a language (even if you're not planning to use it soon) is recommended to broaden the mind.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
## Variable naming [#variable-naming]
|
## Variable naming [#variable-naming]
|
||||||
|
|
||||||
There are two limitations on the variable name in JavaScript:
|
There are two limitations for the variable name in JavaScript:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>The name must contain only letters, digits, symbols `$` and `_`.</li>
|
<li>The name must contain only letters, digits, symbols `$` and `_`.</li>
|
||||||
<li>The first character must not be a digit.</li>
|
<li>The first character must not be a digit.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
Valid name examples:
|
Valid names, for instance:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let userName;
|
let userName;
|
||||||
let test123;
|
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 with the capital letter at start: `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 starts with a capital letter: `myVeryLongName`.
|
||||||
|
|
||||||
What's interesting -- the dollar sign `'$'` and the underscore `'_'` are considered ordinary symbols, just like letters.
|
What's interesting -- the dollar sign `'$'` and the underscore `'_'` are considered ordinary symbols, just like letters.
|
||||||
|
|
||||||
|
@ -173,29 +174,29 @@ let my-name; // a hyphen '-' is not allowed in the name
|
||||||
Variables named `apple` and `AppLE` -- are two different variables.
|
Variables named `apple` and `AppLE` -- are two different variables.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
[smart header="Non-english letters are allowed, but not recommended."]
|
[smart header="Non-english letters are allowed, but not recommended"]
|
||||||
|
|
||||||
It is possible to use cyrillic letters or even hieroglyphs, like this:
|
It is possible to use any language, including cyrillic letters or even hieroglyphs, like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
|
||||||
let имя = 123;
|
let имя = 123;
|
||||||
let 我 = 456;
|
let 我 = 456;
|
||||||
```
|
```
|
||||||
|
|
||||||
Technically, there is no error here, but there is a tradition to use only latin alphabet in variable names.
|
Technically, there is no error here, but there is an international tradition to use english words in variable names. Even if you're writing a small script, it may have a long life ahead. People coming with another language background may need to read it some time.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
[warn header="Reserved names"]
|
[warn header="Reserved names"]
|
||||||
There is a list of reserved words, which cannot be used as variable names, because they are used by the language itself.
|
There is a list of reserved words, which cannot be used as variable names, because they are used by the language itself.
|
||||||
|
|
||||||
For example: `var, class, return, function` and alike are reserved.
|
For example, words `let`, `class`, `return`, `function` are reserved.
|
||||||
|
|
||||||
The code below will give a syntax error:
|
The code below will give a syntax error:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
//+ run no-beautify
|
||||||
let return = 5; // error!
|
let let = 5; // can't name a variable "let", error!
|
||||||
|
let return = 5; // also can't name it "return", error!
|
||||||
```
|
```
|
||||||
[/warn]
|
[/warn]
|
||||||
|
|
||||||
|
@ -211,9 +212,9 @@ num = 5; // the variable "num" is created if didn't exist
|
||||||
alert(num);
|
alert(num);
|
||||||
```
|
```
|
||||||
|
|
||||||
...But that is allowed for compatibility with the old scripts. It is a frowned-upon feature that is disabled in the strict mode.
|
...But that is an old feature of the language, kept for compatibility with the old scripts. It's usage has been frowned-upon for a long time. It's an error in the strict mode.
|
||||||
|
|
||||||
The code below will give an error:
|
The code with `"use strict"` will give an error:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run untrusted
|
//+ run untrusted
|
||||||
|
@ -232,23 +233,24 @@ There is no strict mode here:
|
||||||
```js
|
```js
|
||||||
//+ run untrusted no-strict
|
//+ run untrusted no-strict
|
||||||
alert("some code");
|
alert("some code");
|
||||||
// …
|
// "use strict" below is ignored, must be not on the top
|
||||||
|
|
||||||
"use strict"; // ignored, because not on top
|
"use strict";
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
num = 5; // No error! strict mode is not activated
|
num = 5; // No error! strict mode is not activated
|
||||||
*/!*
|
*/!*
|
||||||
```
|
```
|
||||||
|
Only comments may appear in the script before `"use strict"`, they are not counted.
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
## Chrome (V8) needs "use strict" for "let"
|
## V8 needs "use strict" for "let"
|
||||||
|
|
||||||
In Chrome browser and Node.JS, both powered by V8 engine, `"use strict"` is required if we want to use `let` and many other modern features of the language.
|
In Chrome browser, Opera and Node.JS, powered by V8 engine, `"use strict"` is required if we want to use `let` and many other modern features of the language.
|
||||||
|
|
||||||
Here, on-site, most examples are evaluated with implicit (omitted in text, but auto-added before execution) `"use strict"`.
|
Here, in the tutorial, there's a code which executes examples, and it auto-adds `"use strict"` most of time (except those rare cases which are described in the text as "without use strict").
|
||||||
|
|
||||||
But when you write JS, make sure that you do not forget `"use strict". Otherwise using `let` will give you an error in console.
|
But when you write JS, make sure that you do not forget `"use strict"`. Otherwise using `let` in V8 will give you an error.
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
||||||
|
@ -263,10 +265,12 @@ const myBirthday = '18.04.1982';
|
||||||
myBirthday = '01.01.2001'; // error!
|
myBirthday = '01.01.2001'; // error!
|
||||||
```
|
```
|
||||||
|
|
||||||
A constant variable never changes, so the further code can rely on that to calculate dependant values (like the age or the sign of the zodiac). It can be sure that the calculations are always valid.
|
A constant variable never changes, so the further code can rely on that to calculate dependant values. For example, the age or a sign of the zodiac.
|
||||||
|
|
||||||
|
We can be sure that the calculated variables always hold a valid value, because `myBirthday` is not going to change.
|
||||||
|
|
||||||
[smart header="CONSTANT_NAMING"]
|
[smart header="CONSTANT_NAMING"]
|
||||||
There is a widespread practice to use constants as aliases for difficult-to-remember values. Such constants are named using capitals and underscores.
|
There is a widespread practice to use constants as aliases for difficult-to-remember and hard-coded prior to execution values. Such constants are named using capitals and underscores.
|
||||||
|
|
||||||
Like this:
|
Like this:
|
||||||
|
|
||||||
|
@ -281,41 +285,52 @@ let color = COLOR_ORANGE;
|
||||||
alert( color ); // #FF7F00
|
alert( color ); // #FF7F00
|
||||||
```
|
```
|
||||||
|
|
||||||
`COLOR_ORANGE` is easy to understand and remember. It is much easier to grasp what `color = COLOR_ORANGE` means than `color = "#FF7F00"`.
|
`COLOR_ORANGE` is much easier to understand and remember than `"#FF7F00"`. Also it is much easier to make a typo in `"#FF7F00"` than in `COLOR_ORANGE`. Finally, that makes the code more readable.
|
||||||
|
|
||||||
Besides, it is much easier to make a typo in `"#FF7F00"` than in `COLOR_ORANGE`.
|
|
||||||
|
|
||||||
So, sometimes constants are used as aliases to complex values, to evade errors and make the code more readable.
|
|
||||||
[/smart]
|
[/smart]
|
||||||
|
|
||||||
|
## Name things right
|
||||||
|
|
||||||
|
And there's one more thing.
|
||||||
|
|
||||||
|
Please name the variables sensibly.
|
||||||
|
|
||||||
|
Variable naming is one of the most important and complex skills in programming. Just looking at variable names can obviously show which code is written by a beginner and which by an experienced guru.
|
||||||
|
|
||||||
|
In the real project, most of time is spent on modifying and extending the existing code, rather than writing something completely torn-off and new.
|
||||||
|
|
||||||
|
And when we return to the code after some time of absence, it's much easier to find the information that is well-labelled. Or, in other words, when the variables are named right.
|
||||||
|
|
||||||
|
Please spend some time thinking about the right name for a variable before declaring it. That will repay you a lot.
|
||||||
|
|
||||||
|
Few good-to-follow rules are:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Use human-readable names like `userName` or `shoppingCart` instead of abbreviations or short names `a`, `b`, `c`.</li>
|
||||||
|
<li>Make the name maximally descriptive and concise. Examples of bad names are `data` and `value`. Any variable stores a "data" or a "value" after all. So such name says nothing. It is only ok to use them if it's exceptionally obvious which data or value is meant.</li>
|
||||||
|
<li>Agree on terms within the team and in your own mind. If a site visitor is called a "user" then we should name variables like `currentUser` or `newUser`, but not `currentVisitor` or a `newManInTown`.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
Sounds simple? In fact it is. Creating best descriptive-and-concise names isn't. Go hard for it.
|
||||||
|
|
||||||
|
[smart header="Reuse or create?"]
|
||||||
|
There are some lazy programmers who instead of declaring a new variable, tend to reuse the existing one.
|
||||||
|
|
||||||
|
As the 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.
|
||||||
|
|
||||||
|
Such a programmer saves a little bit on variable declaration, but looses ten times more on debugging the code.
|
||||||
|
|
||||||
|
An extra variable is good, not evil.
|
||||||
|
[/smart]
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## 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. That can be done using `var` or `let` or `const`.
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>`let` -- is a modern variable declaration. The code must be in strict mode to use `let` in Chrome (V8).</li>
|
<li>`let` -- is a modern variable declaration. The code must be in strict mode to use `let` in Chrome (V8).</li>
|
||||||
<li>`var` -- is an old-school compatibility variable declaration. We'll study the subtle differences from `let` later, after we get familiar with the basics.</li>
|
<li>`var` -- is an old-school variable declaration. We'll study the subtle differences from `let` later, after we get familiar with the basics.</li>
|
||||||
<li>`const` -- is like `let`, but the variable can't be changed.</li>
|
<li>`const` -- is like `let`, but the variable can't be changed.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Please name the variables sensibly.
|
Variables should be named in a way that allows to easily understand what's inside.
|
||||||
|
|
||||||
Variable naming is one of the most important and complex skills in programming. Just looking at variable names can obviously show which code is written by a beginner and which by an experienced guru.
|
|
||||||
|
|
||||||
In the real project, most of time is spent not on writing of the completely new code, but rather on modifying and improving the existing one.
|
|
||||||
|
|
||||||
It might be not so obvious to the one who didn't write big things. Or to a freelances who writes a "read-only code" (write 5 lines, give to the customer, forget). But for the serious and especially team projects, that's always true.
|
|
||||||
|
|
||||||
It is much easier to find the information if it's well-labelled. Or, in other words, when the variables is named right.
|
|
||||||
|
|
||||||
Please spend some time thinking about the right name for a variable before declaring it. That will repay you a lot.
|
|
||||||
|
|
||||||
[warn header="The name must always correspond to the data"]
|
|
||||||
There exist lazy programmers who instead of declaring a new variable, tend to reuse the existing one, write other data into it.
|
|
||||||
|
|
||||||
As the 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.
|
|
||||||
|
|
||||||
Such a programmer saves a little bit on variable declaration, but looses ten times more on debugging the code.
|
|
||||||
|
|
||||||
An extra variable is good, not evil.
|
|
||||||
[/warn]
|
|
|
@ -13,26 +13,29 @@ let n = 123;
|
||||||
n = 12.345;
|
n = 12.345;
|
||||||
```
|
```
|
||||||
|
|
||||||
There is a single *number* type for both integer and floating point numbers.
|
A *number* type serves both for integer and floating point numbers.
|
||||||
|
|
||||||
Besides numbers it may store so called "numeric values": `Infinity`, `-Infinity` and `NaN`.
|
Besides numbers there are special "numeric values" which belong to that type: `Infinity`, `-Infinity` and `NaN`.
|
||||||
|
|
||||||
`Infinity` is meant to represent the mathematical [Infinity](https://en.wikipedia.org/wiki/Infinity).
|
<ul>
|
||||||
|
<li>`Infinity` is meant to represent the mathematical [Infinity](https://en.wikipedia.org/wiki/Infinity).
|
||||||
|
|
||||||
We can get it dividing by zero:
|
We can get it as a result of division by zero:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
alert( 1 / 0 ); // Infinity
|
alert( 1 / 0 ); // Infinity
|
||||||
alert( -1 / 0 ); // -Infinity
|
alert( -1 / 0 ); // -Infinity
|
||||||
```
|
```
|
||||||
|
</li>
|
||||||
`NaN` represents a computational error. It is the result of an incorrect or undefined mathematical operation, for instance:
|
<li>`NaN` represents a computational error. It is a result of an incorrect or an undefined mathematical operation, for instance:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
alert( "not a number" * 2 ); // NaN
|
alert( "not a number" * 2 ); // NaN
|
||||||
```
|
```
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
These values formally belong to the "number" type. Of course they are not numbers in a common sense of this word.
|
These values formally belong to the "number" type. Of course they are not numbers in a common sense of this word.
|
||||||
|
|
||||||
|
@ -51,26 +54,20 @@ In JavaScript, there are 3 types of quotes.
|
||||||
<ol>
|
<ol>
|
||||||
<li>Double quotes: `"Hello"`.</li>
|
<li>Double quotes: `"Hello"`.</li>
|
||||||
<li>Single quotes: `'Hello'`.</li>
|
<li>Single quotes: `'Hello'`.</li>
|
||||||
<li>Backtricks are "extended functionality" quotes. They allow to embed other variables or even expressions into the string wrapping them by `${…}`.</li>
|
<li>Backtricks: <code>`Hello`</code>.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
Double and single quotes are essentially the same. The only difference between them can be seen when the string includes the quotation character `"` or `'`.
|
Double and single quotes are essentially the same.
|
||||||
|
|
||||||
A double quote symbol may appear inside single-quoted lines and vise versa:
|
Backtricks are "extended functionality" quotes. They allow to embed variables and expressions into a string by wrapping them in `${…}`, for example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
let hello = "I'm JavaScript"; // single-quote inside "…"
|
//+ run
|
||||||
let name = 'My "official" name is "EcmaScript"'; // vise versa
|
let name = "John";
|
||||||
|
|
||||||
|
alert( `Hello, ${name}!` ); // Hello, John!
|
||||||
```
|
```
|
||||||
|
|
||||||
If we want to include a single quote inside a same-quoted string, we can do it too. But we need to prepend it with a slash:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// prepend ' inside the string with a slash \'
|
|
||||||
let hello = 'I\'m JavaScript';
|
|
||||||
```
|
|
||||||
|
|
||||||
Similarly with double quotes.
|
|
||||||
|
|
||||||
[smart header="There is no *character* type."]
|
[smart header="There is no *character* type."]
|
||||||
In some languages, there is a special "character" type for a single character. For example, in the C language it is `char`.
|
In some languages, there is a special "character" type for a single character. For example, in the C language it is `char`.
|
||||||
|
@ -82,7 +79,7 @@ We'll cover strings more thoroughly in the chapter [](/string).
|
||||||
|
|
||||||
## A boolean (logical)
|
## A boolean (logical)
|
||||||
|
|
||||||
The boolean type has only two values in it: `true` and `false`.
|
The boolean type has only two values: `true` and `false`.
|
||||||
|
|
||||||
This type is commonly used to store the yes/no values.
|
This type is commonly used to store the yes/no values.
|
||||||
|
|
||||||
|
@ -94,7 +91,7 @@ let checked = true; // the form field is checked
|
||||||
checked = false; // the form field is not checked
|
checked = false; // the form field is not checked
|
||||||
```
|
```
|
||||||
|
|
||||||
Boolean values usually originate from the comparisons:
|
Boolean values also come as the result of comparisons:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -118,7 +115,7 @@ In JavaScript `null` is not a "reference to a non-existing object" or a "null po
|
||||||
|
|
||||||
It's just a special value which has the sense of "nothing" or "value unknown".
|
It's just a special value which has the sense of "nothing" or "value unknown".
|
||||||
|
|
||||||
The code above basically says that the `age` is unknown.
|
The code above states that the `age` is unknown.
|
||||||
|
|
||||||
## The "undefined" value
|
## The "undefined" value
|
||||||
|
|
||||||
|
@ -146,20 +143,19 @@ alert( x ); // "undefined"
|
||||||
|
|
||||||
...But it's not recommended to do that, because such assignment contradicts to the sense of `undefined`.
|
...But it's not recommended to do that, because such assignment contradicts to the sense of `undefined`.
|
||||||
|
|
||||||
To write an "empty" or an "unknown" value into the variable, use `null`.
|
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.
|
||||||
|
|
||||||
## The typeof operator [#type-typeof]
|
## The typeof operator [#type-typeof]
|
||||||
|
|
||||||
The `typeof` operator returns the type of the argument.
|
The `typeof` operator returns the type of the argument.
|
||||||
|
|
||||||
It has two syntaxes: with the brackets or without them.
|
It allows two forms of syntax:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>As an operator: `typeof x`.</li>
|
<li>As an operator: `typeof x`.</li>
|
||||||
<li>Function style: `typeof(x)`.</li>
|
<li>Function style: `typeof(x)`.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
They work the same.
|
In other words, it works both with the brackets or without them. They result is the same.
|
||||||
|
|
||||||
The result of `typeof x` is a string, which has the type name:
|
The result of `typeof x` is a string, which has the type name:
|
||||||
|
|
||||||
|
@ -189,7 +185,7 @@ Please note the last two lines, because `typeof` behaves specially there.
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>The result of `typeof null` equals to `"object"`. That is an officially recognized error in the language that is kept for compatibility. In fact, `null` is not an object, but a special value from a data type of its own.</li>
|
<li>The result of `typeof null` equals to `"object"`. That is an officially recognized error in the language that is kept for compatibility. In fact, `null` is not an object, but a special value from a data type of its own.</li>
|
||||||
<li>Functions are yet to be covered. As of now let's just note that functions is a kind of objects. But `typeof` treats them separately returning `"function"`. That's very convenient in practie.</li>
|
<li>Functions are yet to be covered. As of now let's just note that functions do not make a separate type, instead they are a special kind of objects in JavaScript. But `typeof` treats them separately returning `"function"`. That's actually just fine and very convenient in practice.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
## Type conversions
|
## Type conversions
|
||||||
|
@ -197,16 +193,16 @@ Please note the last two lines, because `typeof` behaves specially there.
|
||||||
A variable in JavaScript can contain any data. The same variable can get a string and, a little bit later, be used to store a number:
|
A variable in JavaScript can contain any data. The same variable can get a string and, a little bit later, be used to store a number:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
// perfectly fine
|
// no error
|
||||||
let message = "hello";
|
let message = "hello";
|
||||||
message = 123456;
|
message = 123456;
|
||||||
```
|
```
|
||||||
|
|
||||||
But sometimes we need to convert a value of one type to another. That is mostly useful because each type has it's own features, so we are really going to benefit from storing a number as a number, not a string with it.
|
But sometimes we need to convert a value of one type to another. That is mostly useful because each type has it's own features. So we are really going to benefit from storing a number as a number, not a string with it.
|
||||||
|
|
||||||
There are many type conversions in JavaScript, fully listed in [the specification](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-type-conversion).
|
There are many type conversions in JavaScript, fully listed in [the specification](http://www.ecma-international.org/ecma-262/6.0/index.html#sec-type-conversion).
|
||||||
|
|
||||||
Three conversions that are required most often are:
|
Three conversions that happen the most often are:
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li>String conversion.</li>
|
<li>String conversion.</li>
|
||||||
|
@ -216,7 +212,7 @@ Three conversions that are required most often are:
|
||||||
|
|
||||||
### String conversion
|
### String conversion
|
||||||
|
|
||||||
The string conversion happens when we need a string from a value.
|
The string conversion happens when we need a string form of a value.
|
||||||
|
|
||||||
For example, `alert` does it:
|
For example, `alert` does it:
|
||||||
|
|
||||||
|
@ -250,7 +246,7 @@ For example, a mathematical operation like division '/' can be applied to non-nu
|
||||||
alert( "6" / "2" ); // 3, strings become numbers
|
alert( "6" / "2" ); // 3, strings become numbers
|
||||||
```
|
```
|
||||||
|
|
||||||
Although if we want to ensure that the value is a number, we can use a `Number(value)` function to do it explicitly:
|
If we want to ensure that a value is a number, we can use a `Number(value)` function to do it explicitly:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
|
@ -259,18 +255,16 @@ let n = Number("6");
|
||||||
alert(typeof n); // number
|
alert(typeof n); // number
|
||||||
```
|
```
|
||||||
|
|
||||||
We can use that to ensure that a user-supplied value is a number.
|
It's handy when there is a value comes from a text form field, or another source which is string-bsed.
|
||||||
|
|
||||||
If the string is not a number, the result is `NaN`.
|
If the string is not a number, the result of such conversion is `NaN`, for instance:
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
let age = Number("a user-supplied string");
|
let age = Number("a user-supplied string");
|
||||||
|
|
||||||
alert(age); // NaN, conversion failed
|
alert(age); // NaN, conversion failed
|
||||||
alert(age); // number, because NaN belongs to the "number" type
|
alert(typeof age); // number, because NaN belongs to the "number" type
|
||||||
```
|
```
|
||||||
|
|
||||||
The rules of transformation:
|
The rules of transformation:
|
||||||
|
@ -282,8 +276,8 @@ The rules of transformation:
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>`undefined`</td><td>`NaN`</td></tr>
|
<tr><td>`undefined`</td><td>`NaN`</td></tr>
|
||||||
<tr><td>`null`</td><td>`0`</td></tr>
|
<tr><td>`null`</td><td>`0`</td></tr>
|
||||||
<tr><td>`true / false`</td><td>`1 / 0`</td></tr>
|
<tr style="white-space:nowrap"><td>`true / false`</td><td>`1 / 0`</td></tr>
|
||||||
<tr><td>A string</td><td>Whitespaces from the start and the end are cut.<br>Afterwards, if we have an empty string, then `0`, otherwise -- "read" aиначе из непустой строки "считывается" число, при ошибке результат `NaN`.</td></tr>
|
<tr><td>A string</td><td>Whitespaces from the start and the end are cut off.<br>Then, if the remaining string is empty, the result is `0`, otherwise -- the value is "read" from the string. An error gives `NaN`.</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -292,20 +286,22 @@ Other examples:
|
||||||
```js
|
```js
|
||||||
//+ run
|
//+ run
|
||||||
alert( Number(" 123 ") ); // 123
|
alert( Number(" 123 ") ); // 123
|
||||||
alert( Number("123z") ); // NaN (not a number because of "z")
|
alert( Number("123z") ); // NaN (error reading a number at "z")
|
||||||
alert( Number(true) ); // 1
|
alert( Number(true) ); // 1
|
||||||
alert( Number(false) ); // 0
|
alert( Number(false) ); // 0
|
||||||
```
|
```
|
||||||
|
|
||||||
Please note that `null` and `undefined` are similar in many aspects, but here they are not. A `null` becomes a zero, but `undefined` becomes `NaN`.
|
Please note that `null` and `undefined` are similar in many aspects, but here they are not. A `null` becomes a zero, but `undefined` becomes `NaN`.
|
||||||
|
|
||||||
## Boolean conversion
|
### Boolean conversion
|
||||||
|
|
||||||
Boolean conversion is happens automatically in some operations, but also can be performed manually with the call of `Boolean(value)`.
|
Boolean conversion is happens automatically in logical operations (to be covered), but also can be performed manually with the call of `Boolean(value)`.
|
||||||
|
|
||||||
All values that are intuitively "empty" become `false`. These are: `0`, an empty string, `null`, `undefined` and `NaN`.
|
The conversion rule is simple here:
|
||||||
|
<ul>
|
||||||
Other values become `true`.
|
<li>All values that are intuitively "empty" become `false`. These are: `0`, an empty string, `null`, `undefined` and `NaN`.</li>
|
||||||
|
<li>Other values become `true`.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
[warn header="Please note: a string `\"0\"` is `true`"]
|
[warn header="Please note: a string `\"0\"` is `true`"]
|
||||||
Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript a non-empty string is always `false`, no matter what is in it.
|
Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript a non-empty string is always `false`, no matter what is in it.
|
||||||
|
@ -346,7 +342,7 @@ alert( user.name ); // John
|
||||||
alert( user.age ); // 30
|
alert( user.age ); // 30
|
||||||
```
|
```
|
||||||
|
|
||||||
We'll cover working with objects in the chapter [](/object).
|
Objects are very capable and tunable in JavaScript. This topic is huge, we'll start working with objects in the chapter [](/object) and continue studying them in other parts of the tutorial.
|
||||||
|
|
||||||
### Symbol
|
### Symbol
|
||||||
|
|
||||||
|
@ -356,18 +352,18 @@ The `symbol` type is used to create unique identifiers.
|
||||||
let id = Symbol("id");
|
let id = Symbol("id");
|
||||||
```
|
```
|
||||||
|
|
||||||
...And then we could use `id` as a special kind of identifier for object properties. We'll see more about object properties in the following chapters.
|
...And then we could use `id` as a special kind of identifier for object properties.
|
||||||
|
|
||||||
As of now, let's just say that JavaScript symbols is a separate primitive type. And they are different from symbols in Ruby language (just in case you are familiar with it, please don't get trapped by the same word).
|
We'll see more about object properties in the following chapters. As of now, let's just say that JavaScript symbols is a separate primitive type. And they are different from symbols in Ruby language (if you are familiar with it, please don't get trapped by the same word).
|
||||||
|
|
||||||
We list symbols here for completeness, their in-depth study will follow after objects.
|
We list symbols here for the sake of completeness, their in-depth study will follow after objects.
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>There are 7 basic types in JavaScript. Six "primitive" types: `number`, `string`, `boolean`, `null`, `undefined`, `symbol` and `object`.</li>
|
<li>There are 7 basic types in JavaScript. Six "primitive" types: `number`, `string`, `boolean`, `null`, `undefined`, `symbol` and `object`.</li>
|
||||||
<li>Use `typeof x` to see which type is stored in `x`, but note that `typeof null` is mistakingly returned as undefined.</li>
|
<li>The `typeof` operator allows to see which type is stored in the variable, but note that it mistakingly returns `"object"` for `null`.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Type conversions usually happen automatically, but there are also functions for the manual conversion:
|
Type conversions usually happen automatically, but there are also functions for the manual conversion:
|
||||||
|
@ -377,5 +373,4 @@ Type conversions usually happen automatically, but there are also functions for
|
||||||
<li>Boolean</li>
|
<li>Boolean</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
Now let's move on to operators and compute something using these types.
|
Further we'll cover each type in a separate chapter, devoted to this type only. But first let's move on to study operators and other language constructs. It will be more productive.
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
# Разъяснения
|
|
||||||
|
The answer is:
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>`a = 2`</li>
|
||||||
|
<li>`b = 2`</li>
|
||||||
|
<li>`c = 2`</li>
|
||||||
|
<li>`d = 1`</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
//+ run no-beautify
|
||||||
let a = 1, b = 1, c, d;
|
let a = 1, b = 1;
|
||||||
|
|
||||||
// префиксная форма сначала увеличивает a до 2, а потом возвращает
|
alert( ++a ); // 2, prefix form returns the new value
|
||||||
c = ++a; alert(c); // 2
|
alert( b++ ); // 1, postfix form returns the old value
|
||||||
|
|
||||||
// постфиксная форма увеличивает, но возвращает старое значение
|
alert( a ); // 2, incremented once
|
||||||
d = b++; alert(d); // 1
|
alert( b ); // 2, incremented once
|
||||||
|
|
||||||
// сначала увеличили a до 3, потом использовали в арифметике
|
|
||||||
c = (2+ ++a); alert(c); // 5
|
|
||||||
|
|
||||||
// увеличили b до 3, но в этом выражении оставили старое значение
|
|
||||||
d = (2+ b++); alert(d); // 4
|
|
||||||
|
|
||||||
// каждую переменную увеличили по 2 раза
|
|
||||||
alert(a); // 3
|
|
||||||
alert(b); // 3
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
# Инкремент, порядок срабатывания
|
# The postfix and prefix forms
|
||||||
|
|
||||||
[importance 5]
|
[importance 5]
|
||||||
|
|
||||||
Посмотрите, понятно ли вам, почему код ниже работает именно так?
|
What are the final values of all variables in the code below?
|
||||||
|
|
||||||
```js
|
```js
|
||||||
//+ run no-beautify
|
let a = 1, b = 1;
|
||||||
let a = 1, b = 1, c, d;
|
|
||||||
|
|
||||||
c = ++a; alert(c); // 2
|
let c = ++a; // ?
|
||||||
d = b++; alert(d); // 1
|
let d = b++; // ?
|
||||||
|
|
||||||
c = (2+ ++a); alert(c); // 5
|
|
||||||
d = (2+ b++); alert(d); // 4
|
|
||||||
|
|
||||||
alert(a); // 3
|
|
||||||
alert(b); // 3
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|