m2

Prototype.js 1.6 コードリーディング #Function 編

ほとんど「Prototype.js 1.6.0 の変更点」の焼き直しなんですが、自分の言葉で書きとめておくのもいいかと思い、エントリにしました*1

目次

  • Function#argumentNames()
  • Function#bind(thisObj, arg0, ...)
  • Function#bindAsEventListener(thisObj, arg0, ...)
  • Function#curry(arg0, ...)
  • Function#delay(seconds, arg0, ...)
  • Function#wrap(wapperFunc)
  • Function#methodize(func)
  • Function#defer(func)

本文

Function#argumentNames()

関数の引数名を配列で返す。
例:

(function(){
  alert(new Function("a","b","return a+b").argumentNames())
})()
// => a, b

どう実現しているかというと、Function#toString() で得られるソース文字列から正規表現で持ってきている。
まず使い道の無いメソッドで、Prototype.js 内でも継承でしか使っていない*2

Function#bind(thisObj, arg0, ...)

Function#apply(thisObj, arguments) のラッパー。apply を実行する function を返す。
JavaScript では function を引数に渡すということがよくあるけど、

(function(){
  setTimeout(function() {
    alert(null, '1秒後に alert!')
  }, 1000);
})()

これが

(function(){
  setTimeout(alert.bind(null, '1秒後に alert!'), 1000);
})()

のように書ける。「function」と書く回数が減らせる。

bind の第一引数は apply の第一引数と同じ意味。上の alert ではあまり関係ないけれど、

(function(){
  var Hoge = Class.create({
    methodA: function(msg) {
      this.msg = msg;
      setTimeout(this.methodB.bind(this), 1000);
    },
    methodB: function() {
      alert(this.msg);
    }
  });
  new Hoge().methodA('aaa');
})()

のように書ける。
ちょっとトリッキーだけど、

(function(){
  alert([].slice.bind(arguments)().inspect()) // => [1, 2, 3]
})(1,2,3)

なんてのもできる。

Function#bindAsEventListener(thisObj, arg0, ...)

bind のイベントリスナ用。これを使うと、返される function の第一引数にイベントオブジェクトを渡してくれる。
例:

(function(){
  function listener(e, msg) {
    alert(msg + ' clientX:' + e.clientX + ' clientY:' + e.clientY);
  }
  Event.observe(document.body, 'click', listener.bindAsEventListener(this, 'clicked!'), false);
})()
Function#curry(arg0, ...)

カリー化。やっている内容は「JavaScript で引数束縛: Days on the Moon」と同じ。ただ「JavaScript でカリー化、再び」を読んだ後だと少々物足りない。
例:

(function(){
  function sum(a,b,c) { return a + b + c }
  var sum10 = sum.curry(10);
  alert(sum10(1,2));    // => 13
  alert(sum10(1));      // => 10 + 1 + undefined => NaN
  var sum10_20 = sum.curry(10, 20);
  alert(sum10_20(1));   // => 31
})()
Function#delay(seconds, arg0, ...)

setTimeout のラッパー。
例:

(function(){
  var Sleeper = Class.create({
    wakeup: function(msg) {
      alert(msg + ' ' + this.sec);
    },
    wakeupLater: function(sec) {
      this.cancelWakeup();
      this.sec = sec;
      this.tid = this.wakeup.bind(this).delay(sec, sec <= 1 ? 'Good morning!' : 'Good evening!');
    },
    cancelWakeup: function() {
      if (this.tid) clearTimeout(this.tid);
      this.tid = null;
    }
  });
  var sleeper = new Sleeper();
  sleeper.wakeupLater(1);
  sleeper.wakeupLater(3);
})()
Function#wrap(wapperFunc)

wrapperFunc の第一引数に自分を渡す function を返す*3。継承のところで $super にスーパークラスのメソッドを渡すのに使われている。
例:

(function(){
  function hoge() { alert('hoge') }
  var wrapped = hoge.wrap(function(w) {
    alert('before');
    w();
    alert('after');
  });
  wrapped();
})()
Function#methodize(func)

関数をメソッドにする。と言うとちょっと変な感じだけど。
func の第一引数に this を渡す function を返す。

(function(){
  // http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array:forEach#Compatibility
  function forEach(array, fun /*, thisp*/) {
    var len = array.length;
    if (typeof fun != "function") throw new TypeError();
    var thisp = arguments[2];
    for (var i = 0; i < len; i++) {
      if (i in array) fun.call(thisp, array[i], i, array);
    }
  }
  forEach([1,2,3], function(v) { alert(v) } );
  if (!Array.prototype.forEach) {
    Array.prototype.forEach = forEach.methodize();
  }
  [1,2,3].forEach(function(v) { alert(v) });
})()
Function#defer(func)

おまけメソッド。短いから転載。

Function.prototype.defer = Function.prototype.delay.curry(0.01);

たぶん curry って言いたいだけ。

*1:連載の予定はありません。

*2:サブクラスのメソッドで、最初の引数を「$super」としておくと、スーパークラスの同名のメソッドの参照が得られる。

*3:これを wrap と呼んでいいのか?