If you had experience in writing JS applications, then surely you are familiar with its syntax. It is not very common and has a number of quirks, but on the whole it is considered, understood and has many similarities with other languages.
ES6 adds several new syntactic forms with which we will meet. In this chapter we will go over many of them and find out what's new in our arsenal.
')
Please note: At the time of writing this book, most of the new features of ES6 have already been implemented by both popular browsers (Firefox, Chrome, etc.), and many interesting environments. But unfortunately not all browsers or environments can work with ES6. As we said in the last chapter, transpiling is our everything. With this approach, you can run any of the examples in this book. To do this, we have a number of tools at our disposal -
ES6Fiddle (
http://www.es6fiddle.net /) is an excellent and easy to use platform, in order to try ES6 and REPL for
Babel (
http://babeljs.io/repl/ ).
Block scope
Surely you know that the fundamental unit of visibility in JavaScript is function. If you need to implement a block with its own scope, then the most suitable way is to use IIFE (an expression of an instantly called function), for example:
var a = 2; (function IIFE(){ var a = 3; console.log( a );
Let statement
However, now we can implement scopes in any kind of block, and this is called block scoping. All we need to create a new field of view is a pair of well-known curly braces
{ .. }
. Instead of using the
var
operator that we used to declare the variables of the internal (or global) scope, we can use
let
to declare a block:
var a = 2; { let a = 3; console.log( a );
Of course, this is unusual for JS, but for developers who have worked with other languages, this pattern has long been known.
This is the best way to create block visibility using
{ ... }
. I recommend that you put
let
declarations at the very beginning of a block, and if you have more than one declaration, use only one
let
statement.
Stylistically, I would even recommend that you make an ad on the same line as the opening one
{
, so that you
clearly emphasize for which block these variables are intended:
{ let a = 2, b, c;
Now it looks rather strange and does not coincide with the majority of recommendations from the literature on ES6, but I have reasons for my madness. (comment perev .: find out just below).
There is another proposed form of the
let
statement, and it is called a let-block:
This is a great example of what I call the "
explicit block scope ", while
let
similar to
var
is
not explicit , as it applies to the entire scope block.
Unfortunately, the form
let (..) { .. }
was not accepted in ES6. Perhaps in the next updates of ES6, we will see this form, but for now the first example is the best we have.
To emphasize the
implicit nature of the
let
statement, let's consider the following example:
let a = 2; if (a > 1) { let b = a * 3; console.log( b );
And now a small quiz, without re-examining the code, tell me: which variables exist in the
if
block, and which variables only in the
for
loop?
Answer: the
if
block contains the variables
a
and
b
block scope, and
for
variables
i
and
j
block scope.
Doesn't it surprise you that the variable
i
not added to the scope of the
if
block?
In this example, danger also lurks in such a low declaration of the variable
c
;
The traditional
var
operator kind of "
attaches " a variable to the scope and initializes it when the function is entered before it is executed, regardless of where it was declared. This phenomenon is known as
elevation . The
let
statement also attaches a variable to the scope before it is executed, but does not initialize it when entering the function, which, if attempted to access such a variable, before it is declared / initialized, will result in an error.
Illustrative example:
{ console.log( a );
Note: ReferenceError, which will be caused by
an early access to the variable , before it was declared or initialized, is technically called a TDZ (temporal dead zone) error.
I want to draw your attention to the fact that “not initialized” does not mean that it needs to explicitly assign some value.
let b;
- quite enough. It is assumed that a variable which was not assigned a value during the declaration will be initialized to the value
undefined
, the default.
let b;
same as
let b = undefined;
. But I repeat - before,
let b;
will not be executed, use this variable, you can not.
Another unexpected twist is the
typeof
behavior with tdz variables:
{ if (typeof a === "undefined") { console.log( "cool" ); } if (typeof b === "undefined") {
Since the variable
not declared, the only safe way to check it for existence is using the
typeof
operator. But this was not the case with the variable
b
, which throws an exception, since it was declared much lower using the
let
operator.
I do not just recommend, but insist that all ads using the new operator
let
be at the very top of the block. This will save you from the headache of refactoring, eliminating unexplained errors, make the code more understandable and predictable.
Note: More about the
let
statement and block scope, we'll talk in chapter 3.
let + for
Another significant feature of the
let
statement is how it behaves with the
for
loop.
Consider an example:
var funcs = []; for (let i = 0; i < 5; i++) { funcs.push( function(){ console.log( i ); } ); } funcs[3]();
The
let i
expression in the
for
header declares a new variable not for the entire cycle, but for each individual iteration. The same is true for the circuit inside the loop. In fact, the code behaves exactly as expected.
If you try to use
var i
instead of
let i
, then you would get 5 instead of 3 as a result of doing this, since in this case the closure will be common to all iterations.
By adding a little water, you can get the following example, which does the same thing:
var funcs = []; for (var i = 0; i < 5; i++) { let j = i; funcs.push( function(){ console.log( j ); } ); } funcs[3]();
In this example, we explicitly create a new
j
variable for each iteration. I prefer the first option, which, of course, less obvious, but not so much as to abandon it.
Const operator
We have another operator to consider, also related to block scope:
const
.
A constant is a variable that, after initial initialization, is read only.
For example:
{ const a = 2; console.log( a );
You cannot change the value of a variable that has already been assigned a value during the declaration. When declaring a constant,
you must explicitly specify the value of the variable . By default, the constant does not get the value
undefined
, therefore if you need such a value, then you will have to manually assign it -
const a = undefined;
.
Constants have restrictions on assignments, but there are no restrictions associated with the value of a variable. For example, if the constant is a complex value, then you can still modify it:
{ const a = [1,2,3]; a.push( 4 ); console.log( a );
Thus, the constant maintains a kind of
constant reference , but the array it points to is still dynamic.
Note: Since we cannot delete references, assigning an object or an array to a constant means that the value cannot be deleted by the garbage collector until it leaves the lexical scope. Of course, this may be a desirable behavior, but be careful.
Previously, we used capital letters in literals that were intended for variables, thereby hinting that changing the values ​​of such variables is not desirable. Now, we can use constants that warn us against unwanted changes.
According to rumors, since the use of a constant, makes it clear to the JS engine that the value will never be changed, will make this operator a more optimized solution than
let
or
var
.
Despite our fantasies about this, it is much more important that you understand when you really need to use a constant. You should not use constants for simple variables, otherwise this may cause misunderstanding.
Block scope of functions
Starting with ES6, it is assumed that the functions that were declared in the block will have its scope (block). Before ES6, the specification was silent about this, in contrast to various specific implementations. However, the specification now corresponds to reality.
Consider an example:
{ foo();
In this example, the function is declared in the block scope, therefore it is not accessible from the outside. It is also worth noting an interesting fact - in contrast to the
let
declaration, which we cannot work with before such a variable is initialized, in this case the TDZ error does not occur.
Block scope functions can be a problem for you if you are still writing code like in the example below, relying on the old behavior:
if (something) { function foo() { console.log( "1" ); } } else { function foo() { console.log( "2" ); } } foo();
In pre-ES6 environments, the result of calling the
foo()
function is
"2"
-
regardless of the value of the variable something
, since, thanks to the
elevation, the working function will be the second.
In ES6, the last line throws an exception -
ReferenceError
.