以下の要約:
https://www.typescriptlang.org/docs/handbook/variable-declarations.html
TypeScript の変数宣言
- 変数宣言の種類
-
var
: 昔ながらの記法 -
let
: 新しい記法、var
の落とし穴を回避 -
const
:let
の再代入を不可にしたもの
-
-
var
よりもlet
とconst
を使うべき
var
宣言
var
宣言
var a = 10;
- 関数の中から外の変数にもアクセスできる
function f() {
var a = 10;
return function g() {
var b = a + 1;
return b;
}
}
var g = f();
g(); // returns '11'
変数値が決まるタイミング
- 関数
f
がreturn
した後に関数g
を宣言しても、a
はg
の呼び出し時の値になる
function f() {
var a = 1;
a = 2;
var b = g();
a = 3;
return b;
function g() {
return a;
}
}
f(); // returns '2'
var
の気持ち悪い変数スコープ
- スコープはブロックに関係なく、
function
/module
/namespace
/ グローバルいずれかの全体になる!
function f(shouldInitialize: boolean) {
if (shouldInitialize) {
var x = 10;
}
return x;
}
f(true); // returns '10'
f(false); // returns 'undefined'
事態は悪化!
- 同じ変数名を何度も宣言できてしまう…
-
i
の値は何…?
function sumMatrix(matrix: number[][]) {
var sum = 0;
for (var i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (var i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
var
変数値の落とし穴
- 次の実行結果はどうなるか?
for (var i = 0; i < 10; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
- 答え
-
:
10 10 10 10 10 10 10 10 10 10
-
:
0 1 2 3 4 5 6 7 8 9
-
:
ワークアラウンド
- IIFE (Immediately Invoked Function Expression) を使おう
for (var i = 0; i < 10; i++) {
// capture the current state of 'i'
// by invoking a function with its current value
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
let
宣言
let
宣言
let hello = "Hello!";
-
var
のこれまでの問題点を解決するために導入された
let
の変数スコープ
-
let
はレキシカルスコープ(=ブロックスコープ)
function f(input: boolean) {
let a = 100;
if (input) {
// Still okay to reference 'a'
let b = a + 1;
return b;
}
// Error: 'b' doesn't exist here
return b;
}
宣言の前には変数にアクセスできない
- 何を当たり前のことを…
a++; // illegal to use 'a' before it's declared;
let a;
- 実際には、変数はスコープ全体ですでに「存在」しているが、宣言までの部分は「一時的なデッドゾーン」として扱われる
…どういうこと?
- 宣言前に、変数を「捕捉」することが可能
- イリーガルなのは、「捕捉」した関数を呼び出すことだけ
- ES2015 のモダンなランタイムならエラーを吐くが、TypeScript 自身はこれを許容する
function foo() {
// okay to capture 'a'
return a;
}
// illegal call 'foo' before 'a' is declared
// runtimes should throw an error here
foo();
let a;
再宣言とシャドーイング
-
let
は再宣言を許可しない
let x = 10;
let x = 20; // error: can't re-declare 'x' in the same scope
- こんな例もエラー
function f(x) {
let x = 100; // error: interferes with parameter declaration
}
function g() {
let x = 100;
var x = 100; // error: can't have both declarations of 'x'
}
スコープを分ければ OK
function f(condition, x) {
if (condition) {
let x = 100;
return x;
}
return x;
}
f(false, 0); // returns '0'
f(true, 0); // returns '100'
シャドーイング
- ネストしたスコープで同じ変数名を
let
宣言すること
function sumMatrix(matrix: number[][]) {
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (let i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
ブロックスコープの変数捕捉
- スコープを実行すると、変数の「環境」が作られる
- スコープ終了後も、その環境は存在できる
function theCityThatAlwaysSleeps() {
let getCity;
if (true) {
let city = "Seattle";
getCity = function() {
return city;
}
}
return getCity();
}
IIFE よさらば
- IIFE とは結局、無理やり新しい変数環境を作るトリック
-
for
ループでlet
を使うと、毎回新しいスコープが生成されている!
for (let i = 0; i < 10 ; i++) {
setTimeout(function() { console.log(i); }, 100 * i);
}
const
宣言
const
宣言
const numLivesForCat = 9;
- 一度値を割り当てたら、再割り当てできない
- スコーピング規則は
let
と同じ
immutable ではない
const numLivesForCat = 9;
const kitty = {
name: "Aurora",
numLives: numLivesForCat,
}
// Error
kitty = {
name: "Danielle",
numLives: numLivesForCat
};
// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;
immutable にしたいときは?
-
readonly
を使おう
interface Point {
readonly x: number;
readonly y: number;
}
- 詳細は Interfaces の章で。
let
対 const
let
対 const
-
最小権限の原則に基づけば、可能な限り
const
を使うべき- 不用意な変数の書き換えを防げる
- データフローを追いやすくなる
- 一方で、
let
はvar
と同じくらい字数が少なくて書くのが簡単(え…?)
分割代入
分割代入(Destructuring)
- ECMAScript 2015 の機能の一つ
- 配列やオブジェクトのプロパティから値を取り出して、別の変数に割り当てられる
- 詳細はこちら
配列
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
既存の変数や関数のパラメータにも使える
- 変数
// swap variables
[first, second] = [second, first];
- 関数パラメータ
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f([1, 2]);
高度な使い方
-
...
構文で残りをリストに格納
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
- 要らない要素は無視できる
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
let [, second, , fourth] = [1, 2, 3, 4];
オブジェクト
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
変数宣言なしでも OK
({ a, b } = { a: "baz", b: 101 });
-
( ... );
が必要なことに注意- JavaScript では
{
から始まる文は、ブロックとして解釈されてしまうため
- JavaScript では
- (誰が使うの?)
...
構文も使える
- 残りはオブジェクトとして格納される
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
プロパティのリネーム
let { a: newName1, b: newName2 } = o;
- 以下と同等
let newName1 = o.a;
let newName2 = o.b;
- 型宣言したい場合
let { a, b }: { a: string, b: number } = o;
デフォルト値
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
}
-
b
が未定義の場合b = 1001
になる
関数宣言の分割代入
type C = { a: string, b?: number }
function f({ a, b }: C): void {
// ...
}
デフォルト値の定義は難しい
- オブジェクト自体にデフォルト値を定義
function f({ a, b } = { a: "", b: 0 }): void {
// ...
}
f(); // ok, default to { a: "", b: 0 }
- オプションの
b
にだけデフォルト値を定義
function f({ a, b = 0 } = { a: "" }): void {
// ...
}
f({ a: "yes" }); // ok, default b = 0
f(); // ok, default to { a: "" }, which then defaults b = 0
f({}); // error, 'a' is required if you supply an argument
- 分割代入の使用はシンプルかつ最小限に
スプレッド(Spread)
- 分割代入の反対をやる演算子
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
// => [0, 1, 2, 3, 4, 5]
オブジェクトの場合
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };
// => { food: "rich", price: "$$", ambiance: "noisy" }
- 後から出てきた値に上書きされる
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults };
// => { food: "spicy", price: "$$", ambiance: "noisy" }
オブジェクトスプレッドの制約
- オブジェクトが「所有する、列挙可能な」プロパティしか引き継がない
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!