【javascript】コールバック関数におけるthis
はじめに
コールバック関数を用いた実装で度々バグが発生しており、仕様について改めて勉強してみた。
コールバック関数のおさらい
まず、コールバック関数とは何かおさらいをする。
コールバック関数とは、たいていの場合ある関数に引数として渡される関数のことを指す。
具体例で言うと以下の通り。
function greeting(name) {
alert(`Hello, ${name}`);
}
function processUserInput(callback) {
const name = prompt("Please enter your name.");
callback(name);
}
processUserInput(greeting);
// ラムダ式や無名関数もOK
processUserInput(function(name) {alert(`Hello, ${name}`);});
processUserInput((name) => {alert(`Hello, ${name}`);});
この例では、processUserInput
の処理内でまずprompt("Please enter your name.");
で名前の入力が行われる。
次にcallback(name);
が処理されるが、このcallback
は引数で受け取った値となるので、greeting(name)
が実行される。
ではここで問題。以下のコードを実行した場合、結果はどうなるだろうか。
class Class1 {
constructor (name) {
this.name = name;
}
exeClbk (callback) {
callback();
}
callEvent () {
this.exeClbk(function(){
console.log(`myNameIs${this.name}`);
});
}
}
let c1 = new Class1('Takashi');
c1.callEvent();
callEvent()
にて、自身のexeClbk
を呼び、「myNameIsTakashi」
を出力するように見えるが、そうはならない。
では何故そうならないかというと、コールバック関数として渡している処理のconsole.log(`myNameIs${this.name}`);
のthis
が、インスタンスを指していないから。
javascriptにおけるthis
私はjavaばかり触っていて全く気にも留めていなかったのだが、javascriptではthisは必ずしもインスタンスのことを指すわけではない。
どうやらjavascriptにおいて、thisはその呼び出し方によって別のオブジェクトになりうるらしい。
細かい仕様は長くなってしまうので、また別の機会に調査してみることにする。
このような意図しない挙動を回避するためには、以下のような書き方をすればよい。
-
無名関数ではなくラムダ式で渡す
ラムダ式は基本的には無名関数の代替構文という認識でよいが、this(他super等も)を束縛しないとのこと。つまり、関数が記述された時点でのthisで処理を行うので、問題を回避できる。 -
bind()
を使用する
百聞は一見に如かず、以下のような書き方をすることでthisを指定できる。
・・・
callEvent () {
this.exeClbk(function(){
console.log(`myNameIs${this.name}`);
}.bind(this));
}
・・・
まとめ
日々javaばかり業務で触っているのでthis
に対して先入観を持っていたが、今回調査を行うことで、javascriptについてまた知見を深めることができた。
皆様もjavascriptのthis
にはお気をつけていただきたい。
Discussion