【備忘】【JavaScript】動かしながらthisについて理解する

# この記事の意図
thisが何か分からなくなるので、忘れないようにメモ

# JavaScriptのthisってなに
thisは、呼び出す場所でころころ変わる特殊なオブジェクト

# 呼び出す場所で変わるってどういうこと
下の例だと、一方はhelloと出力され、もう一方はundefinedになる。

```js
class Example {
    constructor(param) {
        this.param = param
        console.log(this.param);
    }
    lateHello() {
        setTimeout(function() {
            console.log(this.param);
        });
    }
}

const example = new Example('hello'); // →　hello
example.lateHello(); // →　undefined
```

「なぜlateHello()だと、this.paramがundefinedなんだー」って時は、<b>実際にthisそのものをコンソールに出力</b> して調べてみる。

```js
class Example {
    constructor(param) {
        this.param = param
        // thisを出力
        console.log(this);
    }
    lateHello() {
        setTimeout(function() {
            // thisを出力
            console.log(this);
        });
    }
}

const example = new Example('hello'); // →　Example {param: "hello"}
example.lateHello(); // →　Window {0: Window, window: Window, self: Window, document: document, name: "", location: Location, …}
```

上記の結果から、thisが参照しているところが違ってたから、lateHello()の方は、this.paramがundefinedになっていたことがわかる。

じゃあ、この `window` ってなんだ？

# windowはグローバルオブジェクトといいます
ブラウザの開発者ツールでコンソールを開いてthisと打って実行すると
`Window {0: Window, window: Window, self: Window, document: document, name: "", location: Location, …}`
と表示されます。これがグローバルオブジェクト。
> グローバルオブジェクトは、グローバルスコープ上に常時存在するオブジェクトです。
https://developer.mozilla.org/ja/docs/Glossary/Global_object

window（グローバルオブジェクト）には、たくさんの便利な関数やプロパティが入っていて、JavaScriptを使う時にみんなお世話になっています。

そして、そんな関数の一つに `setTimeout()'があります。

# 実はお世話になっているwindowオブジェクト
↓こんな風に何気なく使っている関数は

```js
setTimeout(xxxxx, 1000);
```

↓実はこれの省略形でした。

```js
window.setTimeout(xxxxx, 1000);
```

# thisの特性
基本的にthisは<b>「直近で囲まれているオブジェクトを参照する」</b>という特性を持つ。

最初の例に戻ります。
`setTimeout`は、実は`window.setTimeout`の省略形であることがわかったので、下記のように書き直します。

```js
class Example {
    constructor(param) {
        this.param = param
        // thisを出力
        console.log(this);
    }
    lateHello() {
        // thisの直近のオブジェクトはwindowになっている
        window.setTimeout(function() {
            // thisを出力
            console.log(this);
        });
    }
}

const example = new Example('hello'); // →　Example {param: "hello"}
example.lateHello(); // →　Window {0: Window, window: Window, self: Window, document: document, name: "", location: Location, …}
```

すると、lateHelloのsetTimeout内の直近のオブジェクトは`window`になっています。
だから、thisがwindowオブジェクトに一致しています。

# thisをコロコロ変えない方法
thisに意図しないオブジェクトが入ることはよくあります。
なので、thisを思い通りにコントロールする必要があります。

その手段はいくつかすでに用意されているので、その一部を抜粋。

## 手段①：bind(this)で束縛する
よく使われる手法です。
bind()は、thisに入れる値を設定するための関数です。

こんなかんじで使います。

```js
class Example {
    constructor(param) {
        this.param = param
        // thisを出力
        console.log(this);
    }
    lateHello() {
        const _self = this; // ここでは、thisはExampleオブジェクト自身を参照している

        // thisの直近のオブジェクトはwindowになっている
        window.setTimeout(function() {
            // thisを出力
            console.log(this);
        }).bind(_self); // bindした_selfには、window.setTimeoutの直前のthisと同じオブジェクトが入っており、setTimeout内のthisを_selfに束縛する
    }
}

const example = new Example('hello'); // →　Example {param: "hello"}
example.lateHello(); // →　Example {param: "hello"}
```

ただこの書き方は、冗長な書き方です（分かりやすいので、_selfをつかいました）
下記のように書かれるのが一般的です。

```js
class Example {
    constructor(param) {
        this.param = param
        // thisを出力
        console.log(this);
    }
    lateHello() {
        window.setTimeout(function() {
            // thisを出力
            console.log(this);
        }).bind(this); // bindしたthisには、window.setTimeoutの直前のthisと同じオブジェクトが入っている
    }
}

const example = new Example('hello'); // →　Example {param: "hello"}
example.lateHello(); // →　Example {param: "hello"}
```

## 手段②：thisを変数に格納して使う
`bind()`を使わない書き方です。

```js
class Example {
    constructor(param) {
        this.param = param
        // thisを出力
        console.log(this);
    }
    lateHello() {
        const _self = this;
        window.setTimeout(function() {
            console.log(_self);
        });
    }
}

const example = new Example('hello'); // →　Example {param: "hello"}
example.lateHello(); // →　Example {param: "hello"}
```

この方が直感的でわかりやすいです。
ただ、bind()を使っている人は多いと思うので、どちらでも書けるようにしておくのがいいと思います。

# 実際にbind()を使う具体的な例
ボタンをクリックしたら、コンソールに`hello`と出力させるプログラムです。

下記のコードでは`undefined`となってしまいます。
理由は、`this.param`の`this`に、btnオブジェクトが入ってしまってるからです。（実際にconsole.log(this)して確認すればわかります）

```html:index.html
<button id="btn">ボタン</button>
```

```js:main.js
class Hello {
    constructor(param) {
        this.hello = param;
    }
    sayHello() {
        console.log(this.param)
    }
}

const hello = new Hello('hello');

// ボタン要素をセレクト
const btn = document.querySelector('#btn');
// NG例：クリックしたらhelloとコンソールに出力する
btn.addEventListener('click', hello.sayHello()); // → undefined 
```

これを下記のように書き直すときちんと`hello`と出力されるはずです。

```js:main.js
class Hello {
    constructor(param) {
        this.hello = param;
    }
    sayHello() {
        console.log(this.param)
    }
}

const hello = new Hello('hello');

// ボタン要素をセレクト
const btn = document.querySelector('#btn');
// helloオブジェクトをbindする
btn.addEventListener('click', hello.sayHello().bind(hello)); // → hello
```

他にもthisを制御する方法はあると思いますが、ここら辺でおわり。

# おわりに
だいぶthisについて理解できた。
ただコードが複雑になれば「あれ、動かん。。。」ってなると思うから、プロジェクトを通して、コードに触れるのが大事なんだろうなと思います。

けどそういう時はあわてず、コンソールとかで現在のthisをちまちま確認していくのが近道なのかな。