JavaScriptの変数と定数について

jsコード JavaScript入門

本記事では、プログラミング言語を問わず重要、且つ基礎的な内容である「変数」(および定数)について、詳しく説明していきます。

変数とは

JavaScriptに限らず、全てのプログラミング言語には「変数」という概念があります。

プログラミングにおける「変数」を一文で簡潔に表現するならば、次のようになります。
この一文をしっかりと頭に入れた状態で、記事を読み進めていってください。

「変数」とは、情報を入れておくための容器である。

どんなプログラミング言語にも「変数」はあるので、書き方が異なるだけです。

本記事では、JavaScriptの変数について学んでいきます。

変数を作る(変数宣言)

プログラミング言語で、何か情報を入れる容器を作ることを「変数を宣言する」と言います。

記憶ポイント!

JavaScriptの場合、変数を宣言するには「let」というキーワードを使用します。

では、試しに変数を宣言してみましょう。

例:「年齢」情報を入れておくための変数を宣言してください。

script.js
let age;

入れておく情報が「年齢」なので、変数の名前を「age」にしています。
このように、変数名は、その変数に入る情報を的確に表す英単語で命名することが重要です。

JavaScriptの学習を進める中で、「var」というキーワードを使って変数宣言しているコードを見かけることがあると思います。
これは2015年以前の文法で、以降はあまり推奨されません。世の中には2015年以前に作られたJavaScriptプログラムが現役で活躍しているため、「var」の存在を知っておく必要はあります。

ただし、これからプログラムされる際には、特別な理由がない限り変数宣言には「let」を使用してください。

変数に情報を入れる(代入/初期化)

変数に情報を入れることを、「代入」、あるいは「初期化」と言います。

変数の初期化

変数は、宣言と同時に情報を入れることができます。
それを「変数を初期化する」と言います。

では、試しに変数を初期化してみましょう。

例:「年齢」情報を入れておくための変数を宣言し、20という値で初期化してください。

script.js
let age = 20;

変数への代入

変数を宣言した行とは別の場所で情報を入れることもできます。
それを「変数に代入する」と言います。

変数に代入してみましょう。

script.js
let age;

age = 20;

宣言した変数に代入された値は、別の値に入れ替えることができます。
これを再代入と言います。

script.js
let age = 20;  // 👈変数「age」に、二十歳として20を代入

age = 30;      // 👈サバを読んでいたようで、30に再代入

console.log( age );
実行結果
> 30

定数

変数は、「再代入」できるとお伝えしましたが、再代入を許可したくない場合もあります。
そんな時に役に立つのが定数です。

定数は、一度代入された値を変更することができません。

定数の宣言(および初期化)

定数を宣言する際には、「const」というキーワードを使用します。
また、定数の名前は一般的にすべて大文字のスネークケースで記述されます。
※ スネークケースが分からない方は、「命名規則」の記事をお読みいただくことをお勧めします。

また、重要なポイントとして、定数は宣言と同時に初期化する必要があります。

つまり、次のようなコードはNGです。

script.js
const MY_BIRTH;  // 👈構文エラー!

定数を宣言したならば、その場で固定の値を代入するというルールをしっかりと覚えておいてください。

script.js
const MY_BIRTH = "1999/12/12";  // 👈宣言と初期化が同時に行われている!

そして、前述の通り、初期化された値は再代入できません。

script.js
const MY_BIRTH = "1999/12/12";
MY_BIRTH = "2000/12/12";       // 👈再代入してしまっている

console.log( MY_BIRTH );

👆このように書いてしまうと実行時にエラーになります。エラーメッセージは「定数に対して代入しているタイプエラー」という意味になります。

定数(const)の注意事項

constによって宣言した定数の「巻き上げ」

constキーワードによって宣言された定数は、「let」と同様に、宣言した位置よりも後ろでないとアクセスすることができず、アクセスした場合はエラーになります。

オブジェクトや配列の扱い

constキーワードによって宣言された定数の値が、オブジェクトや配列だった場合、全体を再代入することはできませんが、それらの要素に対する上書きや要素の追加は可能なので注意が必要です。

まずは、定数らしく「再代入ができない」パターンを2つ示します。
定数の値がオブジェクトの場合と、配列の場合です。

定数(オブジェクト)への再代入NGパターン
const x = {k: "v"};

// 👇オブジェクト全体を再代入しようとしている
x = {key: "val"};

// 👇エラーになる
console.log(x);
デベロッパーツールのコンソール
▶ Uncaught TypeError: Assignment to constant variable.
定数(配列)への再代入NGパターン
const x = [1, 2, 3];

// 👇配列全体を再代入しようとしている
x = [4, 5, 6];

// 👇エラーになる
console.log(x);
デベロッパーツールのコンソール
▶ Uncaught TypeError: Assignment to constant variable.

続いて、定数ですが「再代入できる」パターンを2つ示します。
定数の値がオブジェクトの場合と、配列の場合です。

定数のオブジェクト要素への再代入
const x = {k: "v"};

// kプロパティの値を再代入している
x.k = "val";

console.log(x);
デベロッパーツールのコンソール
▶ {k: 'val'}
定数の配列要素への再代入
const x = [1, 2, 3];

// インデックス 0 の値を再代入している
x[0] = 100;

console.log(x);
デベロッパーツールのコンソール
▶ (3) [100, 2, 3]

シャロ―イミュータビリティ(Shallow Immutability)

前述のように、constキーワードによって宣言された定数であっても、オブジェクトや配列だった場合、それらの要素に対する上書きや要素の追加は可能になることを、「シャロ―イミュータビリティ(Shallow Immutability)」あるいは「参照の不変性(Reference Immutability)」と呼ぶことがあります。

シャロ―イミュータビリティを解決するには、Object.freezeを使用する方法があります。

Object.freezeの使用例
const person = Object.freeze({
    name: 'Alice',
    age: 25
});

person.age = 26; // 変更されない
console.log(person.age); // 25

しかし、Object.freezeを使用したとしても、配列の要素がオブジェクトである場合、そのオブジェクト自体は凍結されないため、プロパティの変更が可能になります。

この問題を解決するには、「深い凍結(Deep Freeze)」という手法が用いられます。

Deep Freezeの例
function deepFreeze(obj) {
    Object.freeze(obj);

    Object.keys(obj).forEach(key => {
        if (typeof obj[key] === 'object' && obj[key] !== null) {
            deepFreeze(obj[key]);
        }
    });
}

const people = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 30 }
];
deepFreeze(people);

people[0].age = 26; // 変更されない
console.log(people[0].age); // 25

people.push({ name: 'Charlie', age: 35 }); // 変更されない
console.log(people.length); // 2

👆上記のdeepFreeze関数は、オブジェクトや配列のすべてのネストされたプロパティを再帰的に凍結し、完全な不変性を実現しています。

オブジェクトが複雑にネストされると深い凍結(Deep Freeze)は手間がかかります。JavaScriptのオブジェクトや配列は柔軟であり、どこまでもネスト可能なので、すべての階層を凍結するのは現実的ではない場合もあります。

実際には、完全な深い凍結が必要なケースはそれほど多くありません。
多くの場合、トップレベルの構造が変わらないことを保証するだけで十分です。

それでも尚、特定のシナリオで完全なイミュータビリティが必要という場合には、以下のようなアプローチが考えられます。

  • 部分的な凍結:
    • 重要な部分のみを凍結し、すべてのネストされたプロパティを凍結する必要がない場合は、部分的な凍結を適用します。
  • イミュータビリティを実現できるライブラリの使用:
    • JavaScriptのイミュータビリティをサポートするライブラリ(Immutable.jsやImmerなど)を使用すると、深い凍結を簡単に扱うことができます。

「変数」「定数」という名前の意味

文字通り、「変数」はわる(化する)で、「定数」はまった(固の)なのは想像がつくかと思います。

しかし、本記事で学習したように、変数にも定数にも、「文字列」を入れられますよね。
それなのに「数」というのは矛盾している、と感じられた方はいませんか?
実はこれには納得の理由があるのです。

コンピュータでの「文字」の扱い

いきなり結論から入りますが、コンピュータにとっては、文字も数値なのです。

コンピュータは、あらゆる情報を0と1の2種類の数値から成る2進数で表現します。
人間の目には文字に見える「a」でも、コンピュータから見れば「00001010」という二進数ですし、日本語などの全角文字や、記号にも二進数が割り当てられています。
例えば「あ」は「11100011 10000001 10000010」です。

これが、変数や定数に文字列を入れられても、名前に「数」が付いている理由です。

最後に

次に学習する記事としてオススメするのは、「JavaScriptのデータ型について」です。

本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。

最後までお読みいただき、ありがとうございました。

『詳説 JavaScript』メニューに戻る

コメント

タイトルとURLをコピーしました