Var, let or const? Problems scopes variables and ES6

https://alistapart.com/article/fixing-variable-scope-issues-with-ecmascript-6
  • Transfer
Scopes in JavaScript have always been a difficult topic, especially when compared to more strictly organized languages, such as C and Java. For many years, the areas of visibility in JS have not been widely discussed, since the language simply did not have the means to significantly influence the current situation. But in ECMAScript 6 some new features have appeared that allow developers to better control the scopes of variables. These features nowadays support browsers very well, they are quite accessible to most developers. However, new keywords for declaring variables, given the fact that the old keyword varhas not gone away, mean not only new opportunities, but also the emergence of new questions. When to use keywords letandconst? How do they behave? In what situations is the keyword still relevant var? The material, the translation of which we are publishing today, is aimed at studying the problem of the scope of variables in JavaScript.



Variable scope: overview


The scope of a variable is an important programming concept that, however, may confuse some developers, especially newbies. The scope of a variable is the part of the program where this variable can be accessed.

Take a look at the following example:

var myVar = 1;
functionsetMyVar() {
  myVar = 2;
}
setMyVar();
console.log(myVar);

What will the method output console.log? The answer to this question will not surprise anyone: it will lead 2. The variable is myVardeclared outside of any function, which tells us that it is declared in the global scope. Consequently, any function declared in the same scope can refer to myVar. In fact, if we are talking about the code executed in the browser, even functions declared in other files connected to the page will have access to this variable.

Now take a look at the following code:

functionsetMyVar() {
  var myVar = 2;
}
setMyVar();
console.log(myVar);

Externally, its changes, compared with the previous example, are insignificant. Namely, we just put the variable declaration inside the function. What will lead now console.log? Actually, nothing, since this variable is not declared, and when you try to access it, you will get a message about an unhandled error ReferenceError. This is because the variable, using the keywordvar, declared inside the function. As a result, the scope of this variable is limited to the internal scope of the function. It can be accessed in the body of this function; functions embedded in this function can work with it, but it is not available from the outside. If we need a certain variable to be used by several functions that are on the same level, we need to declare this variable in the same place where these functions are declared, that is, one level above their internal scope.

Here is one interesting observation: the code of most websites and web applications does not apply to the work of any one programmer. Most software projects are the results of team development, and, moreover, they use third-party libraries and frameworks. Even if one programmer is involved in the development of a certain site, he usually uses external resources. Because of this, it is usually not recommended to declare variables in the global scope, since it is not possible to know in advance which variables will be declared by other developers whose code will be used in the project. In order to circumvent this problem, you can use some techniques, in particular - the pattern " Module " and IIFEwhen applying an object-oriented approach to JavaScript development, although the same effect can be achieved by encapsulating data and functions in ordinary objects. In general, it can be noted that variables, the scope of which goes beyond the limits that they need, usually represent a problem with which to do something.

Var keyword problem


So, we figured out the concept of "scope". We now turn to more complex things. Take a look at the following code:

functionvarTest() {
  for (var i = 0; i < 3; i++) {
    console.log(i);
  }
  console.log(i);
}
varTest();

What gets to the console after it is executed? It is clear that increasing the value of the counter will be displayed inside the loop i: 0, 1and 2. After the cycle ends, the program continues to run. Now we are trying to access the same counter variable that was declared in a loop for, outside of this loop. What will come of it?

After accessing to ithe outside of the loop, the console will get 3, since the keyword varoperates at the function level. If you declare a variable with use var, then you can access it in a function after exiting the construction where it was declared.

This can become a problem when the functions become more complex. Consider the following example:

functiondoSomething() {
  var myVar = 1;
  if (true) {
    var myVar = 2;
    console.log(myVar);
  }
  console.log(myVar);
}
doSomething();

What gets to the console now? 2and 2. We declare a variable, initialize it with the number 1, and then try to override the same variable inside the expression if. Since these two declarations exist in the same scope, we cannot declare a new variable with the same name, even though we obviously want to do just that. As a result, the first variable is overwritten inside the expression if.

This is the biggest disadvantage of the keyword.var. The scope of variables declared using it is too large. This can lead to inadvertent overwriting of data and other errors. Large scopes often lead to sloppy programs. In general, a variable should have a scope that is limited by its needs, but not exceeding them. It would be good to be able to declare variables whose scope is not as large as when used var, which would allow, if necessary, to use more stable and better error-protected software constructs. In fact, ECMAScript 6 provides us with such opportunities.

New ways to declare variables


The ECMAScript 6 standard (a new set of JavaScript capabilities, also known as ES6 and ES2015) gives us two new ways of declaring variables that are limited, compared with varthe scope and having some other features. These are keywords letand const. Both that and another gives us a so-called block scope. This means that the scope of their use can be limited to a block of code, such as a loop foror an expression if. This gives the developer more flexibility in the choice of scopes of variables. Consider new keywords.

▍Using the let keyword


The keyword is letvery similar var, the main difference is the limited scope of variables declared with its help. Rewrite one of the above examples, replacing varwith let:

functiondoSomething() {
  let myVar = 1;
  if (true) {
    let myVar = 2;
    console.log(myVar);
  }
  console.log(myVar);
}
doSomething();

In this case, the numbers 2and will get to the console 1. This happens because the expression ifdefines a new scope for a variable declared using a keyword let. This leads to the fact that the second declared variable is a completely independent entity, not connected with the first one. You can work with them independently. However, this does not mean that nested blocks of code, like our expression if, are completely cut off from the variables declared by the keyword letin the scope in which they themselves are located. Take a look at the following code:

functiondoSomething() {
  let myVar = 1;
  if (true) {
    console.log(myVar);
  }
}
doSomething();

In this example, a number will appear in the console 1. The code inside the expression ifhas access to the variable we created outside of it. Therefore, he displays its value in the console. What happens if you try to shuffle the scopes? For example, do this:

functiondoSomething() {
  let myVar = 1;
  if (true) {
    console.log(myVar);
    let myVar = 2;
    console.log(myVar);
  }
}
doSomething();

It may seem that the first call console.logwill output 1, but in fact, when you try to execute this code, an error will appear ReferenceErrorthat tells us that the variable myVarfor this scope is not defined or not initialized (the text of this error differs in different browsers). In JavaScript, there is such a thing as raising variables to the upper part of their scope. That is, if a variable is declared in some scope, JavaScript reserves space for it even before the command for its declaration is executed. How exactly this happens is different when using varand let.

Consider the following example:

console.log(varTest);
var varTest = 1;
console.log(letTest);
let letTest = 2;

In both cases, we try to use the variable before it is declared. But the commands to output data to the console behave differently. The first, using a variable that will later be declared using a keyword var, will output undefined— that is, what will be written into this variable. The second command, which tries to access a variable, which will later be declared using a keyword let, will issue ReferenceErrorand inform us that we are trying to use the variable before its declaration or initialization. What's the matter?

But the point is that before executing the code, the mechanisms responsible for its execution look through this code, find out whether any variables will be declared in it, and, if so, they are lifted with space reserved for them. In this case, variables declared using a keyword are varinitialized with a value undefinedwithin their scope, even if they are accessed before they are declared. The main problem here is that the value undefinedin the variable does not always indicate that the variable is attempted to be used before it is declared. Take a look at the following example:

var var1;
console.log(var1);
console.log(var2);
var var2 = 1;

In this case, despite the fact that var1they are var2announced differently, both calls console.logwill be output undefined. The point here is that the variables declared using var, but not initialized, automatically recorded value undefined. In this case, as we have said, the variables declared with the help varthat are accessed before they are declared, also contain undefined. As a result, if something goes wrong in such a code, it will not be possible to understand what exactly is the source of the error — the use of an uninitialized variable or the use of a variable prior to its declaration.

Place for variables declared by the keywordlet, reserved in their block, but, before their announcement, they fall into the Temporal Dead Zone (TDZ). This leads to the fact that they, before their declaration, cannot be used, and an attempt to access such a variable leads to an error. However, the system knows the exact cause of the problem and reports this. This is clearly seen in this example:

let var1;
console.log(var1);
console.log(var2);
let var2 = 1;

Here, the first call console.logwill print undefined, and the second will cause an error ReferenceError, telling us that the variable has not yet been declared or initialized.

As a result, if it varappears during use undefined, we do not know the reason for the program’s behavior. A variable can either be declared and uninitialized, or it may not yet be declared in this scope, but will be declared in code that is located below the command to access it. When using a keyword, letwe can understand what exactly is happening, and this is much more useful for debugging.

▍Using the const keyword


The keyword is constvery similar let, but they have one important difference. This keyword is used to declare constants. Values ​​of constants after their initialization cannot be changed. It should be noted that this applies only to the values ​​of primitive types, water strings or numbers. If a constant is something more complicated, for example, an object or an array, the internal structure of such an entity can be modified, you cannot just replace it with another. Take a look at the following code:

let mutableVar = 1;
const immutableVar = 2;
mutableVar = 3;
immutableVar = 4;

This code will be executed up to the last line. Attempting to assign a new value to a constant will result in an error TypeError. This is how constants behave, but, as already mentioned, the objects with which constants are initialized can be changed, they can undergo mutations, which can lead to surprises .

Perhaps you, as a JavaScript developer, wonder why immunity of variables is important. Constants are a new phenomenon in JavaScript, while they are an essential part of languages ​​like C or Java. Why is this concept so popular? The fact is that the use of constants makes us think about exactly how our code works. In some situations, a change in the value of a variable may disrupt the operation of the code, for example, if it contains the number Pi and it is constantly being accessed, or if the variable has a link to some HTML element that you need to work with all the time. Say, here is a constant in which a link to a certain button is written:

const myButton = document.querySelector('#my-button');

If the code depends on the link to the HTML element, then we need to ensure that the link remains unchanged. As a result, it can be said that the key word constgoes not only along the path of improvements in the scope of visibility, but also along the path of limiting the possibility of modifying the values ​​of constants declared using this keyword. Remember how we talked about the fact that a variable should have exactly the scope that it needs. This idea can be continued by putting forward a recommendation that the variable should have only such an opportunity to change, which is necessary for proper work with it, and no more. Here isgood material on the topic of immunity, from which an important conclusion can be drawn, according to which the use of immiable variables forces us to think more carefully about our code, which leads to an improvement in the purity of the code and to a decrease in the number of unpleasant surprises that arise during its operation.

When I just started using keywords letand const, I basically applied let, resorting to constonly when writing a new value to a variable declared with help letcould harm the program. But, learning more about programming, I changed my mind on this issue. Now my main tool is this const, andletI use it only when the value of a variable needs to be rewritten. It makes me think about whether it is really necessary to change the value of some variable. In most cases, this is not necessary.

Do we need the var keyword?


Keywords letand constpromote a more responsible approach to programming. Are there any situations in which the keyword is still needed var? Yes, there are. There are several situations in which this keyword is still useful to us. Think about what we are going to talk about before changing varto letor const.

▍var level of keyword support by browsers


Variables declared using a keyword varare distinguished by one very important feature missing from letand const. Namely, we are talking about the fact that this keyword is supported by absolutely all browsers. Although the let and const browsers support is very good, however, there is a risk that your program will get into a browser that does not support them. In order to understand the consequences of such an incident, you need to consider how browsers treat unsupported JavaScript code, as opposed to, for example, how they react to incomprehensible CSS code.

If the browser does not support any CSS feature, then this basically leads to some distortion of what will be displayed on the screen. A site in a browser that does not support any of the styles used by the site will not look as expected, but it is very likely you can use it. If you use, for example, letand the browser does not support this keyword, then your JS code will simply not work there. It will not be - that's all. Considering the fact that JavaScript is one of the important components of the modern web, this can be a very serious problem if you need your programs to work in outdated browsers.

When they talk about the support of sites by browsers, they usually ask themselves in which browser the site will work optimally. If we are talking about a site, the functionality of which is based on use letand const, then a similar question will have to be put differently: “In which browsers will our site not work?”. And this is much more serious than talking about whether to use or not display: flex. For most websites, the number of users with outdated browsers will not be large enough to be worth worrying about. However, if we are talking about something like an online store, or sites whose owners buy advertising, this can be a very important consideration. Before using new opportunities in such projects, assess the level of risk.

If you need to maintain a really old browsers, but you want to still use let, constand other new features ES6, one of the solutions to the problem is to use JavaScript-transpilyatora like Babel . Transporters provide a translation of the new code into what will be clear to the old browsers. Using Babel, you can write modern code that uses the latest features of the language, and then convert it to code that older browsers can perform.

Sounds too good to be true? In fact, the use of transpilers conceals some unpleasant features. So, it significantly increases the amount of finished code, if we compare it with what could be obtained by writing it manually. As a result, the volume of files grows. In addition, if you started using a certain transpiler, your project is tied to it. Even if you write the ES6-code, which is perfectly processed by Babel, the refusal of Babel will lead to the fact that you have to recheck all the code, carefully test it. If your project works like a clock, this idea is unlikely to appeal to those who develop and support it. There will have to ask some questions. When is it planned to rework the code base? When will support for something like IE8 no longer matter? Maybe,

▍Using var to solve one specific problem.


There is another situation in which a keyword varcan do something that others cannot. This is a rather specific task. Consider the following code:

var myVar = 1;
functionmyFunction(){
  var myVar = 2;
  // Внезапно оказалось, что нам нужна переменная myVar из глобальной области видимости!
}

So, here we declare a variable myVarin the global scope, but later we lose access to it, because we declare the same variable in the function. Suddenly it turns out that we need a variable from the global scope. Such a situation may seem far-fetched, since the first variable, for example, can simply be passed to functions, or one of them can be renamed. But it may well happen that neither one nor the other is available to you. And this is where opportunities will come in handy var.

var myVar = 1;
functionmyFunction() {
  var myVar = 2;
  console.log(myVar); // 2
  console.log(window.myVar); // 1
}

When a variable is declared in the global scope using it var, it is automatically bound to the global object window. Keywords letand constdo not. This feature once helped me out in a situation where the assembly script checked the JS code before merging the files, and the link to the global variable in one of the files (which, after the assembly, would be merged with the others) produced an error that did not allow to build the project.

It should be noted that the use of this feature leads to writing inaccurate code. Most often, a similar problem is solved much more elegantly, resulting in a cleaner code and a lower probability of errors. The point is that variables, in the form of properties, are written into their own object:

let myGlobalVars = {};
let myVar = 1;
myGlobalVars.myVar = myVar;
functionmyFunction() {
  let myVar = 2;
  console.log(myVar); // 2
  console.log(myGlobalVars.myVar); // 1
}

Of course, there will have to write more code, but this approach makes the code used to solve some unexpected problems more understandable. In any case, it happens that the possibility of a keyword being considered varturns out to be useful, although, before resorting to it, it is worthwhile to look for another, more understandable, way to solve the problem.

Results


So what to choose? How to prioritize? Here are some thoughts on this:

  • Are you going to support IE10 or really old browsers? If you give a positive answer to this question and do not intend to use transpilers, refuse new features and use var.
  • If you can afford the use of new JavaScript features, should start with the fact that wherever applied before the keyword var, use const. If you need somewhere the possibility of rewriting the value of a variable (although if you try to rewrite your code, then you may not need this opportunity) - use let.

New keywords letand constnew ones in ECMAScript 6 give us more control over the scope of variables (and constants) in the code of websites and web applications. They make us think more about how the code works, and such reflections have a good influence on what we do. Of course, before using something new, it is worth weighing the pros and cons as applied to a specific task, but using letand constyou will make your projects more stable and prepare them for the future.

Dear readers! Do you agree with the recommendation that a keyword constshould be made the main substitute for a keyword varwhen resorting tolet only in cases where the value of the variable, for objective reasons, is it necessary to overwrite?


Also popular now: