JavaScriptでプログラミング教育するときの変数定義ではvar/let/constのどれを使うべきか2020

アシアル情報教育研究所の岡本です

JavaScriptで変数を定義するときに「let」使っていますか?

私、これまでletを使ってこなかったのですが、2021年度の公式テキストでは「let」導入しようかなと思い立ち、調査を始めました。
※ 2020年度版のテキストは印刷済みなので、「var」のままです

let導入の検討理由

「開発者ツール」で「ステップ実行」するときに、letで変数定義した方が確認しやすかったから。

letで定義すると「スクリプト」のスコープで変数が扱われるので、開発者ツールで変数の値を確認するときに見つけやすいことが判明しました。

varで定義すると「グローバル」のスコープで変数の値を確認できるのですが、ここには有りとあらゆる変数が列挙されており、変数を探すのが大変でした。

これは、let導入を検討する必要がありますね。

プログラミング教育でスコープの話は必要か?

必ずしも必要ではない(というか後回しにすべき)!

「システム開発」の新人教育では必要ですが、「プログラミング教育」はプロの要請を目的としていないため、スコープの話は必ずしも必要ではありません。
 学習時間が潤沢にあれば別ですが、10~20時間の限られた時間でプログラミング入門を教える場合には「配列・関数・繰り返し」を教えるが大変なので、スコープの話は後回しで良いと考えます。

第一、関数よりも前にスコープの話をしても、有り難みがない。

あと、文科省の教員研修用資料では変数の紹介はありますが、スコープはスコープ外で「var」すら使ってませんでした(マジで)。

「var」を「let」に切り替えたときのデメリット

varはVariableの略と説明できるが、letは何の略と説明するか、それが問題だ。
あとはif文やfor文の中で変数定義をした場合に、ifやforの外から変数が参照できなくて問合せが殺到するかもしれない。

そうだ変数を先に定義させよう

プログラムの中で利用するスコープの広い変数を先に宣言するようにし、ifやforやfunctionの中では狭いスコープの変数を定義するように指導すれば、スコープが原因でトラブルに見舞われる機会は減るはず。

試しにサンプルアプリをletに書き換えた
書籍「Monacaで学ぶはじめてのプログラミング」で最もソースコードの長いサンプルアプリである「英単語学習アプリ」(約70行)をvarからletに変えてみました。

英単語学習アプリのJS部分(before)


// 問題番号
var no = 0;
// 正解数
var score = 0;
// 単語リスト
var wordList = [
    {
        japanese: "電話",
        english: "Phone"
    },
    {
        japanese: "歴史",
        english: "History"
    },
    {
        japanese: "社会",
        english: "Society"
    },
    {
        japanese: "世代",
        english: "Generation"
    },
    {
        japanese: "知識",
        english: "Knowledge"
    }
];

// 問題を表示する
function showQuestion() {
    if(no < wordList.length) {
        // 次の問題がある場合は、表示する
        document.getElementById("question").innerHTML = wordList[no].japanese;              
    } else {
        //全問終了したら、成績を発表する
        document.getElementById("question").innerHTML = score + "/" + wordList.length;
        document.getElementById("answerForm").style.display = "none";
        if(score == wordList.length) {
            // 全問正解の場合
            document.getElementById("resultMessage").innerHTML = "全問正解!よくできました!";
            document.getElementById("resultImage").src = "gold.png";
        } else if(score >= wordList.length * 0.6) {
            // 6割以上正解の場合
            document.getElementById("resultMessage").innerHTML = "惜しい!あともう一歩でした!";
            document.getElementById("resultImage").src = "silver.png";
        } else {
            // 6割未満の場合
            document.getElementById("resultMessage").innerHTML = "もう少しがんばりましょう。";
            document.getElementById("resultImage").src = "bronze.png";
        }
    }
}

// 入力された回答の正誤判定を行う
function judge() {
    var answer = document.getElementById("answer").value;
    if(answer == wordList[no].english) {
        alert("正解です!");
        score++;
    } else {
        alert("残念!不正解です。");
    }
   
    // 次の問題を表示
    no++;
    showQuestion();
    var answer = document.getElementById("answer").value = "";
}

英単語学習アプリのJS部分(after)


// 問題番号
let no = 0;
// 正解数
let score = 0;
// 単語リスト
let wordList = [
    {
        japanese: "電話",
        english: "Phone"
    },
    {
        japanese: "歴史",
        english: "History"
    },
    {
        japanese: "社会",
        english: "Society"
    },
    {
        japanese: "世代",
        english: "Generation"
    },
    {
        japanese: "知識",
        english: "Knowledge"
    }
];

// 問題を表示する
function showQuestion() {
    if(no < wordList.length) {
        // 次の問題がある場合は、表示する
        document.getElementById("question").innerHTML = wordList[no].japanese;              
    } else {
        //全問終了したら、成績を発表する
        document.getElementById("question").innerHTML = score + "/" + wordList.length;
        document.getElementById("answerForm").style.display = "none";
        if(score == wordList.length) {
            // 全問正解の場合
            document.getElementById("resultMessage").innerHTML = "全問正解!よくできました!";
            document.getElementById("resultImage").src = "gold.png";
        } else if(score >= wordList.length * 0.6) {
            // 6割以上正解の場合
            document.getElementById("resultMessage").innerHTML = "惜しい!あともう一歩でした!";
            document.getElementById("resultImage").src = "silver.png";
        } else {
            // 6割未満の場合
            document.getElementById("resultMessage").innerHTML = "もう少しがんばりましょう。";
            document.getElementById("resultImage").src = "bronze.png";
        }
    }
}

// 入力された回答の正誤判定を行う
function judge() {
    let answer = document.getElementById("answer").value;
    if(answer == wordList[no].english) {
        alert("正解です!");
        score++;
    } else {
        alert("残念!不正解です。");
    }
   
    // 次の問題を表示
    no++;
    showQuestion();
    let answer = document.getElementById("answer").value = "";
}

let変更後の実行結果

二重定義して怒られました、MonacaIDE上では77行目です。
function judge()の中でanswer変数をletで二重に定義しているのが原因です。
varなら二重定義が許されていたのですが、letだとダメ。
これは、二重定義しなければよい、77行目のletを外せば解決できました。

ところで「const」とは何か?

定数のようなものです。

アシアル社内のエンジニアいわく、「再代入できない変数」のほうがしっくりくるそうですが、定数とします。MDNにも定数と書かれています。
const – MDN – Mozilla

数字や文字列のような値(プリミティブ)を代入する場合は、変更できないのですが配列やオブジェクトや配列(JavaScriptの場合は配列もオブジェクトの一種)の中の値は変更可能です。

こんなかんじ。オブジェクトのプロパティも普通に追加できますね。

なお、アシアル社内のエンジニアいわく、「基本は全部constで定義して、後で変更する物だけletにして欲しい」と申していました。

岡本の意見は「初心者が事前に、後で変更するかどうか分かるわけ無いだろう。インタプリタ型のテキストプログラミングで入門するメリットは、保守性より柔軟性。」
ということで、原則const案は採用しないと思いますが、初級者以上の方はご参考までに。

まとめ

  • プログラミング教育においてスコープの話はスコープ外
  • 100行未満のコードだったら、varでもletでも大して変わらないと思う
  • しかし、開発者ツールでステップ実行するときにletの御利益がある
  • ifやforの中でletを使ってスコープにハマる事故さえ防げるならlet使いたい
  • 「スコープ」や「変数の巻き上げ」を紹介する記事を次は書きたい

追伸:

元アシアル社員で「Webフロントエンドハイパフォーマンスチューニング」という専門書も執筆されているanatooさんも、constの推奨について2016年にブログで言及されていました。

ES2015でvarやletを使う場面はほとんど無いので、まずconstを使う。constだとダメな場合にはletを使う。
JavaScript(ES2015)でvarやletを使う必要はほぼ無い