web-dev-qa-db-ja.com

ES6では、letまたはconstで宣言された変数は使用されませんか。

私はしばらくの間ES6で遊んできましたが、varで宣言されたwhile変数が期待通りに巻き上げられていることに気づきました...

console.log(typeof name); // undefined
var name = "John";

... letまたはconstで宣言された変数は、巻き上げに問題があるようです。

console.log(typeof name); // ReferenceError
let name = "John";

そして

console.log(typeof name); // ReferenceError
const name = "John";

これは、letまたはconstで宣言された変数がホイストされていないという意味ですか?本当にここで何が起こっているの?この問題でletconstの間に違いはありますか?

217
Luboš Turek

@thefourtheyeは、宣言される前にこれらの変数アクセスできないと言っているのが正しいです。しかし、それはそれより少し複雑です。

letname__またはconstname__で宣言された変数はホイストされていませんか?本当にここで何が起こっているの?

すべての宣言varname__、letname__、constname__、functionname__、function*classname__)JavaScriptでは「含まれている」これは、名前がスコープ内で宣言されている場合、そのスコープ内で識別子が常にその特定の変数を参照することを意味します。

x = "global";
// function scope:
(function() {
    x; // not "global"

    var/let/… x;
}());
// block scope (not for `var`s):
{
    x; // not "global"

    let/const/… x;
}

これは、関数スコープとブロックスコープの両方に当てはまります。1

varname __/functionname __/function*宣言とletname __/constname __/classname__宣言の違いは初期化です。
前者は、バインディングがスコープの先頭に作成されたときに、undefinedname__または(generator)関数で初期化されます。ただし、字句的に宣言された変数は未初期化のままです。つまり、アクセスしようとするとReferenceErrorname__例外がスローされます。 letname __/constname __/classname__ステートメントが評価されるときにのみ初期化されます。それ以前の一時的デッドゾーンと呼ばれるものすべてです。

x = y = "global";
(function() {
    x; // undefined
    y; // Reference error: y is not defined

    var x = "local";
    let y = "local";
}());

let y;ステートメントがlet y = undefined;のようにundefinedname__で変数を初期化することに注意してください。

一時的なデッドゾーンは構文上の場所ではなく、時間です。変数(スコープ)の作成と初期化の間そのコードが実行されない限り宣言の上のコードで変数を参照することはエラーではありません、そして初期化の前に変数にアクセスしたとしても、初期化の前に変数にアクセスすれば例外を投げます。コードが宣言の下にあります(たとえば、呼び出しが早すぎる呼び出し関数宣言の中など)。

この問題でletname__とconstname__の間に違いはありますか?

いいえ、吊り上げに関しては同じように機能します。唯一の違いは、constname__antは宣言のイニシャライザ部分でのみ割り当てられ、割り当てられることができるということです(const one = 1;const one;およびそれ以降のone = 2のような再割り当ては無効です)。

1:varname__宣言は、もちろん関数レベルでのみ機能しています、もちろん

293
Bergi

ECMAScript 6(ECMAScript 2015)仕様の引用符、 letおよびconstの宣言 section、

含まれているLexical Environmentがインスタンス化されたときに変数が作成されますが、変数のLexicalBindingが評価されるまでにアクセスすることはできません

したがって、あなたの質問に答えるために、はい、letおよびconstホイストですが、実際の宣言が実行時に評価される前にそれらにアクセスすることはできません。

73
thefourtheye

ES6block level scopingを思い付くLet変数を紹介します。 ES5まで我々はblock level scopingを持っていませんでした、それでブロックの中で宣言される変数は関数レベルの有効範囲に対して常にhoistedです。

基本的にScopeはあなたのプログラムのどこであなたの変数が見えるかを参照します、それはあなたが宣言した変数を使うことが許される場所を決定します。 ES5にはglobal scope,function scope and try/catch scopeがあり、ES6にはLetを使ってブロックレベルのスコープを取得することもできます。

  • varキーワードで変数を定義すると、それが定義された瞬間から関数全体がわかります。
  • letステートメントで変数を定義すると、その変数は定義されているブロック内でしか認識されません。

     function doSomething(arr){
         //i is known here but undefined
         //j is not known here
    
         console.log(i);
         console.log(j);
    
         for(var i=0; i<arr.length; i++){
             //i is known here
         }
    
         //i is known here
         //j is not known here
    
         console.log(i);
         console.log(j);
    
         for(let j=0; j<arr.length; j++){
             //j is known here
         }
    
         //i is known here
         //j is not known here
    
         console.log(i);
         console.log(j);
     }
    
     doSomething(["Thalaivar", "Vinoth", "Kabali", "Dinesh"]);
    

コードを実行すると、変数jloop内でのみ認識され、前後には認識されないことがわかります。しかし、変数iは、定義された瞬間からentire functionで認識されています。

Letをそのまま使用するもう1つの大きな利点があります。新しいレキシカル環境を作成し、古い参照を保持するのではなく、新しい値をバインドします。

for(var i=1; i<6; i++){
   setTimeout(function(){
      console.log(i);
   },1000)
}

for(let i=1; i<6; i++){
   setTimeout(function(){
      console.log(i);
   },1000)
}

最初のforループは常に最後の値を表示し、letを指定すると新しいスコープを作成し、新しい値をバインドして1, 2, 3, 4, 5を出力します。

constantsに来ると、それは基本的にletのように動作します、唯一の違いはそれらの値が変更できないことです。定数では変更は許可されていますが、再割り当ては許可されていません。

const foo = {};
foo.bar = 42;
console.log(foo.bar); //works

const name = []
name.Push("Vinoth");
console.log(name); //works

const age = 100;
age = 20; //Throws Uncaught TypeError: Assignment to constant variable.

console.log(age);

定数がobjectを参照する場合、それは常にobjectを参照しますが、object自体は変更可能です(変更可能な場合)。不変のobjectを使いたい場合は、Object.freeze([])を使用できます。

18
Thalaivar

MDN web docs: から

ECMAScript 2015では、letおよびconstは吊り上げられていますが、初期化されていません。変数はブロックの開始から宣言が処理されるまで「一時的なデッドゾーン」にあるため、変数宣言の前にブロック内の変数を参照するとReferenceErrorになります。

console.log(x); // ReferenceError
let x = 3;
1