dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[dojo]dojo.onでthis.[変数名]がundefinedになる

前回の記事の続き。

dojoでカスタムウィジェットを作成するチュートリアルを読んでいたけど
マウスオーバー時のアクションの部分でundefinedエラーが発生していた。

該当のコードはこんな感じ。

// colors for our background animation
baseBackgroundColor:  '#fff',
mouseBackgroundColor: '#def',

postCreate: function(){
    // Get a DOM node reference for the root of our widget
    var domNode = this.domNode;
 
    // Run any parent postCreate processes - can be done at any point
    this.inherited(arguments);
 
    // Set our DOM node's background color to white -
    // smoothes out the mouseenter/leave event animations
    domStyle.set(domNode, "backgroundColor", this.baseBackgroundColor);
    // Set up our mouseenter/leave events - using dojo/on
    // means that our callback will execute with `this` set to our widget
    on(domNode, 'mouseenter', function (e) {
        this._changeBackground(this.mouseBackgroundColor);
    });
    on(domNode, 'mouseleave', function (e) {
        this._changeBackground(this.baseBackgroundColor);
    });
},

_changeBackground(toCol) {
    ...

チュートリアル通りに実装したのですが、
this._changeBackgroundthis.mouseBackgroundColorundefinedだと怒られる...

で、調べてわかったことですが
これチュートリアルの記述が間違ってますね。

正しくはこうです。

(onメソッド内部のみ)

on(domNode, 'mouseenter', lang.hitch(this, '_changeBackground', this.mouseBackgroundColor));

つまり、lang.hitchを使う必要があります。

なんで?という部分については
lang.hitch()のリファレンスにサンプルコード含めてわかりやすく書いてた。 dojo/_base/lang — The Dojo Toolkit - Reference Guide

require(["dojo/on"], function(on){

  var processEvent = function(e){
    this.something = "else";
  };

  on(something, "click", processEvent);

});

こういう書き方をしてもうまくいかないのは
非同期のコールバック関数(onで呼び出してる関数)のcontextが変わっているからなんだと。
contextという単語にしっくりくる日本語訳はわからんが
thisが示すもの」が変わっている、と考えるとよさそう。

つまり、関数を定義している時のthisと、実際に非同期でコールバックが呼ばれている時のthisは別物ってことですね。

それを回避するために、lang.hitchを使います。
上の例だと次のように書けばよい。

require(["dojo/on", "dojo/_base/lang"], function(on, lang){

  var processEvent = function(e){
    this.something = "else";
  };

  on(something, "click", lang.hitch(this, processEvent));

});