typescript、javascriptにおけるスコープについて（関数とクラスの違い）

# スコープとは
 - 変数、関数、クラスによって外部からアクセスできる範囲を意味する。
 - スコープには作りによって、関数スコープとブロックスコープ、場所によってはグローバルスコープとローカルスコープに分けられる。


# スコープのルール
- 外側のスコープをグローバルスコープ、それ以外はローカルスコープという。
- ローカルスコープからグローバルスコープへのアクセスはできるが、逆はできない。
- グローバルスコープよりローカルスコープの方が優先順位は高い。
- 矢印関数は関数スコープではなく、ブロックスコープ扱いになる。



# 例文
まずは以下のコードを実行するとどうなるでしょう。

~~~javascript:javascript
function count() {
    for (var i = 1; i < 10; i++) {
        setTimeout(() => {
            console.log(i)
        }, i*100)
    }
}
count()
~~~
このコードを実行すると、1, 2, 3, ... 9を0.1秒間隔で出力すると思うけど、実は10が９回出力されます。
なぜこのような結果になったのでしょうか。
また、1, 2, 3, ... 9を0.1秒間隔で出力させるためにはどのように修正すれば良いでしょうか。
これを説明するためにはこれから説明するスコープについて知る必要があります。

# 関数スコープとブロックスコープ
### 関数スコープ
~~~javascript:javascript
function someFn() {
    if (true) {
        var text = 'foo';
    }
    console.log(text); // foo
}
someFn();
~~~
上記のコードを実行すると、`foo`が出力されます。
その理由としては、`var`で宣言された変数は関数レベルのスコープを持っつことになります。
つまり、`if`文を包む関数内の先頭にホイスティングされるため、`if`文の外で`name`変数を使おうとしてもエラーにならないでしょう。
このように`{}`で囲んだブロック内記述したとしても、ホイスティングされて`{}`で囲んだブロックの外に宣言されることを関数スコープといいます。
なお、`let`や`const`で宣言された場合はどうなるでしょうか。

### ブロックスコープ
~~~javascript:javascript
function someFn() {
    if (true) {
        const text = 'foo';
    }
    console.log(text); // [ERR]: "Executed JavaScript Failed:"  // [ERR]: text is not defined
}
someFn();
~~~
上記のコードを実行すると`text`が見つからないとエラーが発生します。
その理由は、先ほどの`var`キーワードとは違って、`let`や`const`で宣言された変数は`if`文の中でのみ参照が可能だからです。
今回のコードですと、`if`文の外で`text`変数を使おうとしていたので、エラーが発生しました。
このように関数ではなく、`{}`で囲んだブロック内で有効な変数を宣言することを`ブロックスコープ`といいます。
では、最初にご紹介しました10が９回出力される関数を1, 2, 3, ... 9を0.1秒間隔で出力するにはどのように変更すべきでしょうか。

# 最初の問題の修正
~~~javascript:javascript
function count() {
    for (var i = 1; i < 10; i++) {
        setTimeout(() => {
            console.log(i)
        }, i*100)
    }
}
count()
~~~

上記のコードからすると、`var`で宣言された`i`は１から１０まで１づつ増加しながら繰り返されることがわかります。
そして`for`文の中には`setTimeout`関数があり、受け取った`i`を0.1秒を掛け算して`i`を出力するようにしました。
しかし、上記のコードだと、`for`文の外に`i`を宣言したことと同様で、`setTimeout`関数が0.1秒を待っている間に、`for`文は1から10までの処理が完了され、`setTimeout`関数が`i`を参照しようとするタイミングでは、`i`は既に`10`になってしまうため、`10`が繰り返し表示されることになります。
これを修正するにはスコープを修正するだけになります。

### 方法１ 新しいスコープを`for`文の中に追加する
~~~javascript:javascript
function count() {
    for (var i = 1; i < 10; i++) {
        (function(num) {
            setTimeout(() => {
            console.log(num)
        }, i*100)
        })(i)
    }
}
count()
~~~
こうすることで、`i`という値を別のスコープである`num`に代入することで、想定した1, 2, 3, ... 9を0.1秒間隔で出力することができます。

### 方法２ ブロックスコープを利用する
~~~javascript:javascript
function count() {
    for (let i = 1; i < 10; i++) {
        setTimeout(() => {
            console.log(i)
        }, i*100)
    }
}
count()
~~~
もう一つの方法としては、`var`で宣言した`i`を`let`に変更するだけです。
これで、`i`は`for`文の中でのみ有効になり、想定した1, 2, 3, ... 9を0.1秒間隔で出力することができます。

# classにおけるスコープ
クラスにおいてのスコープは代表的にprivate、protected、publicを上げることができます。
- private: クラスの内部でのみアクセス可能。継承はできない。
- protected: 継承ができるため、クラス内部及び拡張された子クラスでのみアクセス可能。
- public: 外部からのアクセス可能。継承も可能。

これらを実際に検証してみると以下のコードになります。

### インスタンス化したパターン
~~~typescript:typescript
class Base {
    private num: number
    constructor() {
        this.num = 1
    }
    protected plus() {
        return this.num++
    }
    public minus() {
        return this.num--
    }
}

const base = new Base()
const num = base.num  //num変数にアクセス不可
const plus = base.plus  //plusメソッドにアクセス不可
const minus = base.minus  //minusメソッドにアクセス可能
~~~

クラスBaseをインスタンス化したbaseから、Baseに定義されたplusとminus、また変数numにアクセスしようとすると、publicで定義したminusのみがアクセス可能であることがわかります。
また、継承をしようとすると以下のコードになるかと思います。

### 継承パターン
~~~typescript:typescript
class Child extends Base {
    numPlus() {
        return this.plus()  //親クラスのplusメソッドにアクセス可能
    }

    numMinus() {
        return this.minus()  //親クラスのminusメソッドにアクセス可能
    }

    numReturn() {
        return this.num  //privateに設定されているため、アクセス不可。　エラーが表示。
    }
}
~~~

継承先では、privateで定義したnumにはアクセスができず、その他のprotectedとpublicで定義したplusとminusは継承ができていることがわかります。