📜 ⬆️ ⬇️

ES6 and beyond. Chapter 2: Syntax. Part 1



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 ); // 3 })(); console.log( a ); // 2 

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 ); // 3 } console.log( a ); // 2 

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:
 //      ES6 let (a = 2, b, c) { // .. } 

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 ); // 6 for (let i = a; i <= b; i++) { let j = i + 10 console.log( j ); } // 12 13 14 15 16 let c = a + b; console.log( c ); // 8 } 

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 ); // undefined console.log( b ); // ReferenceError! var a; let b; } 

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") { // ReferenceError! // .. } // .. let b; } 

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](); // 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](); // 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 ); // 2 a = 3; // TypeError! } 

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 ); // [1,2,3,4] a = 42; // TypeError! } 

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(); // ! function foo() { // .. } } foo(); // ReferenceError 

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 .

Source: https://habr.com/ru/post/257511/


All Articles