この記事の意図
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.param
のthis
に、btnオブジェクトが入ってしまってるからです。(実際にconsole.log(this)して確認すればわかります)
<button id="btn">ボタン</button>
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
と出力されるはずです。
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をちまちま確認していくのが近道なのかな。