原文: https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/

新しい機能がたくさん ES2015 (ES6) で発表されました。2020 年現在、多くの JavaScript の開発者はその機能に慣れ、そして使い出したと考えられています。

この考えは部分的には正しいかもしれませんが、一部の開発者には、その機能のいくつかはいまだに謎のままかもしれません。

ES6 で発表された機能のひとつに、letconst の追加があります。これらは、変数宣言に用いられます。問題となるのは、私たちが使用してきた古き良き var とどう違うのかということです。もし、この違いをまだ十分に理解されていないならば、この記事が役に立つことでしょう。

この記事では varletconst のそれぞれのスコープ、使用法、宣言の巻き上げ (hoisting) に関して説明します。これから挙げられる、各宣言の違いに注目しながら読み進めてください。

Var

ES6 の到来以前は、var 宣言が使われていました。しかし、var で宣言された変数には問題がありました。そのため変数を宣言する、新しい方法が登場する必要がありました。その問題を説明する前に、まずは var をより理解することから始めていきましょう。

var のスコープ

スコープとは本来、変数が使用できる場所を意味します。var 宣言は、グローバルスコープまたは関数/ローカルスコープです。

関数の外で var によって変数が宣言される場合、その変数はグローバルスコープです。これは、関数ブロックの外において var で宣言されたあらゆる変数は、プログラム全体のどこでも使用できることを意味します。

関数の中で var によって変数が宣言される場合、その変数は関数スコープです。これは、宣言した変数が、その関数の中でのみ使用可能であることを意味します。

さらに理解を深めるため、以下の例をご覧ください。

    var greeter = "hey hi";
    
    function newFunction() {
        var hello = "hello";
    }

ここで、関数の外にある greeter はグローバルスコープです。一方、hello は関数スコープです。関数の外から変数 hello にはアクセスできません。そのため、次のように記述した場合には:

    var tester = "hey hi";
    
    function newFunction() {
        var hello = "hello";
    }
    console.log(hello); // error: hello is not defined

関数の外では hello が使用できないことを示すエラーが発生します。

var で宣言された変数は、再宣言も更新も可能

次のように、同じスコープ内であれば、var によって宣言された変数は再宣言も更新も可能であり、エラーも発生しません:

    var greeter = "hey hi";
    var greeter = "say Hello instead";

これはまた、次のようにも記述できます:

    var greeter = "hey hi";
    greeter = "say Hello instead";

var 宣言の巻き上げ

巻き上げとは、コードを実行する前に、変数や関数の宣言がそのスコープの先頭に移されるという JavaScript の仕組みです。これは、以下のように記述した場合には:

    console.log (greeter);
    var greeter = "say hello"

次のように解釈されることを意味します:

    var greeter;
    console.log(greeter); // greeter is undefined
    greeter = "say hello"

つまり、var で宣言された変数は、そのスコープの先頭に巻き上げられ、値は undefined に初期化されます。

var に伴う問題

var には弱点があります。次の例を見ながら解説します:

    var greeter = "hey hi";
    var times = 4;

    if (times > 3) {
        var greeter = "say Hello instead"; 
    }
    
    console.log(greeter) // "say Hello instead"

ここで、times > 3 は true を返しますので、greeter"say Hello instead" に再定義されます。greeter が再定義されるということをわかっていれば問題はありませんが、一方で、それ以前に変数 greeter が既に定義されていることに気づいていない場合は問題です。

気づいていないままコードの他の部分の中で greeter を使用した場合、得られる結果に驚かされるかもしれません。このことは、コードの中で多くのバグを引き起こしかねません。それを避けるために、letconst が必要になります。

Let

let は現在、変数の宣言に好んで用いられています。それは、var 宣言の改善として提供されているので、当然のことでしょう。また let は、先ほど取り扱った var の問題を解決します。これから、なぜそのようなことが可能なのかを見ていきましょう。

let 宣言はブロックスコープ

ブロックとは、{} に囲まれたコードのかたまりです。ブロックは、中括弧の中にあります。中括弧の中にあるすべてのものが、ブロックです。

ブロックの中で、 let で宣言された変数は、そのブロックの中でのみ使用できます。次の例を見ながら解説します:

   let greeting = "say Hi";
   let times = 4;

   if (times > 3) {
        let hello = "say Hello instead";
        console.log(hello);// "say Hello instead"
    }
   console.log(hello) // hello is not defined

ここで、ブロック (変数が宣言された中括弧) の外で hello を用いようとして、エラーが発生しています。let で宣言された変数は、ブロックスコープだからです。

let で宣言された変数は、更新はできても、再宣言はできない

var と同様に、let で宣言された変数は、同じスコープの中で更新できます。ただし、var とは違い、let で宣言された変数は、そのスコープの中での再宣言はできません。以下のコードは動作しますが:

    let greeting = "say Hi";
    greeting = "say Hello instead";

一方で、次に示したコードは、エラーを返します:

    let greeting = "say Hi";
    let greeting = "say Hello instead"; // error: Identifier 'greeting' has already been declared

しかし、同じ名前の変数が、別のスコープの中で定義される場合には、エラーは発生しません:

    let greeting = "say Hi";
    if (true) {
        let greeting = "say Hello instead";
        console.log(greeting); // "say Hello instead"
    }
    console.log(greeting); // "say Hi"

どうしてエラーが起こらないのでしょうか?これは、異なるスコープにあるために、両方のインスタンスが異なる変数として扱われるためです。

このため、let を選択するほうが var よりも良いとされています。let を用いる時、変数はそのスコープ内にのみ存在するため、それよりも前に変数名を使用したかどうかについて、悩まなくてもよくなります。

また、let で宣言された変数は、スコープの中で複数回宣言できないため、前述の var の問題は起こりません。

let 宣言の巻き上げ

var と同様に、let 宣言は先頭に巻き上げられます。undefined で初期化される var との違いは、let キーワードは初期化されないということです。そのため、let で変数をある名前で宣言するより前に、その名前の変数の使用を試みた場合は、Reference Error が発生します。

Const

const で宣言された変数は、定数値を保持します。const 宣言は let 宣言とよく似ています。

const 宣言はブロックスコープ

let 宣言と同様に、const で宣言された変数は、宣言されたブロックの中でのみアクセスできます。

const で宣言された変数は、更新も再宣言もできない

const で宣言された変数の値は、そのスコープの中では変化しません。更新することも、再宣言もできません。そのため、const で変数を宣言した場合、次のようには記述できませんし:

    const greeting = "say Hi";
    greeting = "say Hello instead";// error: Assignment to constant variable. 

また、次のようにも記述できません:

    const greeting = "say Hi";
    const greeting = "say Hello instead";// error: Identifier 'greeting' has already been declared

よって、const で宣言される変数はすべて、宣言時に初期化する必要があります。

const で宣言されたオブジェクトについては、この動作はどういうわけか異なります。const で宣言されたオブジェクトは、更新できないとしても、そのオブジェクトのプロパティーは更新できます。従って、次のようにして const でオブジェクトを宣言した場合には:

    const greeting = {
        message: "say Hi",
        times: 4
    }

次のようには記述できませんが:

    greeting = {
        words: "Hello",
        number: "five"
    } // error:  Assignment to constant variable.

一方で、次のようには記述できます:

    greeting.message = "say Hello instead";

こうすることで、エラーを返さずに greeting.message の値を更新します。

const 宣言の巻き上げ

let と同様に、const 宣言は先頭に巻き上げられますが、初期化されません。

それでは、違いを見落とした場合に備えて、以下にまとめておきます:

  • var 宣言はグローバルスコープまたは関数スコープである一方で、let 宣言と const 宣言はブロックスコープです。
  • var で宣言された変数は、そのスコープの中で更新できますし再宣言できますが、let で宣言された変数は、更新はできても再宣言はできず、const で宣言された変数は、更新も再宣言もできません。
  • 各宣言は、すべてそのスコープの先頭まで巻き上げられます。しかし、var で宣言された変数が、undefined で初期化される一方で、letconst で宣言された変数は、初期化されません。
  • varlet は初期化しなくても宣言できますが、const は、宣言時に初期化される必要があります。

何かご質問やご要望があれば、ご連絡ください。

お読みいただきありがとうございました :)