吉本集の個人ブログ
Web制作の技術について書いています。たまに日記も書きます。

【JS入門】ブロックスコープについて(let, const)

2018年1月17日 / category : javascript

前回の記事で次回は演算子について解説するという終わり方をしましたが、演算子とその次に考えていたループ文に関しては、MDNサイトの次のページを参考にしていただければと思います。

演算子
ループ文

このブログでも途中まで書いていたのですが、上の参考ページと同じ内容になるので、出来上がっている記事を見た方が早いかと思い、、、、やめました。ただ、プログラムでは大事な知識ですので、しっかり理解してください。
今回の記事では、以前の記事で少し触れたES6から変数を宣言する際に使用するlet,constという新しい機能について解説してみたいと思います。

let

letの解説には、varと比較して解説するとわかりやすいかもしれません。varで宣言すると、宣言した場所によってグローバル変数にもローカル変数にもなります。例えば、次のようなコードを見てみます。

sample.js
var a = 100;
function func(){
	var b = 100;
	c = 100;
	console.log( a, b, c ); //100,100,100
}
func();
console.log( a, c ); //100,100

上のコードを見ると、変数aはグローバル変数、変数bはローカル変数、そしてvar宣言していない変数cはグローバル変数ということが確認できると思います。変数cに関して言い換えると、var宣言しないとグローバル変数になってしまう、と言えます。
それに対し、letは局所変数(ブロックスコープ)となります。では、ブロックスコープ(ブロック範囲)の「ブロック」とはどの範囲になるでしょうか。
ブロックとは、ブロック文というグループ化する場合に使用する構文になります。このブロック文は{}で区切られます。
簡単なブロック文をvar宣言、let宣言した場合、どうなるかを見てみましょう。

sample.js
var a = 100;
let b = 100;
{
	var c = 100;
	let d = 100;
}
console.log( a ); //100
console.log( b ); //100
console.log( c ); //100
console.log( d ); //error:d is not defined

変数dがエラーになります。ブロック文の中でlet宣言した変数をブロックの外から参照しようとしたためです。これがブロックスコープとなります。
もしかするとvar宣言のほうが使い勝手が良いと感じるかもしれませんが、今後はlet宣言を推奨します。大きな理由として、ブロックスコープにすることでさらに範囲が限定的になり、意図しないところで参照できてしまった、というケースを防げます。そのケースを見てみましょう。

sample.js
function func(){
	var a = 100;
	if(true){
		var a = 200;
		console.log( a ); //200
	}
	console.log( a ); //200
}
func();

上のコードを見ると、変数aには共に200が代入されています。例えばif文の外で定義した変数aとif文の中で定義した変数aを違う変数として扱いたい(または誤まって同じ変数名にしてしまった)という意図があった場合、この結果は意図しない結果だったことになります。このケースをlet宣言していたらどういう結果になっていたでしょうか。

sample.js
function func(){
	var a = 100;
	if(true){
		var a = 200;
		console.log( a ); //200
	}
	console.log( a ); //100
}
func();

どうでしょうか、先ほどと結果が違います。これが本来期待していた結果です。そして、これがlet宣言を推奨する理由となります。
それでは、次のコードを見てみます。

sample.js
a = 100;
var a;
console.log( a ); //100

上の結果を見ると100が取得できています。コードを見るとどうでしょうか。宣言していない変数aに100を代入してから変数aをvar宣言しています。
これは本来エラーになりそうです。これは「varの巻き上げ」と呼ばれ、動作的には正しい処理になります。これをlet宣言してみます。

sample.js
a = 100;
let a;
console.log( a ); //error:a is not defined

let宣言にすると、エラーになることが確認できました。コードを見るとエラーになることが自然に思えます。またここで気付く方もいるかもしれませんが、var宣言だけした変数は、undefinedの値が入りますが、let宣言しただけの変数は、undefinedの値が入らず、not definedになります。
続けて、次のコードを見てみます。

sample.js
var a = 100;
let b = 100;
console.log( a ); //100
console.log( b ); //100

上のコードの結果はとてもシンプルで、想像できた結果です。この記述を関数の中ではなく、トップレベルに記述していることに注目し、次のように変更してみます。

sample.js
var a = 100;
let b = 100;
console.log( this.a ); //100
console.log( this.b ); //undefined

変数を参照する際に、thisをつけて参照しています。このthisについては今後別のタイミングで解説したいと思いますが、let宣言ではトップレベルでthisをつけて参照するとundefinedになるという結果になりました。言い換えると、let宣言はトップレベル(グローバルオブジェクト)でプロパティを生成しない、ということになります。
以上が、letの解説になります。続いてconstについて解説していきます。

const

constは、値を変更できない定数です。また、再宣言もできません。スコープはlet宣言と同様、ブロックスコープになります。では、エラーになる2つのケースを見てみます。

sample.js
const a = 100;
a = 200; //error : Assignment to constant variable.
sample.js
const a = 100;
const a = 200; //error : Identifier 'a' has already been declared

最初のコードが、再代入のエラー。2つ目のコードが、再宣言のエラーになります。その他にもエラーになるケースがありますので、いくつか紹介します。

sample.js
const a; //error : 初期値が代入されていない
sample.js
const a;
var a; //error : constで既に宣言されている
let a; //error : constで既に宣言されている
sample.js
const a = { 'b': 100 };
a = { 'c': 100 }; //error : オブジェクトの再代入
sample.js
const a = ['A'];
a = ['B']; //error : 配列の再代入

続いて、許可されているケースをいくつか紹介します。

sample.js
const a = 100;
{
	const a = 100; //ブロックスコープなので、問題ありません
}
sample.js
const a = {'b': 100};
a.b = 200; //プロパティの値の変更は、問題ありません
sample.js
const a = [];
a.push(100); //配列に要素を追加することは、問題ありません

以上が、constの解説になります。
let、constともにプログラムを難しくさせているように感じるかもしれませんが、プログラムを厳密化することのメリットのほうが大きいです。
ES6の環境では、var宣言ではなく、let宣言、const宣言を多用するようにしましょう。