常識なのかもしれないですけど、先日たまたまJavaScript1.6で追加されたArrayの拡張メソッドがWebKitでもサポートされているのに気がつきました。
ちゃんとした文書をみつけられなかったのですが[webkit-reviews] review denied: [Bug 6252] JavaScript 1.6 Array.lastIndexOf : [Attachment 10095] lastIndexOf javascript patchというのが出てきたので、WebKitでも正式にJavaScript1.6がサポートされてるようです。
mapは引数に関数を渡して使います。配列の各要素を引数にして渡された関数を実行して、関数の返した値を配列として返してくれます。mapは戻り値として配列を返すのに対して、何も返さないforEachもあります。何も返さないぶんforEachのほうが高速なんだと思いますが、タイプするときに難しいので、値を返さないときでも私はもっぱらmapを使っています。
mapの何が便利なのか、例としてjavascriptでURLのクエリ文字列をパースして、ひとつのオブジェクトにパラメータの名前をキーにして値を保持する処理を考えてみましょう。mapを使わずにふつうにforを使って書くとこうです。
var params = {};
var q = "q=prototype.js&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a";
var pairs = q.split(/&/);
for(var i = 0; i < pairs.length; i++ ) {
var pair = pairs[i];
var nv = pair.split(/=/);
var n = decodeURIComponent( nv[0] );
var v = decodeURIComponent( nv[1] );
params[n] = v;
}
ループをまわすためにforを書いたり、URLエンコードされているかもしれない名前と値をデコードするために一度変数にいれたりしないといけません(params[ decodeURIComponent(nv[0]) ] = decodeURIComponent(nv[1])って書くのはちょっと嫌ですよね)。
おなじ処理をmapを使って書くとこんなにすっきりします。
var params = {};
var q = "q=prototype.js&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a";
q.split( /&/ ).map( function (pair) {
var nv = pair.split(/=/).map( decodeURIComponent );
params[ nv[0] ] = nv[1];
} );
mapの引数にした関数に配列の要素の値が直接渡されるので、インデックスのための変数が必要なくなるのと、パラメータの名前と値をデコードするのに2回decodeURIComponentを書かなくてよくなるのですっきりします。
今回はわりとなじみがありそうな例としてクエリ文字列のパースをあげました。関数を引数に渡すのはなんか難しそうですが、forでループで書くのにくらべてmapだと書かないといけない量が少ないのでとりあえず楽ができます。
ただけっきょくIEでサポートされてるわけではないので、SafariでJavaScript1.6のArray拡張が実装されていてもiPhone/iPod touch用のサイトくらいしか使える機会がないのが悲しいところです...
mapを使うときには注意です。引数に渡した関数の中ではthisの値が変わっています(windowオブジェクトになっています)。さっきのコードがもし下のようにクラスメンバのparamsに結果を代入するようになっているとParser.paramsではなくwindow.paramsに結果が入ってしまいます。
var Parser = {
params: {},
parse: function (q) {
q.split( /&/ ).map( function (pair) {
var nv = pair.split(/=/).map( decodeURIComponent );
this.params[ nv[0] ] = nv[1];
} );
}
}
これを避けるには2つ方法があります。
よく使われているのが、mapの前にthisをselfに代入しておく方法です。一時的に代入しておくための変数はselfでなくても何でもいいですが、慣習としてselfが使われます。
var Parser = {
params: {},
parse: function (q) {
var self = this;
q.split( /&/ ).map( function (pair) {
var nv = pair.split(/=/).map( decodeURIComponent );
self.params[ nv[0] ] = nv[1];
} );
}
}
もうひとつはmapの第2引数にthisになるオブジェクトを指定する方法です(さっき知りました...)。関数内のthisで示されるオブジェクトを第2引数で変更することができます。var Parser = {
params: {},
parse: function (q) {
q.split( /&/ ).map( function (pair) {
var nv = pair.split(/=/).map( decodeURIComponent );
this.params[ nv[0] ] = nv[1];
}, this );
}
}
前者はスコープチェインをたどってselfという名前が解決されるのにくらべて、後者の方は同じスコープに名前があるぶん処理が軽いでしょう。WebKitでも同様に第2引数で関数内のthisを変更することができます。
上のクエリ文字列のパースは、さらにJavaScript1.7で導入された分割代入とさらなるArrayの拡張に含まれているreduceを使うとさらにすっきりさせることができます。
var q = "q=prototype.js&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-US:official&client=firefox-a";
var params = q.split( /&/ ).reduce( function (_params, pair) {
var [n, v] = pair.split(/=/).map( decodeURIComponent );
_params[ n ] = v;
return _params;
}, {} );
というふつうに便利なのでIEでも使えるようになるといいですね。
トラックバック元エントリにこのエントリへのリンクがない場合はトラックバックを受け付けません。
http://labs.gmo.jp/mt/mt-tb.cgi/220
comments