支援対象地域:札幌、仙台、関東、愛知、関西、広島、福岡


はじめに

プログラミングの言語にもバージョンがあることはご存じでしょうか?大幅なバージョンのアップデートにより、同様の言語であっても一部の使い方が異なったり、処理内容がより効率的になったりすることがあります。

こちらの記事でお話しするJavaScriptも仕様がES5からES6に更新されてからしばらく経過しました。今やJavaScript(以下、JS)を扱う中でES6が主流となってきておりますが、「var / let / const」の使い分けで悩む方もいるのではないでしょうか?
「var / let / const」はすべてJSで使用する変数宣言の方法です。ES5以前のJSは、変数宣言をする際、何も考えずvarを指定すれば問題なかったので選択肢は1つのみと迷うことはありませんでした。しかし、ES6以降で「let / const」という変数の宣言が追加されてからは、今までの「var」を含む3パターンの変数宣言が使用可能となりました。

この変更に逆に使いづらさを感じるようになった方もいるかもしれません。そんな方のために、こちらの記事では「var / let / const」の違いとその特徴について解説します。この記事が読み終わるころには、みなさんはES6の変更を快く受け入れられるようになることでしょう。

var / let / constとは

前述したように、この3つの単語はJSで変数宣言をする時に使います。この「var / let / const」のうち、letとconstがES6(ECMAScript2015)以降から導入された新しい変数宣言のキーワードになります。

ちなみに、var は variable の略で、意味は文字通り「変数」、constはconstant の略で、意味は「定数」や「不変」、letは、そのままlet という単語が元になっており、意味は「~とする」という形で変数の用途に似ています。のちほど説明する各単語の使い勝手も、これらの単語の意味から考えると覚えやすいかもしれないですね。

最初にも簡単に触れましたが、もともとES5ではvarのみが変数宣言のワードでした。更新後のES6では、このvarも引き続き使えますが、加えてletとconstが使えるようになったのです。

基本的にどのプログラミング言語であっても「変数宣言のワード + 変数名」で変数を宣言します。例えば、Javaのような言語では「int」や「string」などと使用する変数の方を宣言します。Javaでは変数を宣言するときに、あらかじめその変数で使用する型を指定しておくことで目的とは違った型が入るのを防ぐことができます。ただ、宣言時にいちいち正確な型を指定したり、引数で利用する際などに型の変換(キャスト)を厳格に行わなければなりません。

一方で、JSでは変数を宣言する際に型の制約がありません。JSでは「var / let / const」の3パターンでいろいろな型を変数として宣言して取り扱うので、比較的自由度が高く、細かな使い分けをしなくてよいので効率的だと言えます。しかし、決まった用途に沿って気を付けて扱わなければ思わぬエラーを生んでしまうことがあります。
変数を正しく扱うためにも、それぞれの特徴をしっかりと把握しておくことが大切です。それぞれの宣言パターンの特徴を解説していきますが、特にES5まで主流で使われていたvarという変数宣言の特徴を知れば、みなさんが変数宣言時に気を付けるべきポイントを理解していただけるでしょう

それでは、「var / let / const」の特徴を1つずつご紹介いたします。

varの特徴

宣言時に値を代入をしなかった場合、undefined(未定義)になる

varを用いて変数を宣言して何かしらの値を代入しなかった場合、以下のようにundefinedを代入したときと同じ状態として認識されます。varでの宣言時はデフォルトでundefinedが入ります。nullではありませんのでご注意ください。

    var variable_sample; // ここではundefined(未定義)の状態になります。

    variable_sample = 100;  // varまたはletではあとから変数に値を代入、再代入できる

実は宣言キーワードがなくても変数を宣言できる

JSでは「var / let / const」などの変数の宣言をするキーワードをつけることなく変数宣言をすることができてしまいます。また、関数内で変数を宣言してもグローバルの変数として参照できるようにもなります。
ただし、ブロックをまたがる参照はバグの発生率を上げる可能性が高くなりますので、推奨されていません。ブロックにおける親子関係をしっかりと把握しておかないとエラーに繋がりやすくなってしまうので、注意が必要です。のちほど、letとconstを説明する際にもこのスコープは関係してきますので、下記のサンプルプログラムにおけるvarの挙動は覚えておきましょう。

    const global = () => {
        variable_sample = 0;
    };
    global();
    console.log(variable_sample);  // ログの出力結果は「0」

変わった動きをする「巻き上げ」

JSのvarを用いた変数には「巻き上げ」というユニークな特徴があります。
本来、プログラムは逐次処理といったコードを上から下へ実行するのが原則です。しかし、JSでは処理を始める前に「varを探して、変数の宣言をあらかじめ実行しておく」という巻き上げが行われるため、 関数の中で宣言された変数が対象のブロックの一番最初に宣言されたものと見なされます。

上記のルールにのっとり、下記はエラーになりません。

    var variable_sample = "テスト1";

    var box = function() {
        console.log(variable_sample); // ログの出力結果は「テスト1」ではなく「undefined」
        var variable_sample = "テスト2";
        console.log(variable_sample); // ログの出力結果は「テスト2」
    };

var box の関数内部で var variable_sample = "テスト2" と記述していますが、これにより「巻き上げ」が発生しました。
box関数の先頭でvariable_sampleという変数が宣言されたということになります。つまり、関数内でログを出力しようとしている段階では変数の宣言だけされた状態なので、variable_sampleは定義がされていないと認識され、初回のログを出力する箇所ではundefinedとして認識されます。

この挙動をコードに起こしなおしてみましょう。 実際には、以下のように関数の先頭で新たに変数が宣言されるのです。先頭で値を代入せずに変数を宣言していますが、捉え方としてはundefined型の変数を宣言しているのと同じ意味になります。そして、その後の行で値を再代入しているというイメージです。

    var variable_sample = "テスト1";

    var box = function() {
        var variable_sample;
        console.log(variable_sample); // ログの出力結果は「undefined」
        variable_sample = "テスト2";
        console.log(variable_sample); // ログの出力結果は「テスト2」
    };

letの特徴

値の再代入はできるが、変数の再宣言はできない

letを用いた変数は値を再代入することは可能ですが、変数の再宣言は不可能な変数です。
以下のサンプルコードは、varだと何も問題はなく実行可能ですが、letでは変数を再び宣言するタイミングでエラーが発生します。

    let let_sample = "テストA";

    let_sample = "テストB";
    console.log(let_sample); // let_sampleに値が再代入され、出力結果は「テストB」になる

    let let_sample = "テストC"; // let_sample変数をletを使って再び宣言するこの行でSyntaxError(文法が正しくないときのエラー)が発生する

スコープ=ブロックスコープ

先ほど、varの説明でグローバルの変数がありました。ブロック内でネストしたブロックを用いた場合は、親から子のブロックに継承してしまう状況です。
letで変数を宣言するとブロックスコープになります。そのため、if文やfor文、try-catch文のようなブロックを用いたコーディングをする際に挙動がvarと異なります。

ちなみに、varの場合は同じ関数の中であればスコープが同じものとして扱われます。つまり、入れ子になっていたとしても値の再代入ができます。

    /* varの場合 */
    var sample= () => {
      var variable_sample = 0;
      console.log(variable_sample); 

      {
        var variable_sample = 10;
        console.log(variable_sample); 
      }

      console.log(variable_sample);
    };

    sample();
      /*
        出力結果は以下の順番で表示される    
        0
        10
        10
      */

では、letの場合はどうでしょうか。
スコープがブロックスコープとなり、同じ関数の内部であってもブロックを分けてあげることで 同じ変数名に値を再代入しても影響を受けなくなります。

    /* letの場合 */
    let sample = () => {
      let let_sample = 0;
      console.log(let_sample);

      {
        let let_sample = 10;
        console.log(let_sample);
      }

      console.log(let_sample);
    };

    sample();
      /*
        出力結果は以下の順番で表示される    
        0
        10
        0
      */

constの特徴

再代入も再宣言もできない

constもletと同様にES6から追加された変数を宣言するキーワードで、値の再代入も変数の再宣言も不可能なため定数として扱うことがほとんどです。下記のサンプルコードは、実行時に値を再代入しようとする箇所ならびに変数の再宣言をしようとした箇所でエラーが発生します。

    const const_sample = 0;

    const_sample  = 10; // 値を再代入しようとしているためTypeError(演算が実行できなくなった時のエラー)が発生する

    const const_sample  = 100; // 変数を再宣言しようとしているためSyntaxError(文法が正しくないときのエラー)が発生する

ただし、注意点もあります。constで宣言した変数への値の再代入はできませんが、objectの中身であれば変更が可能です。値の変更をどれも受け付けないわけではありません。
以下のサンプルコードはobjectの中身を書き換えています。こちらはエラーにはならず、実行することが可能です。

    const sample_object = {
      name: "佐々木一郎",
      age: 28
    };

    sample_object.name = "佐々木次郎";
    sample_object.age = 23;

    console.log(sample_object); // 出力結果は「{ name: "佐々木次郎", age: 23 }」

スコープ=ブロックスコープ

スコープもletと同様にブロックスコープです。ただし、letとは違って値の再代入ができないので、下記のサンプルコードのようにtry-catch文のブロックで囲むことによってスコープの外に出てエラーとなってしまいます。コードを書く際にはスコープを意識した注意が必要です。

    try {
      const sample_object = { name: "山田" };
    } catch (e) {
      console.log(e);
    }

    const name = sample_object.name; // sample_objectが別のブロックで宣言されていて参照することができないReferenceError(文法が正しくないときのエラー)

constを用いてtry-catch文を扱いたい場合には、以下ののサンプルコードのようにletに変更して書く必要があります。このようにletとconstを使い分ける必要があります。

    let sample_object;
    try {
      sample_object = { name: "山田" };
    } catch (e) {
      console.log(e);
    }

    const name = sample_object.name; 
    console.log(name); // 出力結果は「山田」

letとconstの使い分けのイメージは固まってきたでしょうか?これらの仕様があることで、使い方が制限されることがメリットになり得ます。自由度の高さがあればあるほど、設計やあとからの修正が大変になりますが、このletとconstの追加のおかげで、varのみの開発よりも、一段と使いやすくなりました。

では次に、なぜvarが推奨されないのかをご紹介します。それぞれの変数宣言の特徴を知ったうえでさらに推奨されない理由を把握し、効率的なコーディングを行えるようになりましょう。

なぜvarは推奨されないのか

現在、JSではvarが推奨されなくなり、今後JSで変数を宣言する際にはletとconstを用いることが主流となっています。varが推奨されなくなった理由としては以下の2つが挙げられます。

1. varは変数の書き換えが簡単にできてしまうため、意図しないエラーが発生する

エンジニアとして現場を経験していくと、個人開発以外のプログラムに触れることでしょう。大規模な開発プロジェクトになればなるほど長文のコードに触れることになります。そのような長いコードを書く際、varを使って変数を宣言するとどうなるでしょうか。誤って二重に宣言したことに気付かないかもしれませんし、ブロック内で書き換えた値が意図せず関数内の全体に影響を与えてしまうかもしれません。
そんなときに「let / const」を用いていればエラーとして出力してくれます。意図せず変数を上書きをする前にエラーが出力されることで、事前にバグに気付くことができます。

2. letやconstに比べるとvarは「巻き上げ」がエラーを発生させやすい

こちらも事前にエラー出力で解決できるので、考え方は同じです。巻き上げも同様に、letやconstで宣言した場合であれば値を代入する前の変数を参照しようとするとReference Error(文法が正しくないときのエラー)として扱われます。これにより、変数が未定義(undefined)のまま処理を続けてしまうといった状態を防ぐことができます。

推奨されなくなった理由を知ると、letとconstが魅力的に思えてきますね。

今からJSを学習される方は、参考資料にvarがあっても、letやconstに置き換えてコーディングしていくよう心がけましょう。少し古い記事ですと、letやconstが用いられていない場合が多いです。 一部をコピペして自分のコードに組み込むときは、これらの単語が混在しないよう気を付けてください。

まとめ

最後に各キーワードの特徴を簡潔にまとめると以下のようになります。

・var : 再代入○、再宣言○、関数スコープ
・let : 再代入○、再宣言×、ブロックスコープ
・const : 再代入×、再宣言×、ブロックスコープ

今回の記事ではそれぞれの特徴について諸々解説しましたが、慣れないうちは上記の特徴まとめだけでも覚えておくと良いでしょう。これらはプログラミングを行っていくうえで、実際にエラーに触れてこそ覚えていくものです。この記事で説明した事象を、ぜひご自身でも動作させてみてください。

エンジニアをお仕事にされている方であればなおさら、過去のバージョンや以前から組まれていた実際のコードでvarが多用されているプロジェクトに触れることもあるかもしれません。たかが変数宣言かもしれませんが、その1つ1つに気を遣えるエンジニアであれば、チーム開発を進めるうえで重宝されること間違いなしです。
仕組みをしっかりと理解したうえで意図して、自分だけで完結するコーディングをするのではなく、他者のヒューマンエラーも回避していくプログラムが書けると素晴らしいでしょう。最後までお読みいただき、ありがとうございました。