📜 ⬆️ ⬇️

JavaScript scope and closures

This publication is a translation of the material “JavaScript Scope and Closures” by Zell Liew, posted here .

Scopes and closures are important in JavaScript, but they confused me when I first started learning them. The following are explanations of these terms that will help you understand them.


Let's start with scopes


Area of ​​visibility


JavaScript scope determines which variables are available to you. There are two types of scopes: global and local .



Global scope


If a variable is declared outside of all functions or curly braces ( {} ), then it is considered that it is defined in the global scope .


Note: this is only true for javascript in web browsers. In Node.js, global variables are declared differently, but we will not touch Node.js in this article.


 const globalVariable = 'some value'; 

As soon as a global variable is declared, this variable can be used everywhere in the code, even in functions.


 const hello = 'Hello CSS-Tricks Reader!'; function sayHello () { console.log(hello); } console.log(hello); // 'Hello CSS-Tricks Reader!' sayHello(); // 'Hello CSS-Tricks Reader!' 

Although it is possible to declare variables in the global scope, it is not recommended to do this. This is due to the fact that there is a chance that names will intersect when two or more variables are given the same name. If variables are declared via const or let , then each time the intersection of names occurs, an error message will be displayed. This behavior is undesirable.


 //   ! let thing = 'something'; let thing = 'something else'; // , thing    

If you declare variables through var , then the second variable after the declaration will overwrite the first. This behavior is also undesirable, since code is complicated in debugging.


 //   ! var thing = 'something'; var thing = 'something else'; //  -        console.log(thing); // 'something else' 

So, you should always declare local variables, not global ones.


Local scope


Variables that are used only in a certain part of the code are considered to be placed in the local scope. Such variables are called local .


In JavaScript, there are two types of local scopes:



First consider the function scope


Function scope


A variable declared inside a function is available only inside a function. The code outside the function does not have access to it.


In the example below, the hello variable is inside the scope of the sayHello function:


 function sayHello () { const hello = 'Hello CSS-Tricks Reader!'; console.log(hello); } sayHello(); // 'Hello CSS-Tricks Reader!' console.log(hello); // , hello   

Block scope


A variable declared inside curly braces {} via const or let is available only inside curly brackets.


In the example below, you can see that the hello variable is inside the scope of the curly braces:


 { const hello = 'Hello CSS-Tricks Reader!'; console.log(hello); // 'Hello CSS-Tricks Reader!' } console.log(hello); // , hello   

Block scope is a special case of function scope , since Functions are declared with curly brackets (except when using pointer functions with an implicit return value).


Raising visibility function


Functions declared as “ function declarations ” always rise to the current scope. So, the two examples below are equivalent:


 //  ,    sayHello(); function sayHello () { console.log('Hello CSS-Tricks Reader!'); } //  ,    function sayHello () { console.log('Hello CSS-Tricks Reader!'); } sayHello(); 

If the function is declared as a “ function expression ” (functional expression) (note: function of the form var f = function () {...} ), then such a function does not rise in the current scope.


 sayHello(); // , sayHello   const sayHello = function () { console.log(aFunction); } 

Because of these two possible options, raising the function can potentially be confusing, so it is not recommended to be used in practice. Always declare functions first before using them.


Functions do not have access to the scope of other functions.


Functions do not have access to the scope of other functions when they are declared separately, even if one function is used in another.


In the example below, the second function does not have access to the variable firstFunctionVariable .


 function first () { const firstFunctionVariable = `I'm part of first`; } function second () { first(); console.log(firstFunctionVariable); // , firstFunctionVariable  . } 

Nested scopes


When a function is declared in another function, the internal function has access to the variables of the external function. This behavior is called delineation of lexical scopes .


At the same time, the external function does not have access to the internal function variables.


 function outerFunction () { const outer = `I'm the outer function!`; function innerFunction() { const inner = `I'm the inner function!`; console.log(outer); // I'm the outer function! } console.log(inner); // , inner   } 

To visualize how visibility areas work, you can imagine a one-way mirror. You can see those who are on the other side, but those who are on the other side (the mirror side) cannot see you.



If some scopes are embedded in others, then this can be represented as a set of glass surfaces with the principle of operation described above.



If you understand everything about scope, then we can say that you are ready to deal with the fact that such a circuit.


Closures


Whenever you call a function inside another function, you create a closure. It is said that the internal function is a closure. The result of the closure is usually that the variables of the external function become available later.


 function outerFunction () { const outer = `I see the outer variable!`; function innerFunction() { console.log(outer); } return innerFunction; } outerFunction()(); // I see the outer variable! 

Since the inner function is the return value of the outer function, the code can be slightly reduced by combining the return value with the function declaration.


 function outerFunction () { const outer = `I see the outer variable!`; return function innerFunction() { console.log(outer); } } outerFunction()(); // I see the outer variable! 

Thanks to the closures, access to the external function appears, so they are usually used for two purposes:


  1. control side effects;
  2. create private variables.

Side effects control with closures


Side effects occur when some additional actions are performed besides returning the value after the function is called. Many things can be a side effect, such as an Ajax request, a timer, or even console.log:


 function (x) { console.log('A console.log is a side effect!'); } 

When closures are used to control side effects, they tend to pay attention to side effects that may confuse the code (for example, Ajax requests or timers).


For clarification, consider an example.


Let's say you want to make your friend's birthday cake. Cooking a cake will take a second, since the written function displays “baked cake” after a second.


Note: for brevity and simplicity, the arrow functions from ES6 are used below.


 function makeCake() { setTimeout(_ => console.log(`Made a cake`), 1000); } 

As you can see, this feature has a side effect in the form of a timer.


Next, let's say your friend needs to choose the taste of the cake. To do this, add "add taste" to the function makeCake .


 function makeCake(flavor) { setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000); } 

After calling the function, the cake will be baked in exactly one second.


 makeCake('banana'); // Made a banana cake! 

The problem is that, let's say, it is not necessary that the cake be baked immediately after the refinement of the taste, but it is necessary that the cake be baked later when it is needed.


To solve this problem, you can write the function prepareCake , which will store the taste of the cake. Then pass the closure to makeCakeLater via prepareCake .


From this point on, the returned function can be called at any time when it is required, and the cake will be cooked in a second.


 function prepareCake (flavor) { return function () { setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000); } } const makeCakeLater = prepareCake('banana'); //    ... makeCakeLater(); // Made a banana cake! 

So closures are used to reduce side effects — a function is called that activates an internal closure according to your desire.


Private variables with closures


As you now know, variables created inside a function cannot be accessed outside. Due to the fact that they are not available, they are also called private variables .


However, access to such a private variable is sometimes required, and closures are used for this.


 function secret (secretCode) { return { saySecretCode () { console.log(secretCode); } } } const theSecret = secret('CSS Tricks is amazing'); theSecret.saySecretCode(); // 'CSS Tricks is amazing' 

In the example above, saySecretCode is the only function (closure) that outputs the secretCode outside the original secret function. For this reason, this feature is called privileged .


Debugging Scopes with DevTools


Developer Tools (DevTools) Chrome and Firefox make it easy to debug variables in the current scope. There are two ways to use this functionality.


The first way is to add the debugger keyword to the code in order to stop the execution of JavaScript code in browsers for further debugging.


Below is an example with prepareCake :


 function prepareCake (flavor) { //  debugger debugger return function () { setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000); } } const makeCakeLater = prepareCake('banana'); 

If you open DevTools and go to the Sources tab in Chrome (or the Debugger tab in Firefox), you can see the available variables.



You can also move the debugger inside the circuit. Notice how the scope variables change this time:


 function prepareCake (flavor) { return function () { //  debugger debugger setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000); } } const makeCakeLater = prepareCake('banana'); 


The second way is to add breakpoint directly to the code in the Sources (or Debugger) tab by clicking on the line number.



Findings:



')

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


All Articles