2006-03-27

ecmascript の Array で each するときの制限?ではありません

※ 詳しくは後半の追記を読んでください。

すでに最速の人のページをはじめあちこちで普通に紹介されているのですが、

Array.prototype.each = function( callback ) {
  var len = this.length;
  for ( var i = 0; i < len; i++ ) {
    callback( this[i] );
  }
}

で Array に Ruby の each みたいなメソッドを足すことができます。しかしこれ、自分で定義しているオブジェクトの中で利用してプロパティを参照しようとしてもできません。するとエラーで止まります。具体的には

MyObj.prototype = {
  prop: 'hoge',
  method: function() {
    var arr = [ 9, 8, 7, 6 ];
    var str;
    arr.each( function( num ) {
      str += this.prop + "\t" + num + "\n";
    });
  }
}

みたいなことができません。これはあれですか、each の中で this を解釈したときに MyObj にならずに Array になっちゃうからでしょうか。MyObj.prop を参照するつもりなのに本当は Array.prop を見に行っちゃってるのかなと思うんですが。違うかな。あと、回避策はないのでしょうか。

いやまぁ、手元では Array#first(), Array#next(), Array#item(), Array#at_end() なんつーのを用意して for で回す方法で回避できてるんですけどね。なんか面白くないなぁ、と。

すでに Array を拡張しちゃうのは確定なので素直に for ( i in array ) で回せば?っつーのはナシの方向で。

※ このネタ、カテゴリ何にしてようかなぁ…。JavaScript にしちゃうと他の ecma 系のものが該当しないし、Ecma262 か? スクリプト言語の名前に見えないなぁ。あ、ここに挙げたコードは動かしてみてません。


[2006-10-28 追記]

上を書いたときには根本的に分かっていなかったのだが、一応残しておいてとりあえず正解のコードを追記しておく。オブジェクトをまたいで使える Array#each は以下のようになる。

/**
 * Array の要素一つずつに function を適用する
 *
 * @param function callback
 * @param Object obj(あってもなくてもよい)
 */
Array.prototype.each = function( callback, obj ) {
  var len = this.length;
  for ( var i = 0; i < len; i++ ) {
    callback.call( obj, this[i] );
  }
}

これを利用する場合、上のようにあるオブジェクトから利用したいという際は

arr.each( function( e ) {
  ..
}, this );

と第2引数にオブジェクトを投げてやる。特別オブジェクトなど作成していない場合はそのまま

arr.each( function( e ) {
  ..
} );

でよい。このコードを理解する際に必要な知識は

  1. JavaScript の変数スコープ
  2. function の引数は多すぎても足りなくてもエラーは起きないこと
  3. Function#call の第1引数が null か undefined になった場合は Global オブジェクトになること

の3つである。下の2つは

Under Translation of ECMA-262 3rd Edition の

15.3 Function オブジェクトの call メソッド を、

最初のスコープの話は

Hawk's W3 Laboratory : JavaScript Notes : JavaScript 深層 : スコープチェーンと変数のスコープ(2)

をよく読むと分かるはず。

ま、分からなくても使うだけなら使えるけど。

About

例によって個人のなんちゃらです