TypeScript 入門：変数宣言

以下の要約：
https://www.typescriptlang.org/docs/handbook/variable-declarations.html

---

# TypeScript の変数宣言

- 変数宣言の種類
    - `var` : 昔ながらの記法
    - `let` : 新しい記法、`var` の落とし穴を回避
    - `const` : `let` の再代入を不可にしたもの
- `var` よりも `let` と `const` を使うべき

---

# `var` 宣言

---

## `var` 宣言

```ts
var a = 10;
```

- 関数の中から外の変数にもアクセスできる

```ts
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` の呼び出し時の値になる

```ts
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` / グローバルいずれかの全体になる！

```ts
function f(shouldInitialize: boolean) {
    if (shouldInitialize) {
        var x = 10;
    }

    return x;
}

f(true);  // returns '10'
f(false); // returns 'undefined'
```

---

### 事態は悪化！

- 同じ変数名を何度も宣言できてしまう…
- `i` の値は何…？

```ts
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` 変数値の落とし穴

- 次の実行結果はどうなるか？

```ts
for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}
```

- 答え
    - [x] : `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*) を使おう

```ts
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` 宣言

```ts
let hello = "Hello!";
```

- `var` のこれまでの問題点を解決するために導入された

---

## `let` の変数スコープ

- `let` はレキシカルスコープ（＝ブロックスコープ）

```ts
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;
}
```

---

### 宣言の前には変数にアクセスできない

- 何を当たり前のことを…

```ts
a++; // illegal to use 'a' before it's declared;
let a;
```

- 実際には、変数はスコープ全体ですでに「存在」しているが、宣言までの部分は「一時的なデッドゾーン」として扱われる

---

### …どういうこと？

- 宣言前に、変数を「捕捉」することが可能
    - イリーガルなのは、「捕捉」した関数を呼び出すことだけ
    - ES2015 のモダンなランタイムならエラーを吐くが、TypeScript 自身はこれを許容する

```ts
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` は再宣言を許可しない

```ts
let x = 10;
let x = 20; // error: can't re-declare 'x' in the same scope
```

- こんな例もエラー

```ts
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

```ts
function f(condition, x) {
    if (condition) {
        let x = 100;
        return x;
    }

    return x;
}

f(false, 0); // returns '0'
f(true, 0);  // returns '100'
```

---

### シャドーイング

- ネストしたスコープで同じ変数名を `let` 宣言すること

```ts
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;
}
```

---

## ブロックスコープの変数捕捉

- スコープを実行すると、変数の「環境」が作られる
- スコープ終了後も、その環境は存在できる

```ts
function theCityThatAlwaysSleeps() {
    let getCity;

    if (true) {
        let city = "Seattle";
        getCity = function() {
            return city;
        }
    }

    return getCity();
}
```

---

### IIFE よさらば

- IIFE とは結局、無理やり新しい変数環境を作るトリック
- `for` ループで `let` を使うと、毎回新しいスコープが生成されている！

```ts
for (let i = 0; i < 10 ; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}
```

---

# `const` 宣言

---

## `const` 宣言

```ts
const numLivesForCat = 9;
```

- 一度値を割り当てたら、再割り当てできない
- スコーピング規則は `let` と同じ

---

### *immutable* ではない

```ts
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` を使おう

```ts
interface Point {
    readonly x: number;
    readonly y: number;
}
```

- 詳細は [Interfaces](https://www.typescriptlang.org/docs/handbook/interfaces.html) の章で。

---

# `let` 対 `const`

---

## `let` 対 `const`

- [最小権限の原則](https://ja.wikipedia.org/wiki/%E6%9C%80%E5%B0%8F%E6%A8%A9%E9%99%90%E3%81%AE%E5%8E%9F%E5%89%87)に基づけば、可能な限り `const` を使うべき
    - 不用意な変数の書き換えを防げる
    - データフローを追いやすくなる
- 一方で、`let` は `var` と同じくらい字数が少なくて書くのが簡単（え…？）

---

# 分割代入

---

## 分割代入（Destructuring）

- ECMAScript 2015 の機能の一つ
    - 配列やオブジェクトのプロパティから値を取り出して、別の変数に割り当てられる
- 詳細はこちら
    - https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

---

## 配列

```ts
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
```

---

### 既存の変数や関数のパラメータにも使える

- 変数

```ts
// swap variables
[first, second] = [second, first];
```

- 関数パラメータ

```ts
function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f([1, 2]);
```

---

### 高度な使い方

- `...` 構文で残りをリストに格納

```ts
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
```

- 要らない要素は無視できる

```ts
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
```

```ts
let [, second, , fourth] = [1, 2, 3, 4];
```

---

## オブジェクト

```ts
let o = {
    a: "foo",
    b: 12,
    c: "bar"
};
let { a, b } = o;
```

---

### 変数宣言なしでも OK

```ts
({ a, b } = { a: "baz", b: 101 });
```

- `( ... );` が必要なことに注意
    - JavaScript では `{` から始まる文は、ブロックとして解釈されてしまうため
- （誰が使うの？）

---

### `...` 構文も使える

- 残りはオブジェクトとして格納される

```ts
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
```

---

### プロパティのリネーム

```ts
let { a: newName1, b: newName2 } = o;
```

- 以下と同等

```ts
let newName1 = o.a;
let newName2 = o.b;
```

- 型宣言したい場合

```ts
let { a, b }: { a: string, b: number } = o;
```

---

### デフォルト値

```ts
function keepWholeObject(wholeObject: { a: string, b?: number }) {
    let { a, b = 1001 } = wholeObject;
}
```

- `b` が未定義の場合 `b = 1001` になる

---

## 関数宣言の分割代入

```ts
type C = { a: string, b?: number }
function f({ a, b }: C): void {
    // ...
}
```

---

### デフォルト値の定義は難しい

- オブジェクト自体にデフォルト値を定義

```ts
function f({ a, b } = { a: "", b: 0 }): void {
    // ...
}
f(); // ok, default to { a: "", b: 0 }
```

- オプションの `b` にだけデフォルト値を定義

```ts
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）

- 分割代入の反対をやる演算子

```ts
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
// => [0, 1, 2, 3, 4, 5]
```

---

### オブジェクトの場合

```ts
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };
// => { food: "rich", price: "$$", ambiance: "noisy" }
```

- 後から出てきた値に上書きされる

```ts
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults };
// => { food: "spicy", price: "$$", ambiance: "noisy" }
```

---

### オブジェクトスプレッドの制約

- オブジェクトが[「所有する、列挙可能な」プロパティ](https://developer.mozilla.org/ja/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)しか引き継がない

```ts
class C {
  p = 12;
  m() {
  }
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
```

---

## End