Movatterモバイル変換


[0]ホーム

URL:


9. Variables and scoping
Table of contents
Please support this book:buy it (PDF, EPUB, MOBI) ordonate
(Ad, please don’t block.)

9.Variables and scoping



9.1Overview

ES6 provides two new ways of declaring variables:let andconst, which mostly replace the ES5 way of declaring variables,var.

9.1.1let

let works similarly tovar, but the variable it declares isblock-scoped, it only exists within the current block.var isfunction-scoped.

In the following code, you can see that thelet-declared variabletmp only exists inside the block that starts in line A:

functionorder(x,y){if(x>y){// (A)lettmp=x;x=y;y=tmp;}console.log(tmp===x);// ReferenceError: tmp is not definedreturn[x,y];}

9.1.2const

const works likelet, but the variable you declare must be immediately initialized, with a value that can’t be changed afterwards.

constfoo;// SyntaxError: missing = in const declarationconstbar=123;bar=456;// TypeError: `bar` is read-only

Sincefor-of creates onebinding (storage space for a variable) per loop iteration, it is OK toconst-declare the loop variable:

for(constxof['a','b']){console.log(x);}// Output:// a// b

9.1.3Ways of declaring variables

The following table gives an overview of six ways in which variables can be declared in ES6 (inspired bya table by kangax):

 HoistingScopeCreates global properties
varDeclarationFunctionYes
letTemporal dead zoneBlockNo
constTemporal dead zoneBlockNo
functionCompleteBlockYes
classNoBlockNo
importCompleteModule-globalNo

9.2Block scoping vialet andconst

Bothlet andconst create variables that areblock-scoped – they only exist within the innermost block that surrounds them. The following code demonstrates that theconst-declared variabletmp only exists inside the block of theif statement:

functionfunc(){if(true){consttmp=123;}console.log(tmp);// ReferenceError: tmp is not defined}

In contrast,var-declared variables are function-scoped:

functionfunc(){if(true){vartmp=123;}console.log(tmp);// 123}

Block scoping means that you can shadow variables within a function:

functionfunc(){constfoo=5;if(···){constfoo=10;// shadows outer `foo`console.log(foo);// 10}console.log(foo);// 5}

9.3const creates immutable variables

Variables created bylet are mutable:

letfoo='abc';foo='def';console.log(foo);// def

Constants, variables created byconst, are immutable – you can’t assign different values to them:

constfoo='abc';foo='def';// TypeError

Spec detail: changing aconst variable always throws aTypeError

Normally, changing an immutable binding only causes an exception in strict mode, as perSetMutableBinding(). Butconst-declared variables always produce strict bindings – seeFunctionDeclarationInstantiation(func, argumentsList), step 35.b.i.1.

9.3.1Pitfall:const does not make the value immutable

const only means that a variable always has the same value, but it does not mean that the value itself is or becomes immutable. For example,obj is a constant, but the value it points to is mutable – we can add a property to it:

constobj={};obj.prop=123;console.log(obj.prop);// 123

We cannot, however, assign a different value toobj:

obj={};// TypeError

If you want the value ofobj to be immutable, you have to take care of it, yourself. For example, byfreezing it:

constobj=Object.freeze({});obj.prop=123;// TypeError
9.3.1.1Pitfall:Object.freeze() is shallow

Keep in mind thatObject.freeze() isshallow, it only freezes the properties of its argument, not the objects stored in its properties. For example, the objectobj is frozen:

>constobj=Object.freeze({foo:{}});>obj.bar=123TypeError:Can't add property bar, object is not extensible> obj.foo = {}TypeError: Cannot assign to read only property 'foo'of#<Object>

But the objectobj.foo is not.

> obj.foo.qux = 'abc';> obj.foo.qux'abc'

9.3.2const in loop bodies

Once aconst variable has been created, it can’t be changed. But that doesn’t mean that you can’t re-enter its scope and start fresh, with a new value. For example, via a loop:

functionlogArgs(...args){for(const[index,elem]ofargs.entries()){// (A)constmessage=index+'. '+elem;// (B)console.log(message);}}logArgs('Hello','everyone');// Output:// 0. Hello// 1. everyone

There are twoconst declarations in this code, in line A and in line B. And during each loop iteration, their constants have different values.

9.4The temporal dead zone

A variable declared bylet orconst has a so-calledtemporal dead zone (TDZ): When entering its scope, it can’t be accessed (got or set) until execution reaches the declaration. Let’s compare the life cycles ofvar-declared variables (which don’t have TDZs) andlet-declared variables (which have TDZs).

9.4.1The life cycle ofvar-declared variables

var variables don’t have temporal dead zones. Their life cycle comprises the following steps:

9.4.2The life cycle oflet-declared variables

Variables declared vialet have temporal dead zones and their life cycle looks like this:

const variables work similarly tolet variables, but they must have an initializer (i.e., be set to a value immediately) and can’t be changed.

9.4.3Examples

Within a TDZ, an exception is thrown if a variable is got or set:

lettmp=true;if(true){// enter new scope, TDZ starts// Uninitialized binding for `tmp` is createdconsole.log(tmp);// ReferenceErrorlettmp;// TDZ ends, `tmp` is initialized with `undefined`console.log(tmp);// undefinedtmp=123;console.log(tmp);// 123}console.log(tmp);// true

If there is an initializer then the TDZ endsafter the initializer was evaluated and the result was assigned to the variable:

letfoo=console.log(foo);// ReferenceError

The following code demonstrates that the dead zone is reallytemporal (based on time) and not spatial (based on location):

if(true){// enter new scope, TDZ startsconstfunc=function(){console.log(myVar);// OK!};// Here we are within the TDZ and// accessing `myVar` would cause a `ReferenceError`letmyVar=3;// TDZ endsfunc();// called outside TDZ}

9.4.4typeof throws aReferenceError for a variable in the TDZ

If you access a variable in the temporal dead zone viatypeof, you get an exception:

if(true){console.log(typeoffoo);// ReferenceError (TDZ)console.log(typeofaVariableThatDoesntExist);// 'undefined'letfoo;}

Why? The rationale is as follows:foo is not undeclared, it is uninitialized. You should be aware of its existence, but aren’t. Therefore, being warned seems desirable.

Furthermore, this kind of check is only useful for conditionally creating global variables. That is something that you don’t need to do in normal programs.

9.4.4.1Conditionally creating variables

When it comes to conditionally creating variables, you have two options.

Option 1 –typeof andvar:

if(typeofsomeGlobal==='undefined'){varsomeGlobal={···};}

This option only works in global scope (and therefore not inside ES6 modules).

Option 2 –window:

if(!('someGlobal'inwindow)){window.someGlobal={···};}

9.4.5Why is there a temporal dead zone?

There are several reasons whyconst andlet have temporal dead zones:

9.4.6Further reading

Sources of this section:

9.5let andconst in loop heads

The following loops allow you to declare variables in their heads:

To make a declaration, you can use eithervar,let orconst. Each of them has a different effect, as I’ll explain next.

9.5.1for loop

var-declaring a variable in the head of afor loop creates a singlebinding (storage space) for that variable:

constarr=[];for(vari=0;i<3;i++){arr.push(()=>i);}arr.map(x=>x());// [3,3,3]

Everyi in the bodies of the three arrow functions refers to the same binding, which is why they all return the same value.

If youlet-declare a variable, a new binding is created for each loop iteration:

constarr=[];for(leti=0;i<3;i++){arr.push(()=>i);}arr.map(x=>x());// [0,1,2]

This time, eachi refers to the binding of one specific iteration and preserves the value that was current at that time. Therefore, each arrow function returns a different value.

const works likevar, but you can’t change the initial value of aconst-declared variable:

// TypeError: Assignment to constant variable// (due to i++)for (const i=0; i<3; i++) {    console.log(i);}

Getting a fresh binding for each iteration may seem strange at first, but it is very useful whenever you use loops to create functions that refer to loop variables, as explained ina later section.

for loop: per-iteration bindings in the spec

The evaluation of thefor loop handlesvar as the second case andlet/const as the third case. Onlylet-declared variables are added to the listperIterationLets (step 9), which is passed toForBodyEvaluation() as the second-to-last parameter,perIterationBindings.

9.5.2for-of loop andfor-in loop

In afor-of loop,var creates a single binding:

constarr=[];for(variof[0,1,2]){arr.push(()=>i);}arr.map(x=>x());// [2,2,2]

const creates one immutable binding per iteration:

constarr=[];for(constiof[0,1,2]){arr.push(()=>i);}arr.map(x=>x());// [0,1,2]

let also creates one binding per iteration, but the bindings it creates are mutable.

Thefor-in loop works similarly to thefor-of loop.

for-of loop: per-iteration bindings in the spec

Per-iteration bindings infor-of are handled byForIn/OfBodyEvaluation. In step 5.b, a new environment is created and bindings are added to it viaBindingInstantiation (mutable forlet, immutable forconst). The current iteration value is stored in the variablenextValue and used to initialize the bindings in either one of two ways:

9.5.3Why are per-iteration bindings useful?

The following is an HTML page that displays three links:

  1. If you click on “yes”, it is translated to “ja”.
  2. If you click on “no”, it is translated to “nein”.
  3. If you click on “perhaps”, it is translated to “vielleicht”.
<!doctypehtml><html><head><metacharset="UTF-8"></head><body><divid="content"></div><script>constentries=[['yes','ja'],['no','nein'],['perhaps','vielleicht'],];constcontent=document.getElementById('content');for(const[source,target]ofentries){// (A)content.insertAdjacentHTML('beforeend',`<div><a id="${source}" href="">${source}</a></div>`);document.getElementById(source).addEventListener('click',(event)=>{event.preventDefault();alert(target);// (B)});}</script></body></html>

What is displayed depends on the variabletarget (line B). If we had usedvar instead ofconst in line A, there would be a single binding for the whole loop andtarget would have the value'vielleicht', afterwards. Therefore, no matter what link you click on, you would always get the translation'vielleicht'.

Thankfully, withconst, we get one binding per loop iteration and the translations are displayed correctly.

9.6Parameters as variables

9.6.1Parameters versus local variables

If youlet-declare a variable that has the same name as a parameter, you get a static (load-time) error:

functionfunc(arg){letarg;// static error: duplicate declaration of `arg`}

Doing the same inside a block shadows the parameter:

functionfunc(arg){{letarg;// shadows parameter `arg`}}

In contrast,var-declaring a variable that has the same name as a parameter does nothing, just like re-declaring avar variable within the same scope does nothing.

functionfunc(arg){vararg;// does nothing}
functionfunc(arg){{// We are still in same `var` scope as `arg`vararg;// does nothing}}

9.6.2Parameter default values and the temporal dead zone

If parameters have default values, they are treated like a sequence oflet statements and are subject to temporal dead zones:

// OK: `y` accesses `x` after it has been declaredfunctionfoo(x=1,y=x){return[x,y];}foo();// [1,1]// Exception: `x` tries to access `y` within TDZfunctionbar(x=y,y=2){return[x,y];}bar();// ReferenceError

9.6.3Parameter default values don’t see the scope of the body

The scope of parameter default values is separate from the scope of the body (the former surrounds the latter). That means that methods or functions defined “inside” parameter default values don’t see the local variables of the body:

constfoo='outer';functionbar(func=x=>foo){constfoo='inner';console.log(func());// outer}bar();

9.7The global object

JavaScript’sglobal object (window in web browsers,global in Node.js) is more a bug than a feature, especially with regard to performance. That’s why it makes sense that ES6 introduces a distinction:

Note that the bodies of modules are not executed in global scope, only scripts are. Therefore, the environments for various variables form the following chain.

9.8Function declarations and class declarations

Function declarations…

The following code demonstrates the hoisting of function declarations:

{// Enter a new scopeconsole.log(foo());// OK, due to hoistingfunctionfoo(){return'hello';}}

Class declarations…

Classes not being hoisted may be surprising, because, under the hood, they create functions. The rationale for this behavior is that the values of theirextends clauses are defined via expressions and those expressions have to be executed at the appropriate times.

{// Enter a new scopeconstidentity=x=>x;// Here we are in the temporal dead zone of `MyClass`constinst=newMyClass();// ReferenceError// Note the expression in the `extends` clauseclassMyClassextendsidentity(Object){}}

9.9Coding style:const versuslet versusvar

I recommend to always use eitherlet orconst:

  1. Preferconst. You can use it whenever a variable never changes its value. In other words: the variable should never be the left-hand side of an assignment or the operand of++ or--. Changing an object that aconst variable refers to is allowed:
    constfoo={};foo.prop=123;// OK

    You can even useconst in afor-of loop, because one (immutable) binding is created per loop iteration:

    for(constxof['a','b']){console.log(x);}// Output:// a// b

    Inside the body of thefor-of loop,x can’t be changed.

  2. Otherwise, uselet – when the initial value of a variable changes later on.
    letcounter=0;// initial valuecounter++;// changeletobj={};// initial valueobj={foo:123};// change
  3. Avoidvar.

If you follow these rules,var will only appear in legacy code, as a signal that careful refactoring is required.

var does one thing thatlet andconst don’t: variables declared via it become properties of the global object. However, that’s generally not a good thing. You can achieve the same effect by assigning towindow (in browsers) orglobal (in Node.js).

9.9.1An alternative approach

An alternative to the just mentioned style rules is to useconst only for things that are completely immutable (primitive values and frozen objects). Then we have two approaches:

  1. Preferconst:const marks immutable bindings.
  2. Preferlet:const marks immutable values.

I lean slightly in favor of #1, but #2 is fine, too.

Next:10. Destructuring

[8]ページ先頭

©2009-2025 Movatter.jp