What is the difference between let and const commands in ES6?

Keywords: Front-end ECMAScript

(1) let command

Basic Usage

ES6 added a let command to declare variables. It is used similarly to

var, but the declared variable is valid only within the block of code in which the let command resides.

{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

In the code block above, let and var are used to declare two variables, respectively.

The two variables are then called outside the code block, resulting in an error in the variable declared by the let, and the variable declared by the var returns the correct value.

This indicates that a let declares a variable that is valid only in the block of code in which it resides.

for loop counters, let commands are appropriate.

for (let i = 0; i < 10; i++) {
  // ...
}

console.log(i);
// ReferenceError: i is not defined

In the code above, counter i is only valid in the for loop, and references outside the loop will fail.

If var is used in the following code, the final output is 10.

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10

In the code above, variable i is declared by the var command and is valid globally, so there is only one variable i globally.

Each loop changes the value of variable i, and the console.log(i) inside the function assigned to array a within the loop, where I points to the global I.

That is, I in all members of array a points to the same i, resulting in the runtime output of the last round of i, which is 10.

If let is used, the declared variable is valid only at the block level, and the final output is 6.

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

In the code above, the variable i is declared by let, and the current i is only valid in this cycle, so each cycle of i is actually a new variable, so the final output is 6.

You might ask, if the variable i of each cycle is redeclared, how does it know the value of the previous cycle to calculate the value of this cycle?

This is because the JavaScript engine remembers the values of the previous cycle and calculates on the basis of the previous cycle when the variable i of this cycle is initialized.

Another special feature of the for loop is that the part that sets the loop variable is a parent scope, while the inner part of the loop is a separate child scope.

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

The above code runs correctly and outputs abc three times.

This indicates that the variable I inside the function is not in the same scope as the loop variable i, and has separate scopes (the same scope cannot be repeated with let to declare the same variable).

1. There is no variable promotion

The var command produces a "variable promotion", meaning that a variable can be used before it is declared with a value of undefined.

This phenomenon is somewhat odd, and according to general logic, variables should be declared before they can be used.

To correct this, the let command changes the grammatical behavior, and the variables it declares must be used after they are declared, or an error will be reported.

// var situation
console.log(foo); // Output undefined
var foo = 2;

// let's case
console.log(bar); // Error ReferenceError
let bar = 2;

In the code above, the variable foo is declared with the var command and a variable promotion occurs, that is, the variable foo already exists but has no value when the script starts running, so undefined is output.

The variable bar is declared with the let command and no variable promotion occurs.

This means that the variable bar does not exist before it is declared, and if used, an error will be thrown.

2. Transient Dead Zone

As long as the let command exists in the block-level scope, the variables it declares are bound to this area and are no longer affected externally.

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

In the code above, there is a global variable tmp, but let declares a local variable TMP within a block-level scope, which causes the latter to bind to the block-level scope, so TMP assignment errors occur before let declares a variable.

ES6 explicitly states that if let and const commands exist in a block, the variables declared by that block for those commands form a closed scope from the beginning. Any use of these variables before they are declared will result in an error.

In summary, a variable is not available in the code block until it is declared using the let command. This is grammatically called a temporal dead zone (TDZ).

if (true) {
  // TDZ Start
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // End of TDZ
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}

In the code above, before the let command declares the variable tmp, it belongs to the Dead Zone of the variable tmp.

"Transient Dead Zone" also means that typeof is no longer a 100% safe operation.

typeof x; // ReferenceError
let x;

In the code above, the variable x is declared with the let command, so before it is declared, it belongs to the "dead zone" of x, and errors will occur whenever the variable is used.

Therefore, the typeof runtime throws a ReferenceError.

For comparison, if a variable is not declared at all, there is no error using typeof.

typeof undeclared_variable // "undefined"

In the code above, undeclared_variable is a variable name that does not exist and returns "undefined".

Therefore, the typeof operator is 100 percent secure and never fails until there are no let s. This is not true now.

This is designed to allow you to develop good programming habits. Variables must be used after they are declared, or you will get errors.

Some "dead zones" are hidden and are not easy to find.

function bar(x = y, y = 2) {
  return [x, y];
}

bar(); // Report errors

In the code above, the call to the bar function fails (some implementations may not fail) because the default value of parameter x equals another parameter, y, which has not yet been declared, is a "dead zone".

If the default value of y is x, no error will be reported because x is already declared.

function bar(x = 2, y = x) {
  return [x, y];
}
bar(); // [2, 2]

In addition, the code below will also fail, unlike var's behavior.

// No error
var x = x;

// Report errors
let x = x;
// ReferenceError: x is not defined

The above code also failed because of a temporary dead zone.

When using let to declare a variable, an error occurs as long as the variable is used before the declaration is complete.

This is the case with the above line, where the value of X is fetched before the declaration statement for the variable x has been executed, resulting in an error where "x" is undefined.

ES6 specifies that temporary dead zones and let and const statements do not exhibit variable elevation, primarily to reduce runtime errors and prevent the use of variables before they are declared, leading to unexpected behavior.

Such errors are common in ES5 and are now easy to avoid.

In summary, the essence of a temporary dead zone is that once you enter the current scope, the variable you want to use already exists, but is not available. You can only get and use a variable until the line of code that declares it appears.

3. Duplicate declarations are not allowed

Lets do not allow duplicate declarations of the same variable within the same scope.

// Report errors
function func() {
  let a = 10;
  var a = 1;
}

// Report errors
function func() {
  let a = 10;
  let a = 1;
}

Therefore, you cannot redeclare parameters within a function.

function func(arg) {
  let arg;
}
func() // Report errors

function func(arg) {
  {
    let arg;
  }
}
func() // No error

4. Block-level Scope

Why do I need a block-level scope?

ES5 has only global and function scopes and no block-level scopes, which leads to many unreasonable scenarios.

In the first scenario, the inner variable may override the outer variable.

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined

The intent of the above code is that the if code block uses an outer tmp variable externally and an inner tmp variable internally.

However, after function f executes, the output is undefined because the variable is elevated, resulting in the inner tmp variable overwriting the outer tmp variable.

In the second scenario, the loop variable used to count leaks to be a global variable.

var s = 'hello';

for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}

console.log(i); // 5

In the code above, variable i is only used to control the loop, but after the loop ends, it does not disappear and is leaked as a global variable.

(2) const command

Basic Usage

const declares a read-only constant. Once declared, the value of a constant cannot be changed.

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

The code above indicates that changing the value of a constant will cause an error.

The variable declared by const must not change its value, which means that,

1.Once a variable is declared, the const must be initialized immediately and cannot be left to be assigned later.

const foo;
// SyntaxError: Missing initializer in const declaration

The code above indicates that for const, simply declaring no assignment will result in an error.

2.const has the same scope as the let command: only valid within the block-level scope in which the declaration is made.

if (true) {
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined

3. Constants declared by the const command are also not elevated, and there are also temporary dead zones

Can only be used after the declared position.

if (true) {
  console.log(MAX); // ReferenceError
  const MAX = 5;
}

The above code was called before the constant MAX declaration and results in an error.

4. Constants declared by const are also declared as non-repeatable as let s.

var message = "Hello!";
let age = 25;

// Both of the following lines will fail
const message = "Goodbye!";
const age = 30;

essence

const does not actually guarantee that the value of a variable cannot be changed, but that the data stored at the memory address to which the variable points must not be changed.

For simple types of data (numeric, string, Boolean), the value is stored at the memory address to which the variable points, and is therefore equivalent to a constant.

However, for data of composite type (mainly objects and arrays), the memory address that the variable points to is only a pointer to the actual data. const can only guarantee that the pointer is fixed (that is, it always points to another fixed address). As to whether the data structure it points to is variable, it cannot be controlled at all.

Therefore, you must be very careful to declare an object as a constant.

const foo = {};

// Add an attribute to foo to succeed
foo.prop = 123;
foo.prop // 123

// Pointing foo to another object will cause an error
foo = {}; // TypeError: "foo" is read-only

In the code above, the constant foo stores an address that points to an object. The only immutable is this address, i.e. foo cannot be pointed to another address, but the object itself is mutable, so you can still add new attributes to it.

Here is another example.

const a = [];
a.push('Hello'); // Executable
a.length = 0;    // Executable
a = ['Dave'];    // Report errors

In the code above, the constant a is an array, and the array itself is writable, but if you assign another array to a, you will get an error.

If you really want to freeze objects, you should use the Object.freeze method.

const foo = Object.freeze({});

// In general mode, the following line does not work;
// When in strict mode, the line will fail
foo.prop = 123;

In the code above, the constant foo points to a frozen object, so adding new attributes will not work and errors will be reported when the strict mode is applied.

In addition to freezing the object itself, the object's properties should also be frozen.

Here is a function that freezes objects completely.

var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

Posted by bazza84 on Sat, 27 Nov 2021 10:02:43 -0800