ECMAScript6はステートメントを導入しましlet
た。
変数として記述されていると聞きましたが、キーワードlocal
との動作の違いはまだよくわかりません。var
違いは何ですか?let
?の代わりにいつ使用する必要がありvar
ますか?
let
た。変数として記述されていると聞きましたが、キーワードlocal
との動作の違いはまだよくわかりません。var
違いは何ですか?let
?の代わりにいつ使用する必要がありvar
ますか?
主な違いはスコープ ルールです。キーワードによって宣言された変数はvar
、直接の関数本体 (したがって関数スコープ)にスコープされますが、変数は、によって示されるlet
すぐに囲んで{ }
いるブロック(したがってブロック スコープ) にスコープされます。
function run() {
var foo = "Foo";
let bar = "Bar";
console.log(foo, bar); // Foo Bar
{
var moo = "Mooo"
let baz = "Bazz";
console.log(moo, baz); // Mooo Bazz
}
console.log(moo); // Mooo
console.log(baz); // ReferenceError
}
run();
言語にキーワードが導入された理由let
は、関数のスコープがわかりにくく、JavaScript のバグの主な原因の 1 つでした。
別のスタック オーバーフローの質問からこの例を見てください。
var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
My value: 3
funcs[j]();
匿名関数が同じ変数にバインドされているため、呼び出されるたびにコンソールに出力されました。
ループから正しい値を取得するために、すぐに呼び出される関数を作成する必要がありましたが、これも面倒でした。
var
keyword で宣言された変数は巻き上げられundefined
ます (コードが実行される前に で初期化されます)。
function run() {
console.log(foo); // undefined
var foo = "Foo";
console.log(foo); // Foo
}
run();
let
変数は、その定義が評価されるまで初期化されません。初期化の前にそれらにアクセスすると、ReferenceError
. 変数は、ブロックの開始から初期化が処理されるまで、「一時的なデッド ゾーン」にあると言われます。
function checkHoisting() {
console.log(foo); // ReferenceError
let foo = "Foo";
console.log(foo); // Foo
}
checkHoisting();
トップ レベルでlet
は、 は とは異なりvar
、グローバル オブジェクトにプロパティを作成しません。
var foo = "Foo"; // globally scoped
let bar = "Bar"; // not allowed to be globally scoped
console.log(window.foo); // Foo
console.log(window.bar); // undefined
厳密モードでは、SyntaxErrorを発生させvar
ながら、同じスコープで同じ変数を再宣言できます。let
'use strict';
var foo = "foo1";
var foo = "foo2"; // No problem, 'foo1' is replaced with 'foo2'.
let bar = "bar1";
let bar = "bar2"; // SyntaxError: Identifier 'bar' has already been declared
ここにいくつかの例でキーワードの説明があります。let
let
と非常によく似ていvar
ます。主な違いは、var
変数のスコープが囲んでいる関数全体であるということです
ウィキペディアのこの表は、どのブラウザーがJavascript1.7をサポートしているかを示しています。
MozillaとChromeブラウザのみがそれをサポートしていることに注意してください。IE、Safari、および潜在的に他のものはそうではありません。
受け入れられた答えにはポイントがありません:
{
let a = 123;
};
console.log(a); // ReferenceError: a is not defined
let
キーワードを使用して宣言された変数はブロック スコープです。つまり、宣言されたブロックlet
内でのみ使用できます。
最上位レベルでは、を使用して宣言された変数はlet
、グローバル オブジェクトのプロパティを作成しません。
var globalVariable = 42;
let blockScopedVariable = 43;
console.log(globalVariable); // 42
console.log(blockScopedVariable); // 43
console.log(this.globalVariable); // 42
console.log(this.blockScopedVariable); // undefined
関数内 (ただしブロック外)let
は、 と同じスコープを持ちますvar
。
(() => {
var functionScopedVariable = 42;
let blockScopedVariable = 43;
console.log(functionScopedVariable); // 42
console.log(blockScopedVariable); // 43
})();
console.log(functionScopedVariable); // ReferenceError: functionScopedVariable is not defined
console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined
ブロック内で使用して宣言された変数はlet
、そのブロックの外ではアクセスできません。
{
var globalVariable = 42;
let blockScopedVariable = 43;
console.log(globalVariable); // 42
console.log(blockScopedVariable); // 43
}
console.log(globalVariable); // 42
console.log(blockScopedVariable); // ReferenceError: blockScopedVariable is not defined
in ループで宣言された変数は、let
そのループ内でのみ参照できます。
for (var i = 0; i < 3; i++) {
var j = i * 2;
}
console.log(i); // 3
console.log(j); // 4
for (let k = 0; k < 3; k++) {
let l = k * 2;
}
console.log(typeof k); // undefined
console.log(typeof l); // undefined
// Trying to do console.log(k) or console.log(l) here would throw a ReferenceError.
let
ループの代わりに使用するvar
と、反復ごとに新しい変数が取得されます。つまり、ループ内でクロージャを安全に使用できます。
// Logs 3 thrice, not what we meant.
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Logs 0, 1 and 2, as expected.
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 0);
}
一時的なデッド ゾーンのため、を使用して宣言された変数は、宣言let
される前にアクセスできません。そうしようとすると、エラーがスローされます。
console.log(noTDZ); // undefined
var noTDZ = 43;
console.log(hasTDZ); // ReferenceError: hasTDZ is not defined
let hasTDZ = 42;
を使用して同じ変数を複数回宣言することはできませんlet
。let
また、 using で宣言された別の変数と同じ識別子で変数 using を宣言することはできませんvar
。
var a;
var a; // Works fine.
let b;
let b; // SyntaxError: Identifier 'b' has already been declared
var c;
let c; // SyntaxError: Identifier 'c' has already been declared
const
const
とよく似ていlet
ます—ブロックスコープであり、TDZ があります。ただし、異なる点が 2 つあります。
using で宣言された変数はconst
再割り当てできません。
const a = 42;
a = 43; // TypeError: Assignment to constant variable.
値が不変であるという意味ではないことに注意してください。そのプロパティはまだ変更できます。
const obj = {};
obj.a = 42;
console.log(obj.a); // 42
不変オブジェクトが必要な場合は、 を使用する必要がありますObject.freeze()
。
を使用して変数を宣言するときは、常に値を指定する必要がありますconst
。
const a; // SyntaxError: Missing initializer in const declaration
微妙な違いがいくつかあります —let
スコープの動作は、多かれ少なかれ他の言語での変数のスコープのように動作します。
たとえば、囲んでいるブロックにスコープが設定されている、宣言される前は存在しない、などです。
let
ただし、これは新しい Javascript 実装の一部にすぎず、さまざまな程度のブラウザー サポートがあることに注意してください。
変数が巻き上げられない
let
それらが出現するブロックの全範囲にホイストすることはありません。対照的に、var
以下のようにホイストできます。
{
console.log(cc); // undefined. Caused by hoisting
var cc = 23;
}
{
console.log(bb); // ReferenceError: bb is not defined
let bb = 23;
}
実際、@Bergi ごとに、 と の両方が巻き上げられてvar
let
います。
ガベージ コレクション
のブロック スコープは、let
メモリを再利用するためのクロージャとガベージ コレクションに関連して役立ちます。検討、
function process(data) {
//...
}
var hugeData = { .. };
process(hugeData);
var btn = document.getElementById("mybutton");
btn.addEventListener( "click", function click(evt){
//....
});
ハンドラー コールバックには、変数click
はまったく必要ありません。hugeData
理論的には、process(..)
実行後、巨大なデータ構造hugeData
がガベージ コレクションされる可能性があります。click
ただし、関数にはスコープ全体のクロージャーがあるため、一部の JS エンジンはこの巨大な構造を保持しなければならない可能性があります。
ただし、ブロック スコープを使用すると、この巨大なデータ構造をガベージ コレクションにすることができます。
function process(data) {
//...
}
{ // anything declared inside this block can be garbage collected
let hugeData = { .. };
process(hugeData);
}
var btn = document.getElementById("mybutton");
btn.addEventListener( "click", function click(evt){
//....
});
let
ループ
let
ループ内で、ループの各反復に再バインドし、前のループ反復の最後から値を再割り当てするようにします。検討、
// print '5' 5 times
for (var i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i);
}, 1000);
}
ただしvar
、let
// print 1, 2, 3, 4, 5. now
for (let i = 0; i < 5; ++i) {
setTimeout(function () {
console.log(i);
}, 1000);
}
a) イニシャライザ式 b) 各反復 (以前はインクリメント式を評価するため) にこれらの名前を使用して新しいレキシカル環境を作成するためlet
、詳細についてはhereを参照してください。
これは、他の人がすでに書いたものに追加する例です。関数の配列 を作成するとしますadderFunctions
。各関数は単一の Number 引数を取り、引数と配列内の関数のインデックスの合計を返します。adderFunctions
キーワードを使用してループを生成しようとしてvar
も、誰かが素朴に期待するようには機能しません。
// An array of adder functions.
var adderFunctions = [];
for (var i = 0; i < 1000; i++) {
// We want the function at index i to add the index to its argument.
adderFunctions[i] = function(x) {
// What is i bound to here?
return x + i;
};
}
var add12 = adderFunctions[12];
// Uh oh. The function is bound to i in the outer scope, which is currently 1000.
console.log(add12(8) === 20); // => false
console.log(add12(8) === 1008); // => true
console.log(i); // => 1000
// It gets worse.
i = -8;
console.log(add12(8) === 0); // => true
のスコープは、各関数が作成されi
たブロックの反復を超えて拡張されるため、上記のプロセスでは目的の関数配列が生成されません。for
代わりに、ループの最後で、i
in 各関数のクロージャーは、i
のすべての無名関数のループの最後 (1000) で の値を参照しadderFunctions
ます。これは私たちが望んでいたことではありません。メモリ内にはまったく同じ動作をする 1000 個の異なる関数の配列があります。その後 の値を更新するとi
、突然変異はすべての に影響しますadderFunctions
。
ただし、次のlet
キーワードを使用して再試行できます。
// Let's try this again.
// NOTE: We're using another ES6 keyword, const, for values that won't
// be reassigned. const and let have similar scoping behavior.
const adderFunctions = [];
for (let i = 0; i < 1000; i++) {
// NOTE: We're using the newer arrow function syntax this time, but
// using the "function(x) { ..." syntax from the previous example
// here would not change the behavior shown.
adderFunctions[i] = x => x + i;
}
const add12 = adderFunctions[12];
// Yay! The behavior is as expected.
console.log(add12(8) === 20); // => true
// i's scope doesn't extend outside the for loop.
console.log(i); // => ReferenceError: i is not defined
今回は、ループi
の反復ごとにリバウンドします。for
各関数i
は、関数の作成時の値を保持し、adderFunctions
期待どおりに動作するようになりました。
ここで、2 つの動作の混合をイメージすると、同じスクリプトで新しい動作let
とconst
古い動作を混在させることが推奨されない理由がわかるでしょう。var
これを行うと、非常に紛らわしいコードが作成される可能性があります。
const doubleAdderFunctions = [];
for (var i = 0; i < 1000; i++) {
const j = i;
doubleAdderFunctions[i] = x => x + i + j;
}
const add18 = doubleAdderFunctions[9];
const add24 = doubleAdderFunctions[12];
// It's not fun debugging situations like this, especially when the
// code is more complex than in this example.
console.log(add18(24) === 42); // => false
console.log(add24(18) === 42); // => false
console.log(add18(24) === add24(18)); // => false
console.log(add18(24) === 2018); // => false
console.log(add24(18) === 2018); // => false
console.log(add18(24) === 1033); // => true
console.log(add24(18) === 1030); // => true
これがあなたに起こらないようにしてください。リンターを使用します。
注:
var
これは、ループ内での/のlet
動作と、関数クロージャを使用したわかりやすい例を示すことを目的とした教育用の例です。これは数字を追加するひどい方法です。しかし、無名関数クロージャでデータをキャプチャする一般的な手法は、現実世界の他のコンテキストで遭遇する可能性があります。YMMV。
次の 2 つの関数が違いを示しています。
function varTest() {
var x = 31;
if (true) {
var x = 71; // Same variable!
console.log(x); // 71
}
console.log(x); // 71
}
function letTest() {
let x = 31;
if (true) {
let x = 71; // Different variable
console.log(x); // 71
}
console.log(x); // 31
}
let
興味深いのは、次のようなことができるからです。
(() => {
var count = 0;
for (let i = 0; i < 2; ++i) {
for (let i = 0; i < 2; ++i) {
for (let i = 0; i < 2; ++i) {
console.log(count++);
}
}
}
})();
その結果、[0、7] がカウントされます。
一方
(() => {
var count = 0;
for (var i = 0; i < 2; ++i) {
for (var i = 0; i < 2; ++i) {
for (var i = 0; i < 2; ++i) {
console.log(count++);
}
}
}
})();
[0, 1] のみをカウントします。
また、少なくとも Visual Studio 2015、TypeScript 1.5 では、「var」はブロック内で同じ変数名の複数の宣言を許可し、「let」は許可しないようです。
これはコンパイルエラーを生成しません:
var x = 1;
var x = 2;
この意志:
let x = 1;
let x = 2;
いくつかのハックlet
:
1.
let statistics = [16, 170, 10];
let [age, height, grade] = statistics;
console.log(height)
2.
let x = 120,
y = 12;
[x, y] = [y, x];
console.log(`x: ${x} y: ${y}`);
3.
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined
let node = {
type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"
let
:let jar = {
numberOfCookies: 10,
get cookies() {
return this.numberOfCookies;
},
set cookies(value) {
this.numberOfCookies = value;
}
};
console.log(jar.cookies)
jar.cookies = 7;
console.log(jar.cookies)
私は現在、JavaScript を深く理解しようとしているので、既に説明したいくつかの優れた部分と、別の観点からのその他の詳細を含む私の簡単な調査を共有します。
functionとblock scopeの違いを理解すれば、 varとletの違いを理解しやすくなります。
次のケースを考えてみましょう。
(function timer() {
for(var i = 0; i <= 5; i++) {
setTimeout(function notime() { console.log(i); }, i * 1000);
}
})();
Stack VariableEnvironment //one VariablEnvironment for timer();
// when the timer is out - the value will be the same value for each call
5. [setTimeout, i] [i=5]
4. [setTimeout, i]
3. [setTimeout, i]
2. [setTimeout, i]
1. [setTimeout, i]
0. [setTimeout, i]
####################
(function timer() {
for (let i = 0; i <= 5; i++) {
setTimeout(function notime() { console.log(i); }, i * 1000);
}
})();
Stack LexicalEnvironment - each iteration has a new lexical environment
5. [setTimeout, i] [i=5]
LexicalEnvironment
4. [setTimeout, i] [i=4]
LexicalEnvironment
3. [setTimeout, i] [i=3]
LexicalEnvironment
2. [setTimeout, i] [i=2]
LexicalEnvironment
1. [setTimeout, i] [i=1]
LexicalEnvironment
0. [setTimeout, i] [i=0]
がtimer()
呼び出されると、 VariableEnvironmentと各反復に対応するすべてのLexicalEnvironmentsの両方を含むExecutionContextが作成されます。
そしてもっと簡単な例
機能範囲
function test() {
for(var z = 0; z < 69; z++) {
//todo
}
//z is visible outside the loop
}
ブロックスコープ
function test() {
for(let z = 0; z < 69; z++) {
//todo
}
//z is not defined :(
}
この記事では、var、let、および const の違いを明確に定義しています。
const
識別子が再割り当てされないというシグナルです。
let
は、ループ内のカウンターやアルゴリズム内の値のスワップなど、変数が再割り当てされる可能性があることを示す信号です。また、変数が定義されているブロックでのみ使用されることも通知します。これは、必ずしも含まれている関数全体ではありません。
var
は、JavaScript で変数を定義するときに使用できる最も弱いシグナルになりました。変数は再割り当てされる場合とされない場合があり、変数は関数全体、または単にブロックまたはループの目的で使用される場合と使用されない場合があります。
https://medium.com/javascript-scene/javascript-es6-var-let-or-const-ba58b8dcde75#.esmkpbg9b