0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

この記事の意図

thisが何か分からなくなるので、忘れないようにメモ

JavaScriptのthisってなに

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

呼び出す場所で変わるってどういうこと

下の例だと、一方はhelloと出力され、もう一方はundefinedになる。

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なんだー」って時は、実際にthisそのものをコンソールに出力 して調べてみる。

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オブジェクト

↓こんな風に何気なく使っている関数は

setTimeout(xxxxx, 1000);

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

window.setTimeout(xxxxx, 1000);

thisの特性

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

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

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に入れる値を設定するための関数です。

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

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をつかいました)
下記のように書かれるのが一般的です。

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()を使わない書き方です。

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.paramthisに、btnオブジェクトが入ってしまってるからです。(実際にconsole.log(this)して確認すればわかります)

index.html
<button id="btn">ボタン</button>
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と出力されるはずです。

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をちまちま確認していくのが近道なのかな。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?