HTML5テクニカルノート
TypeScript入門 07: ブロックスコープに変数を宣言する ー let
- ID: FN1609008
- Technique: HTML5 / JavaScript
- Package: TypeScript 2.0.3
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次元の入れ子配列にするので(「多次元配列」参照)、つぎのようなコードを書きます。親配列の中に子配列を納めることにより、多次元の行列が表せます。
var matrix: number[][] = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]
この入れ子配列のエレメント(行列成分)すべてを足し合わせるとします。その関数(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 ]
- TypeScript: とにかくJavaScriptファイルをビルドしてみる
- TypeScript入門 01: Visual Studio CodeでTypeScriptのコードを書く
- TypeScript入門 02: publicとprivateおよびstatic
- TypeScript入門 03: クラスを継承して使う
- TypeScript入門 04: オブジェクト型リテラルとインタフェースを使う
- TypeScript入門 05: get/setアクセサをを使う
- TypeScript入門 06: メソッド引数のデフォルト値と省略および定数を定める
- TypeScript入門 08: 型の互換性
- TypeScript入門 09: アロー関数式
- TypeScript入門 10: モジュール ー exportとimport
- TypeScript入門 11: 名前空間 ー namespace
- TypeScript入門 12: デコレータ(Decorator)を使う
作成者: 野中文雄
更新日: 2016年9月23日 TypeScriptのバージョンを2.0.3に更新。
作成日: 2016年9月14日
Copyright © 2001-2017 Fumio Nonaka. All rights reserved.