callback の method call

jQueryprototype.js を始め、JavaScript高階関数を扱うライブラリも最近では一般的になりました。

そのようなライブラリでは大抵 callback 関数に引数でオブジェクトを渡すことが多いのですが、JavaScript はれっきとしたオブジェクト指向言語なので、単純なメソッドコールやプロパティアクセスをしたいだけの場合でも、引数を受け取り内部でメソッドを呼び出す function を定義してやる必要があり、非常に歯がゆいです。

var array = ["foo", "bar", "hoge"];
var upperCases = array.map(function(s) {return s.toUpperCase();});

ただ toUpperCase() を呼びたいだけなのに function や return などいちいち書かなければならないのが面倒くさい。息をするのも面倒くさい。
関数型言語ではこんな苦労はありません。オブジェクト指向言語ならではですね。

var upperCases = array.map(String.prototype.toUpperCase);

せめて関数型言語の様にこんな感じで書けないものでしょうか?
ちなみに以下のコードは動きません。

var upperCases = array.map(String.prototype.toUpperCase.call);

Function.prototype.call はあたかも関数の様に書けますが Functionオブジェクトではない様です。
上記のコード、Firefoxでも動作しませんし、WindowsIE6 はクラッシュしてしまいました。

そこで、単純なメソッドコールやプロパティアクセスを行う関数を定義してみました。

function applyMethod(method, args) {
    return function(target) {
        var func = method instanceof Function ? method : target[method];
        return func.apply(target, args);
    }
}
function callMethod(method) {
    var args = Array.prototype.slice.call(arguments, 1);
    return applyMethod(method, args);
}
function getProperty(propertyName) {
    return function(target) {
        return target[propertyName];
    }
}

使い方は以下のような感じになります。
例として下記のような Customer クラスがあるとします。

function Customer(pName, pBirthday) {
    this.name = pName;
    this.birthday = new Date(pBirthday);
}
Customer.prototype = {
    getAge: function() {
        return Math.floor((this._dateToInt(new Date()) - this._dateToInt(this.birthday)) / 10000)
    },
    _dateToInt: function(date) {
        return date.getFullYear() * 10000 + (date.getMonth()+1) * 100 + date.getDate();
    },
    isOver: function(age) {
        return this.getAge() > age;
    },
    isBetween: function(to, from) {
        var age = this.getAge();
        return to <= age && from > age;
    },
}

プロパティとして名前と誕生日を持ち、年齢を返すメソッドと、引数で渡された年齢より上かどうかを判定するメソッドと、年齢が引数の範囲内にあるか判定するメソッドを持った顧客クラスです。

var customers = [
    new Customer("山田", "1971/12/03"),
    new Customer("鈴木", "1988/09/21"),
    new Customer("佐藤", "2001/03/03"),
    new Customer("高橋", "1964/10/12"),
    new Customer("田中", "1979/08/11"),
];

今、このCustomerクラスのインスタンス配列が存在するとします。

customers.map(function(c) {return c.getAge();});      // 通常のやり方。これを以下の様に書くことができます。
customers.map(callMethod(Customer.prototype.getAge)); // Functionオブジェクトを渡す
customers.map(callMethod("getAge"));                  // メソッド名を文字列で渡す

引数の必要なメソッドコールも第二引数以降に渡してやればOKです。

customers.map(callMethod("isOver", 30));
customers.map(callMethod(Customer.prototype.isOver, 30));
customers.map(callMethod("isBetween", 20, 30));           // 複数の引数もOK
customers.map(applyMethod("isBetween", [20, 30]));        // applyMethod は複数の引数を配列で渡せます。

プロパティアクセスには getProperty が使用できます。

customers.map(getProperty("name"));  // ["山田", "鈴木", "佐藤", "高橋", "田中"] が取得できます。

これで高階関数を扱う際にちょっと楽ができるのではないでしょうか。