🙄

【javascript】コールバック関数におけるthis

2024/07/03に公開

はじめに

コールバック関数を用いた実装で度々バグが発生しており、仕様について改めて勉強してみた。

コールバック関数のおさらい

まず、コールバック関数とは何かおさらいをする。

コールバック関数とは、たいていの場合ある関数に引数として渡される関数のことを指す。
具体例で言うと以下の通り。

コールバック関数
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はその呼び出し方によって別のオブジェクトになりうるらしい。
細かい仕様は長くなってしまうので、また別の機会に調査してみることにする。

このような意図しない挙動を回避するためには、以下のような書き方をすればよい。

  1. 無名関数ではなくラムダ式で渡す
    ラムダ式は基本的には無名関数の代替構文という認識でよいが、this(他super等も)を束縛しないとのこと。つまり、関数が記述された時点でのthisで処理を行うので、問題を回避できる。

  2. bind()を使用する
    百聞は一見に如かず、以下のような書き方をすることでthisを指定できる。

  ・・・
  callEvent () {
    this.exeClbk(function(){
      console.log(`myNameIs${this.name}`);
    }.bind(this));
  }
  ・・・

まとめ

日々javaばかり業務で触っているのでthisに対して先入観を持っていたが、今回調査を行うことで、javascriptについてまた知見を深めることができた。
皆様もjavascriptのthisにはお気をつけていただきたい。

Discussion