«前の日記(2006年 02月08日(Wed)) 最新 次の日記(2006年 02月10日(Fri))» 編集

日記のような何か

2002|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|08|09|10|11|12|
2020|01|02|03|04|06|07|08|09|10|11|12|
2021|01|02|03|04|05|07|08|10|11|12|
2022|01|02|03|04|05|06|07|08|10|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|

ようこそいらっしゃいました。
あなたは今日人目、合計人目のお客様です(ちなみに昨日は人のお客様がいらっしゃいました)。
よろしければツッコミに足跡等を残していって下さいな。

My status ← skypeやってます。


2006年 02月09日(Thu) [長年日記]

_ [プログラム]Javascriptでハマる(解決編)

何か昨日の日記はこれとバトンのせいで、ここ4年間のなかで最も長い日記だったような気が。 それはともかく昨日の続き。

さて、昨日はeachを使った途端に動かなくなっちゃうと言う話でしたが、これはeachの引数としてeachで呼び出す関数を渡してるんですが、これがC++の関数オブジェクト等とは性質が違うからです。

説明だけではなんですから例を挙げましょう。まずは昨日の例に新しいクラス定義を追加してみます。

var Counter2 = Class.create();
Counter2.prototype = {
  initialize: function() {
    this.count = 0;
    this.observers = new Array();
  },
  setCallback: function(callback) {
    this.callback = callback;
  },
  addObserver: function(observer) {
    this.observers.push(observer);
  },
  notifyObservers: function() {
    for(i = 0; i < this.observers.length; i++) {
      this.observers[i].update(this);
    }
  }
};

こんな感じ。Counterクラスとの違いはcountUpが無いのと、その代わりに任意のコールバック関数をセットするsetCallbackがあるという点です。

で、インスタンス生成以降の部分は以下のように書き換えます。

var a = new ElementView('count1');
var b = new ElementView('count2');
var c = new Counter();
var d = new Counter2();

c.addObserver(a);
d.addObserver(b);
d.setCallback(c.countUp);

HTMLも上に合わせて書き換えましょう。

<html>
<script type="text/javascript" src="./prototype.js"
charset="UTF-8"></script>
<script type="text/javascript" src="./test2.js"
charset="UTF-8"></script>
<div id='count1'>0</div><div id='count2'>0</div>
<input type='button' value='count up 1'
onclick='c.countUp()' />
<input type='button' value='count up 2'
onclick='d.callback()' />
</html>

実際の例はこちら。ボタンが2個あってcount up 1を押すとc.countUpが呼ばれ、count up 2を押すとd.callbackが呼ばれる仕組み。

で、動かしてみると分かるんですが、count up 2を押すと下側の数字がカウントアップされていきます。あれ? d.callbackってc.countUpがセットされているだからcがカウントアップされるのでは?

そうなのです。Javascriptでは関数を呼び出したオブジェクトがthisに設定されるのであって、元のオブジェクトには関係ないのです。だから上の例でもcでは無く、dがカウントアップされるのは正しい動作と言えます。

でもそれだと困る事もあるんですよね。私はcのcountUpを登録したんだから、cがカウントアップして欲しいって場合だってあると思います。ここでは触れませんが特にイベントに対するコールバック等では良くあるケースだと思います。

で、私も上のような状況にハマってしまって悩んでいたんですが、これに対する回答がprototype.jsの中に存在しました。それがbindです。

bindを使うとある関数のthisを固定(束縛)することが出来ます*1。まあ例を挙げたほうが分かりやすいでしょう。先ほどの「d.setCallBack(c.countUp)」の部分を次のように書き換えます。

c.addObserver(a);
d.addObserver(b);
d.setCallback(c.countUp.bind(c));

実際の動作を見れば、count up 1でも、count up 2でもcがカウントアップしているのが分かると思います。これは「c.countUp.bind(c)」でc.countUpのthisをcに固定(束縛)しているからです。

さて最初の問題に戻りましょう。実はeachを使った途端に動かなくなってしまったのも、eachに渡した関数のthisが適切に設定されていなかったせいなのです。したがってCounter.notifyObserversを以下のように書き換えれば解決します。

notifyObservers: function() {
  this.observers.each(
    function(value, index) {
      value.update(this);
    }.bind(this)
  );
}

これでeachを使っても正しく動くようになりました

*1 かなり大雑把な説明ですが(^^;


Googleカスタム検索

my recommend books