kazu2532
@kazu2532

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

[javascript]同じthisを渡しているようにしか見えないが、実際には違うthisを渡している

概要

他のプロジェクトで使われていたJSのコードを拝借した際にどういう処理をしているのか読んでいたところ、同じthisを渡しているようにしか見えないが、実際には違うthisを渡しているコードを見つけました。これがどうにもわかりづらく腑に落ちないため意見をお聞きしたいです。

該当のコード

以下は簡略化したものです。

export default class {
  constructor (target) {
    this.target = target
  }

  initialize () {
    this.addEventListener()
  }


  hello () {
    console.log('hello')
  }

  addEventListener () {
    this.target.addEventListener('blur', this.hello.bind(this))
  }
}

ここで注目していただきたいのは、this.hello.bind(this)です。どうやらこれhello呼び出し元のthisと引数のthisは違うものを指していると思われるのです。
わかりやすくするため、下記コードに書き換えてみます。

this.target.addEventListener('blur', function (e) {
    this.hello()
}.bind(this))

ハンドラー内のthis.hello()のthisは本来はイベントが起きた要素(target)を指す(参考) のですが、bind(this)によってこのClass自体のthisで矯正しているため、結果としてClass内で定義したhello()が実行できています。

裏付け

hello呼び出し元のthisと引数のthisは違うものを指していると思われる

上記仮説の裏付けとして下記のようにbind(this)を外してみます。

addEventListener () {
    this.target.addEventListener('blur', this.hello())
  }

この状態でブラウザ上のtargetからフォーカスを外してみてもhello()は実行されません。
これはthisがtargetを指しているがtargetはhello()が定義されていないためだと考えられます。

腑に落ちない点

functionで書き換えれば理解はできるが、this.hello.bind(this)の形だとthisが同じものを指しているようにしか見えないため、こういう書き方ができちゃうのって可読性的にどうなの?という点です。(JSに慣れている人は気にならない?)
これはjavascriptの仕様上、もといaddEventListner関数の仕様上仕方のない部分なのでしょうか?

0

this.hello.bind(this)
どうやらこれhello呼び出し元のthisと引数のthisは違うものを指していると思われるのです。

この2つの this は同じものを指します。何を指すかは(自分で定義しているほうの) addEventListener() がどのように呼ばれるかによって変わりますが。

この状態でブラウザ上のtargetからフォーカスを外してみてもhello()は実行されません。
これはthisがtargetを指しているがtargetはhello()が定義されていないためだと考えられます。

違います。第2引数の this.hello() はその場でメソッドを呼び出しており、返り値である undefined をリスナーとして登録することになるからです。仮説を裏付けたいなら this.target.addEventListener('blur', this.hello) と書くべきです。こう書いた場合、仮説に反してフォーカスを外すと hello() が実行されます。

3Like

仕様なのでプロジェクト全体でそのようにしてるなら気にするほどではありませんが、アロー関数が使える環境ならアローに置き換えた上でe.currentTargetを利用した方が優しいと思います。

2Like

this の挙動について詳しくは https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/this を読んでください。

this.hello.bind(this) の形だとthisが同じものを指しているようにしか見えないため、こういう書き方ができちゃうのって可読性的にどうなの?という点です。(JSに慣れている人は気にならない?)

ここで this が同じものを指しているのは置いておくとして、 this.hello.bind(this) という書き方が必要な場合は確かにあります。これはまあ、他の言語から見ると分かりづらいものの、 JS を書く人は慣れの問題で割り切っていると思います。これは this の仕様上仕方のないことです。

1Like

コメントにもあるけど、アロー関数を使えば簡略化できますよ。

this.target.addEventListener('blur', e => this.hello())
3Like

@uasi @shiracamus @tonberry1050
みなさんご回答ありがとうございます!

仮説を裏付けたいなら this.target.addEventListener('blur', this.hello) と書くべきです。

addEventListenerの第2引数で渡したい関数に()をつけてしまうとhello関数の返り値であるundefinedが登録されるだけだったんですね! ここを完全に勘違いしていました・・・

this.hello.bind(this) という書き方が必要な場合は確かにあります。これはまあ、他の言語から見ると分かりづらいものの、 JS を書く人は慣れの問題で割り切っていると思います。

やはりJSはこういう書き方をする必要が出てくるんですね。普段はRubyでコードを書いているためこの書き方はなかなか自分の中に入ってきませんでした・・!

コメントにもあるけど、アロー関数を使えば簡略化できますよ。

なるほど! アロー関数のthisは呼び出し元に依存するのではなく、定義した場所のスコープに基づくためですね!

1Like

Your answer might help someone💌