In the last article, we talked about scopes and briefly introduced block-level scopes. Here, we will cover them in detail.

As we all know, function scope is a common unit scope in JS, and it is also the most common design scheme in most JS. But other types of scoped units exist, and even better, cleaner code can be achieved by using them. This is what we now refer to as block-level scope.

Let’s also use an example that we’ve already used

for (var i=0; i<10; i++) {
 console.log( i );
}
Copy the code

We define the variable I directly in the header of the for loop, usually because we only want to use I within the context of the for loop, ignoring the fact that I is bound to an external scope (function or global). That’s where block scopes come in. Variables should be declared as close to where they are used as possible and as localized as possible. Another example:

var foo = true;
if (foo) {
 var bar = foo * 2;
 bar = something( bar );
 console.log( bar );
}
Copy the code

The bar variable is only used in the context of an if declaration, so it would be interesting to be able to declare it inside an if block. However, when a variable is declared using VAR, it is written the same everywhere, because they all end up in the outer scope. This code is a formal block scope disguised for a more readable style, and if you use this form, making sure you don’t accidentally use bar elsewhere in the scope is a matter of self-awareness. Block scope is a tool used to extend the previous principle of minimum authorization by extending code from hiding information in functions to hiding information in blocks.

Why contaminate the entire function scope with a variable I that is only used inside (or at least should be used only inside) the for loop? So block-level scopes are useful in development for variable management and garbage collection. So what can form block-level scopes, let’s see

try/catch

This is something that many of you probably use, but most of the time we write code in try blocks, so don’t think of code in try blocks as block-level scopes, but variables declared in try blocks are also declared as global variables. What we want to talk about here is the catch variable, so let’s look at an example

Try {var sex=" male "; throw "oecom" }catch(e){ console.log(e) } console.log(sex); / / male console. The log (e); //Uncaught ReferenceError: e is not definedCopy the code

As you can see, err only exists inside the catch clause, and an error is thrown when an attempt is made to reference it from elsewhere. The catch clause creating block scope may seem like a doctrinary-school thing, but many people think it’s a sick thing to write code like this. I have to force an exception to declare a block scope variable. Because the catch clause has block scope, it can be used as an alternative to block scope in pre-ES6 environments. Several tools can transform ES6 code into a form that can run in a pre-ES6 environment. You can use block scope to write code, enjoy the benefits of it, and then use tools at build time to preprocess the code so that it will work at deployment time, which is the point of catch as a block-level scope.

let

One of the great things about ES6 for JS developers is that it introduced the new let keyword, which provides an alternative to var for variable declarations. The let keyword binds variables to any scope they are in (usually {.. } inside). In other words, let implicitly binds the block scope for the variables it declares.

var foo = true;
if (foo) {
 let bar = foo * 2;
 console.log( bar );
}
console.log( bar );
Copy the code

The act of using let to append variables to an existing block scope is implicit. If you don’t pay close attention to which blocks have bound variables in scope and habitually move those blocks around or include them in other blocks as you develop and modify your code, it can lead to messy code.

However, the implicit declaration of block-level scope can easily ignore its scope location during code modification, so we can declare it explicitly when writing code, which is to add {} before and after it, so that the whole code block movement does not cause other problems.

var foo = true; if (foo) { { let bar = foo * 2; console.log( bar ); }}Copy the code

As long as the declaration is valid, you can use {.. } parentheses to create a block for the let to bind. That includes loops, of course.

for (let i=0; i<10; i++) {
 console.log( i );
}
console.log( i ); // ReferenceError
Copy the code

The last output I is going to be an error. The let in the head of the for loop not only binds I to the block of the for loop, it actually rebinds it to each iteration of the loop, ensuring that it is reassigned with the value at the end of the last iteration of the loop. The principle is shown below

{ let j; for (j=0; j<10; j++) { let i = j; // Rebind each iteration! console.log( i ); }}Copy the code

Due to let statement is attached to a new scope rather than the current function scope (also do not belong to the global scope), when the code for the function in scope var statement implicitly rely on, there will be a lot of hidden trap, if use the let to replace the var is needed in the process of code refactoring put extra effort.

var foo = true, baz = 10;
if (foo) {
 var bar = 3;
 if (baz > bar) {
 console.log( baz );
 }
 // ...
}
Copy the code

Note the following changes when using block-scoped variables

var foo = true, baz = 10; if (foo) { let bar = 3; If (baz > bar) {// <-- don't forget bar! console.log( baz ); }}Copy the code

Let, let, let, let, let, let, let, let

console.log(a);
var a = 10;
Copy the code

It’s perfectly fine to write that, it just prints undefined, because it’s like

var a;
console.log(a);
a = 10;
Copy the code

However, variables declared using let do not have variable promotion, and an error will be reported if used before the declaration.

const

In addition to lets, ES6 introduces const, which can also be used to create block-scoped variables, but whose value is fixed (constant). Any subsequent attempts to modify the value will cause an error. The effect is similar to that of let, which I won’t repeat here.

conclusion

One useful aspect of block-level scope has to do with closures and the collection mechanism that recycles memory garbage. Consider the following code

Function process(data) {var someReallyBigData = {.. }; process( someReallyBigData ); var btn = document.getElementById( "my_button" ); btn.addEventListener( "click", function click(evt) { console.log("button clicked"); }, false );Copy the code

This approach is common in code, and the click callback of the click function does not require the someReallyBigData variable. In theory this means that when process(..) After execution, data structures that take up a lot of space in memory can be garbage collected. However, because the click function forms a closure that covers the entire scope, the JavaScript engine will most likely still hold this structure (depending on the implementation).

But block scope can remove this concern and make it clear to the engine that there is no need to save someReallyBigData any longer:

Function process(data) {let someReallyBigData = {.. }; process( someReallyBigData ); } var btn = document.getElementById( "my_button" ); btn.addEventListener( "click", function click(evt) { console.log("button clicked"); }, false );Copy the code

Although the new version of JS provides block-level scope, we should use it properly in our code, and use different scopes for different scenarios to create good code that is readable and maintainable.