サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

TypeScript入門 07: ブロックスコープに変数を宣言する ー let


let宣言は、変数をブロックスコープの{}の中に定めます。変数のvar宣言との違いを、ふたつの例でご説明します。

01 for文でカウンタ変数を参照する例

つぎのTypeScriptコードは、for文の中でwindow.setTimeout()メソッドを定めます。時間差でconsole.log()により、カウンタ変数(i)の値が順にコンソールに示されるはずです。


for (var i: number = 0; i < 5; i++) {
	setTimeout(function(): void {
		console.log(i);
	}, 100 * i);
}
console.log("i = " + i);

このようなコードを書いた場合、0から始まる整数連番が出てくることを期待しているでしょう。ところが、つぎのように値はすべて同じ整数になります。window.setTimeout()メソッドの第1引数に与えたコールバック関数が呼び出されたとき、すべて同じグローバル変数(i)の値を参照するからです。


i = 5
5
5
5
5
5

これは、var宣言がブロックスコープをもたないことによります。けれど、関数の本体でvar宣言した変数や引数はローカルで扱われ、グローバル変数とは区別されます。そこで、つぎのようにfor文の中で即時関数(名前のない関数を直ちに呼び出す)を定めて、引数(i)をwindow.setTimeout()メソッドのコールバックに参照させます。こうすれば、関数の呼び出しごとに引数が別扱いになります。コンソールには、以下のように0から始まる連番整数が示されます。


for (var i: number = 0; i < 5; i++) {
	(function(i: number): void {
		setTimeout(function(): void {
			console.log(i);
		}, 100 * i);
	})(i);
}


0
1
2
3
4

カウンタ変数(i)をつぎのようにlet宣言すれば、for文のブロックスコープの{}の中はループするごとに別の空間として扱われます。したがって、コンソールには上記とおなじ整数連番が表れます。


// for (var i: number = 0; i < 5; i++) {
for (let i: number = 0; i < 5; i++) {
	setTimeout(function(): void {
		console.log(i);
	}, 100 * i);
}

02 入れ子のfor文で同じカウンタ変数を使う例

まず、数値の配列をつくりましょう。その場合のTypeScriptの型づけは、number[]です。ここでは、2次元の入れ子配列にするので(「多次元配列」参照)、つぎのようなコードを書きます。親配列の中に子配列を納めることにより、2次元の行列が表せます。


var matrix: number[][] = [
	[1, 2, 3],
	[4, 5, 6],
	[7, 8, 9]
]

この2次元配列のエレメント(行列成分)すべてを足し合わせましょう。その関数(sumMatrix())をつぎのように定めて、引数には上記の2次元配列を渡したとします。すると、初めの子配列([1, 2, 3])の合計(6)しか得られません。


console.log(sumMatrix(matrix));  // 6

function sumMatrix(matrix: number[][]): number {
	var sum: number = 0;
	for (var i: number = 0; i < matrix.length; i++) {
		var currentRow: number[] = matrix[i];
		for (var i:number = 0; i < currentRow.length; i++) {
			sum += currentRow[i];
		}
	}
	return sum;
}

入れ子のfor文で親と同じ名前のカウンタ変数(i)をvar宣言しました。ところが、ブロックスコープがありませんので、同じ変数として扱われてしまうのです。その結果、つぎのコードのような処理になります。初めの子配列(currentRow)を子のfor文が処理し終えたときに、カウンタ変数(i)の値は親forループの継続条件(i < matrix.length)を外れてしまうのです。


var sum: number = 0;
var i: number = 0;
var currentRow: number[] = matrix[i];
for (i = 0; i < currentRow.length; i++) {
	sum += currentRow[i];
}
console.log(i);  // 3
console.log(sum);  // 6

宣言varを以下のようにすべてletに書き替えれば、{}プロックごとにスコープは分けられ、正しく2次元配列エレメントの合計が得られます。関数の中でひとつしか宣言のない変数(sumとcurrentRow)は、varを用いても構いません。ただ、let宣言なら、ブロックの外との重複など気にしなくて済みます。あえてvar宣言を使わなければならない場合はほとんどないでしょう。


console.log(sumMatrix(matrix));  // 45

function sumMatrix(matrix: number[][]): number {
	// var sum: number = 0;
	let sum: number = 0;
	// for (var i: number = 0; i < matrix.length; i++) {
	for (let i: number = 0; i < matrix.length; i++) {
		// var currentRow: number[] = matrix[i];
		let currentRow: number[] = matrix[i];
		// for (var i:number = 0; i < currentRow.length; i++) {
		for (let i:number = 0; i < currentRow.length; i++) {
			sum += currentRow[i];
		}
	}
	return sum;
}

03 宣言constとlet

const宣言もブロックスコープをもちます。let宣言との違いは、定数(変数)値が書き替えられないことです。したがって、値を書き替えるならlet、変わらない値を使い続けるならconstとすれば、コードが見やすくなります。

ただし、気をつけていただきたいのは、const宣言した変数は書き替えられなくても、参照するオブジェクトのプロパティ値は変えられるということです。つぎのコードはconst宣言した変数に納めたオブジェクトのプロパティ値をエラーなく変更しています。


const zero: number[] = [0];
const point: {x: number[], y: number} = {x: zero, y: 1}
point.x[0]++;
point.y = Math.sqrt(3);
console.log(point.x, point.y, zero);  // [ 1 ] 1.7320508075688772 [ 1 ]


作成者: 野中文雄
更新日: 2016年9月23日 TypeScriptのバージョンを2.0.3に更新。
作成日: 2016年9月14日


Copyright © 2001-2016 Fumio Nonaka.  All rights reserved.