JavaScriptで変数を宣言するために "let"と "var"を使用する違いは何ですか?



Answers

letはクロージャの問題を避けるためにも使用できます。 下の例に示すように、古い参照を保持するのではなく、新しい値をバインドします。

DEMO

for(var i = 1; i < 6; i++) {
  document.getElementById('my-element' + i)
    .addEventListener('click', function() { alert(i) })
}

上のコードは、古典的なJavaScriptクロージャの問題を示しています。 i変数の参照は、 iの実際の値ではなく、クリックハンドラのクロージャに格納されています。

すべてのクリックハンドラは同じオブジェクトを参照します。これは、6個のカウンタオブジェクトが1つしかないため、クリックごとに6個のカウンタオブジェクトが存在するためです。

一般的な回避策は、これを無名関数にラップし、引数としてiを渡すことです。 このような問題は、以下のコードに示すようにlet代わりにvarを使用することでも回避できます。

DEMO (ChromeとFirefox 50でテスト済み)

'use strict';

for(let i = 1; i < 6; i++) {
  document.getElementById('my-element' + i)
    .addEventListener('click', function() { alert(i) })
}
Question

ECMAScript 6 letステートメントを導入しまし 。 私はそれが "ローカル"変数として記述されていると聞いたことがありますが、私はそれがvarキーワードとは異なった動作をするかどうかまだはっきりしていません。

違いは何ですか? いつvar上で使用letれるべきですか?




This article clearly defines the difference between var, let and const

const is a signal that the identifier won't be reassigned.

let , is a signal that the variable may be reassigned, such as a counter in a loop, or a value swap in an algorithm. It also signals that the variable will be used only in the block it's defined in, which is not always the entire containing function.

var is now the weakest signal available when you define a variable in JavaScript. The variable may or may not be reassigned, and the variable may or may not be used for an entire function, or just for the purpose of a block or loop.

https://medium.com/javascript-scene/javascript-es6-var-let-or-const-ba58b8dcde75#.esmkpbg9b







let

ブロックスコープ

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

関数の内部

funcitonの中で(ブロックの外側)、 letvarと同じスコープを持ちます。

(() => {
  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を使って宣言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

ループ内

let 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を使うと、各繰り返しで新しい変数が得られます。 つまり、ループ内でクロージャを安全に使用することができます。

// 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を使って宣言された変数は宣言letれる前にアクセスできません。 そうしようとするとエラーがスローされます。

console.log(noTDZ); // undefined
var noTDZ = 43;
console.log(hasTDZ); // ReferenceError: hasTDZ is not defined
let hasTDZ = 42;

再宣言しない

letを使って同じ変数を複数回宣言することはできません。 varを使って宣言された別の変数と同じ識別子を持つletを使って変数を宣言することもできません。

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

constlet -itのブロックスコープと非常によく似ており、TDZを持っています。 しかし、2つの異なるものがあります。

再割り当てしない

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



Some hacks with 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"

Getter and setter with 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)



May the following two functions show the difference:

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 is interesting, because it allows us to do something like this:

(() => {
    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++);
            }
        }
    }
})();

Which results in counting [0, 7].

Whereas

(() => {
    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++);
            }
        }
    }
})();

Only counts [0, 1].




letvar違いは何ですか?

  • varステートメントを使用して定義された変数は、関数の始めから定義されている関数全体で知られいます。 (*)
  • let文を使って定義された変数はそれが定義されているブロックでのみ定義されます。 (**)

違いを理解するには、次のコードを検討してください。

// i IS NOT known here
// j IS NOT known here
// k IS known here, but undefined
// l IS NOT known here

function loop(arr) {
    // i IS known here, but undefined
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( var i = 0; i < arr.length; i++ ) {
        // i IS known here, and has a value
        // j IS NOT known here
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here

    for( let j = 0; j < arr.length; j++ ) {
        // i IS known here, and has a value
        // j IS known here, and has a value
        // k IS known here, but has a value only the second time loop is called
        // l IS NOT known here
    };

    // i IS known here, and has a value
    // j IS NOT known here
    // k IS known here, but has a value only the second time loop is called
    // l IS NOT known here
}

loop([1,2,3,4]);

for( var k = 0; k < arr.length; k++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS NOT known here
};

for( let l = 0; l < arr.length; l++ ) {
    // i IS NOT known here
    // j IS NOT known here
    // k IS known here, and has a value
    // l IS known here, and has a value
};

loop([1,2,3,4]);

// i IS NOT known here
// j IS NOT known here
// k IS known here, and has a value
// l IS NOT known here

ここで、変数jは最初のforループでのみ認識され、前後では認識されないことがわかります。 しかし、私たちの変数iは関数全体で知られています。

また、ブロックスコープ変数は、ホイストされていないため宣言される前には認識されていないと考えてください。 同じブロックスコープの変数を同じブロック内で再宣言することも許可されていません。 これにより、ブロックスコープ変数は、グローバルにまたは機能的にスコープ化された変数よりもエラーを起こしにくくなります。変数は、ホイストされ、複数の宣言の場合にエラーを生成しません。

今日はletを使っても安全ですか?

将来的にlet文を使用し、var文は廃止されると主張する人もいます。 JavaScriptの達人カイル・シンプソンなぜそうでないのかについて非常に精巧な記事を書い

しかし今日では、そうは間違いありません。 実際には、 let文を使用するのが安全かどうかを実際に尋ねる必要があります。 その質問に対する答えは、あなたの環境によって異なります。

  • サーバー側のJavaScriptコード( Node.js )を記述している場合は、 letステートメントを安全に使用できます。

  • クライアント側のJavaScriptコードを記述していて、Transizer( Traceur )を使用している場合は、 letステートメントを安全に使用することができますが、コードはパフォーマンスに関して最適なものになる可能性があります。

  • クライアントサイドのJavaScriptコードを記述していて、トランスパイライザを使用していない場合は、ブラウザのサポートを考慮する必要があります。

    現在、2016年2月23日には、 letサポートしていないブラウザや、部分的なサポートしかないブラウザがあります。

    • Internet Explorer 10以降 (サポートなし)
    • Firefox 43以降 (サポートなし)
    • Safari 9以降 (サポートなし)
    • Opera Mini 8以降 (サポートなし)
    • Androidブラウザ4以下(サポートなし)
    • Opera 36以降 (部分的サポート)
    • Chrome 51以降 (部分的なサポート)

ブラウザのサポートを追跡する方法

この回答を読んでいるときにletステートメントをサポートしているブラウザの最新の概要については、 このCan I Use 」ページを参照してください

(*)グローバル変数と関数スコープ変数は、JavaScript変数がhoistedれて宣言される前に初期化され、使用されます。 これは、宣言が常にスコープの上端にあることを意味します。

(**)ブロックスコープ変数は持ち上げられません




他の人がすでに書いたものに追加する例です。 関数の配列adderFunctionsを作成したいとします。各関数は単一のNumber引数を取り、引数と関数のインデックスの合計を配列に返します。 varキーワードを使ってループを使ってadderFunctionsを生成しようとすると、誰かが期待できないように動作しません。

// 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

上記のプロセスは、それぞれの関数が作成されたforブロックの反復を超えてスコープが拡張されているので、目的の関数配列を生成しません。 代わりに、ループの最後に、各関数の閉包におけるiは、 adderFunctionsすべての無名関数のループ(1000)の最後にあるiの値を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

今回は、 forループの繰り返しごとにリバウンドします。 各関数は関数の作成時にiの値を保持し、 adderFunctionsは期待adderFunctions動作します。

さて、2つの動作を混在させたイメージを見てみましょう。なぜなら、同じスクリプト内で新しいletconstを古い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。




var is global scope (hoist-able) variable.

let and const is block scope.

test.js

{
    let l = 'let';
    const c = 'const';
    var v = 'var';
    v2 = 'var 2';
}

console.log(v, this.v);
console.log(v2, this.v2);
console.log(l); // ReferenceError: l is not defined
console.log(c); // ReferenceError: c is not defined




いくつかの微妙な違いがあります。スコープは、他の言語の変数スコープのように動作します。

それは囲むブロックにスコープします。宣言される前に存在しません。

しかしletは新しいJavascriptの実装の一部に過ぎず、 ブラウザサポートの程度は様々です。




Related