2022/03/25
【Javascript】var let const の使い分けとObjectの凍結・封印
ES6以降、JavaScriptでは変数・定数の宣言に var, let, const の3種類を使い分けることができます。
私はグローバルはvar、ブロック(関数やfor文など { } で囲んだコード)内は let, const くらいの使い分けでしたが、ちゃんとした使い分けではないので、使い分けについて整理してみました。
var
ES6より以前から使える制限のない変数宣言の構文です。
MDNの説明では「関数スコープまたはグローバルの変数を宣言」とありました。
var を使った場合の変数のふるまいとして特徴的なのは、
- 一度グローバルで宣言した場合、関数を除くブロックスコープ内で再度varにより宣言してもグローバルに適用される
- 値の再代入に制限がない
- スコープ内において宣言の巻き上げが起こる
という点です。
一度グローバルで宣言した場合、関数を除くブロックスコープ内で再度varにより宣言してもグローバルに適用される
1 2 3 4 5 6 | var x = 1; if (x === 1) { var x = 2; console.log(x); // output: 2 } console.log(x); // output: 2; |
グローバル領域でvarによる変数を行った場合、if文などのブロックスコープ内で再宣言を行っても、ブロックスコープの外でその変数を呼び出すと、ブロックスコープ内で再宣言した値が返ってきます。
値の再代入に制限がない
1 2 3 4 | var x = 1; x = 3; // no error x = { hoge: 'fuga' }; // no error x = [1, 2, 3] // no error |
varによって宣言した変数にはどのような値でも再代入することができます。
スコープ内において宣言の巻き上げが起こる
1 2 3 | console.log(a); // output: undefined var a = 1; console.log(a); // output: 1 |
var で宣言した変数は、同じスコープ内で宣言よりも前に呼び出した場合もエラーにはならず、undefined が返ってきます。
これは、varによる宣言は、スコープ内の先頭で宣言を行ったことになるためです。ただし、値の代入は宣言した場所で行われるので、宣言を行うより前で呼び出すと、空の値である undefined が返ってきます。
上のコードは以下のコードと同じように解釈されます。
1 2 3 4 | var a; console.log(a); // output: undefined a = 1; console.log(a); // output: 1 |
let
ES6以降で使える変数宣言の構文です。
MDNの説明では「ブロックスコープのローカル変数を宣言」とありました。
let を使った変数のふるまいとして特徴的なのは、
- ブロックスコープの中での宣言は、ブロックスコープの外で宣言した同名の変数と関連しない
- 値の再代入に制限がない
- スコープ内において宣言の巻き上げは起こらない
という点です。
ブロックスコープの中での宣言は、ブロックスコープの外に影響を与えない
1 2 3 4 5 6 7 | let x = 1; if (x === 1) { console.log(x); // Error let x = 2; console.log(x); // output: 2 } console.log(x); // output: 1; |
var の場合は関数の場合のみ、スコープの中での宣言がスコープの外に影響を与えない特徴がありますが、
letにおいてはif文やfor文などのブロックスコープでもスコープの外に影響を与えません。
また、影響を与えないだけでなく、ブロックスコープ内でブロックスコープの外で宣言した同名の変数を宣言すると、
ブロックスコープの外で宣言した宣言した変数はブロックスコープ内では無かったことになり、ブロックスコープ内で宣言するより前にその変数を呼び出そうとすると、宣言していない変数を呼び出そうとしたことになり、Error となります。
値の再代入に制限がない
letはvarと同様に、値の再代入に制限がありません。
1 2 3 4 | let x = 1; x = 3; // No error x = { hoge: 'fuga' }; // No error x = [1, 2, 3] // No error |
スコープ内において宣言の巻き上げは起こらない
1 2 | console.log(a); // Error let a = 1; |
let の場合は var の場合と異なり、スコープ内で宣言より前に呼び出そうとすると Error になります。
const
ES6以降で使える定数宣言の構文です。
MDNの説明では「let キーワードを使って定義する変数と同様にブロックスコープを持ちます。定数の値は、再代入による変更ができず、再宣言もできません」とありました。
const は var や let と異なり、定数を宣言するための構文です。
ただし、定数としての性質以外は let と共通です。
- ブロックスコープの中での宣言は、ブロックスコープの外で宣言した同名の変数と関連しない
- 値の再代入はできない(ただし、配列やオブジェクトの内容変更は可能)
- スコープ内において宣言の巻き上げは起こらない
ブロックスコープ内外のふるまいと、巻き上げについては let と同様なので割愛します。
値の再代入はできない(ただし、配列やオブジェクトの内容変更は可能)
1 2 3 4 | const x = 1; x = 3; // Error const y = { hoge: 'abc' }; y = { fuga: 'def' }; // Error; |
const で宣言した定数は、いかなる値の再代入もできません。
ただ、注意が必要なのは、配列やオブジェクトの内容を変更すくことは可能であるという点です。
1 2 3 4 5 6 7 | const x = { hoge: 'abc' }; x.hoge = 'def'; // No error x.fuga = 'ghi'; // No error; console.log(x); // output: { hoge: 'def', fuga: 'ghi' }; const y = [1, 2, 3]; y.push(4); // No error; console.log(y); // output: [1, 2, 3, 4]; |
配列やオブジェクトの型だけでなく内容の変更も固定したい場合、const による制限だけでは不十分です。
そこで、配列やオブジェクトの内容を固定したい場合は、Object.freeze() や Object.seel() を使います。
Object.freeze() オブジェクトを完全に固定(凍結)する
1 2 3 4 5 6 7 8 | const x = { hoge: 'abc' }; Object.freeze(x); x.hoge = 'def'; // No error x.fuga = 'ghi'; // No error; console.log(x); // output: { hoge: 'abc' }; const y = [1, 2, 3]; Object.freeze(y); y.push(4); // Error |
Object.freeze() を使うと、const で宣言した配列やオブジェクトのプロパティや値を完全に固定することができます。
配列の場合、Object.freeze()で凍結した後、.push() などで値を追加しようとしても Error となります。
オブジェクトの場合は、新たにプロパティを追加したり、値を変更しようとしても、Error は出ませんが、内容を変更することはできません。
ただし、Object.freeze() で凍結できるのは、凍結したオブジェクトの直属のプロパティだけなので、更に下層のプロパティは変更できてしまうので、注意が必要です。
Object.seal() オブジェクトのプロパティの変更を固定(封印)する
1 2 3 4 5 6 7 8 9 10 | const x = { hoge: 'abc' }; Object.seal(x); x.hoge = 'def'; // No error x.fuga = 'ghi'; // No error; console.log(x); // output: { hoge: 'def' }; const y = [1, 2, 3]; Object.seal(y); y[0] = 2; // No error; console.log(y); // output: [2, 2, 3] y.push(4); // Error |
Object.seal() を使うと、const で宣言した配列やオブジェクトのプロパティを固定することができます。
配列の場合は、Object.seal() で封印した後、配列の各値を変更することは可能ですが、.push() などで要素の数を変更しようとすると Error となります。
オブジェクトの場合は、各プロパティの値を変更することはできますが、新たにプロパティを追加しようとしても、Error にはなりませんが、追加はできません。
Object.seal() も Object.freeze() 同様に、封印できるのは封印したオブジェクトの直属のプロパティのみです。
[おまけ] TypeScriptにおける定数の読み取り専用指定
ちなみに TypeScript の場合は as const というアサーションを指定することで、すべての値を読み取り専用(readonly)にすることができるため、これらの処理は必要ありません。
1 2 | const x = { hoge: 'abc' } as const; x.hoge = 'def'; // Error |
特定のプロパティを読み取り専用にする場合は、読み取り専用にしたいプロパティの型宣言の前に readonly を指定します。
1 2 3 4 5 6 7 | type HogeFuga = { hoge: string; fuga: readonly string; } const x: HogeFuga = { hoge: 'abc', fuga: 'def' }; x.hoge = 'def'; // No error x.fuga = 'ghe'; // Error |
参考にさせていただいたサイト
Author Profile
NINOMIYA
Webデザイナー兼コーダー出身のフロントエンド開発者です。 UXデザインやチーム開発の効率化など、勉強中です。
SHARE