goo blog サービス終了のお知らせ 

DEVELOPMENTOR*LEARNING RESOURCE LAB.

ソフトウェア開発現場の視点からソフトウェアと開発者の価値を高める「ホットな何か」をお届けします。(休止)

JavaScriptでは配列要素の末尾にカンマを付けるとエラーになる?

2007年01月18日 | prototype cast

次のJavaScriptコードは、配列とオブジェクトを生成する単純なものですが、WEBブラウザによって振る舞いが変わってきます。

あるWEBブラウザでは期待どおり動作しますが、あるWEBブラウザではエラーになります。その原因は配列要素の末尾にあるカンマです。そのカンマの有無がWEBブラウザの振る舞いの違いになって現れます。

function run() { 
  var foo = [
    "bar",
  ];
  alert(foo[0]);

  var foo = {
    bar: "baz",
  };
  alert(foo.bar);
}

↓次のJavaScriptコードのように、配列要素の末尾にカンマを付けないようにすべし!です。

function run() { 
  var foo = [
    "bar"
  ];
  alert(foo[0]);

  var foo = {
    bar: "baz"
  };
  alert(foo.bar);
}

私は習慣として、PerlやPHPのスクリプトコードを書くとき、末尾にカンマを付けることにしているので、よくよく間違えます。そして、それに気がつかず悩まされます。


Lyase.View Ajax指向のシンプルテンプレートエンジン(その4)

2006年11月21日 | prototype cast

Lyase.View Ajax指向のシンプルテンプレートエンジン(その3)」まではLyase.Viewの使い方を紹介しました。ここではLyase.Viewをより深く理解するため、Lyase.Viewのソースコードを読みながらその特徴に迫ります。

↓Lyase.Viewがテンプレートの文字列を解析するソースコードです。Lyase.Viewはテンプレートを解析しながら、そのテンプレートをJavaScriptコードに変換するという方法を使っています。

「new Function("context", parsed.join(""))」とあるように、Lyase.Viewは1つのテンプレートの文字列から1つのJavaScript関数を生成します。Functionオブジェクトを使って、動的にJavaScript関数を生成しているのがポイントです。Functionオブジェクトってこういう使い方もあるんですね。詳しくは「Mozilla Developer Center」の「Core JavaScript 1.5 Reference」を見てください。

Lyase.View = {
  _cache : {},
  parse : function(template, id) {
    var self = Lyase.View,parsed = ["var __out=[],render = Lyase.View.render;"]
    var tokens = template.split("<%");
    for(var i = 0, l = tokens.length; i<l; i++) {
      var token = tokens[i];
      if(token.indexOf("%>") == -1){
        parsed.push(self._string(token));
        continue;
      }
      var parts = token.split("%>");
      parsed.push(self[(parts[0].charAt(0) == "=")?"_value":"_code"](parts[0]));
      parsed.push(self._string(parts[1]));
    }
    parsed.push("return __out.join('');");
    var templateFunc = new Function("context", parsed.join(""));
    if(id) self._cache[id] = templateFunc;
    return templateFunc;
  },

テンプレートの文字列から生成したFunctionオブジェクトは、テンプレートの識別子に関連付けてキャッシュしています。「using innerHTML(ページ中の要素として)」のときは要素IDを、「using a template file(外部ファイルとして)」のときはファイルパスを識別子に使います。そのため、同じテンプレートを繰り返し使ったとしても、テンプレートを解析するのは1度だけです。ただし例外があり、「using text(JavaScriptの文字列として)」のときはキャッシュされません。

  render : function(options, values) {
    var self = Lyase.View, template, id;
    if(options.text) return self.parse(options.text, null)(values);
    if(options.element) var element = $(options.element);
    id = (options.file) ? options.file : element.id;
    if(self._cache[id]) return self._cache[id](values);
    if(options.element) {
      template = self._elementTemplate(element);
    }else {
      template = (new Ajax.Request(options.file,{asynchronous : false})).transport.responseText;
    }
    return self.parse(template, id)(values);
  },

「using a template file(外部ファイルとして)」のときはAjax.Request(prototype.js)を使って、テンプレートを外部ファイルから取得しますが、そのときHTTPサーバ側でエラーが発生しても、そのエラーレスポンスの内容をテンプレートして解釈しています。この振る舞いが許容できないときは、1例として次のようにソースコードを改良してエラーが発生したときの振る舞いを追加するとよいでしょう。

  var request = new Ajax.Request(options.file, {asynchronous : false}).transport;
  template = request.status!=200
    ? '...'
    : request.responseText;

Lyase.Viewの紹介は今回(その4)でおしまいです。


Lyase.View Ajax指向のシンプルテンプレートエンジン(その3)

2006年11月14日 | prototype cast

Lyase.View Ajax指向のシンプルテンプレートエンジン(その2)」の続きです。

前回は「using innerHTML(ページ中の要素として)」の方法を使いましたが、今回は「using a template file(外部ファイルとして)」の方法を使って、テンプレートをファイルに分離してみます。↓その結果がこちらです。こんな感じです。

 
View Example

はじめにWEBページ中の要素として追加したテンプレートをファイルに移動します。ファイルに移動したら、次のテンプレート(textarea要素)は不要なのでWEBページ中から削除します。

<textarea id="template" style="display:none;">
  template text
</text>

テンプレートをファイルに保存してアップロードします。ここでは「lyase_view_ajax.txt」という名前のテキストファイルにしました。

lyase_view_ajax.txt
<ol>
<%
  context.each(function(video) {
    video = Object.extend({
      description: '(description)',
      category: '(category)',
      filesize: '(filesize)',
      runtime: '(runtime)'
    }, video);
%>
<li>
<div>
<img src="<%=video.thumbnailUrl%>" />
</div><div>
<a href="<%=video.videoUrl%>" target="_blank"><%=video.title%></a>
</div><div>
<a href="<%=video.channelUrl%>" target="_blank"><%=video.channel%></a> - <%=video.dateFound%>
</div><div>
<%=video.description%> ...
</div><div>
<%=video.category%> - <%=video.filesize%> - <%=video.runtime%> - <%=video.formats%>
</div>
</li>
<%
  });
%>
</ol>

次にLyase.View.renderメソッドを使ってテンプレートにパラメータを適用します。このとき引数としてテンプレートのファイル名を指定します。

  document.getElementById('results').innerHTML =
    Lyase.View.render({file: 'lyase_view_ajax.txt'}, AOLVS.VideoSet.Video);

Lyase.View.renderメソッドはprototype.jsのAjax.Requestを使ってテンプレートファイルを取得します。ただし、オリジナルのソースコードではHTTPのPOSTメソッドを使ってテンプレートファイルを取得するため、WEBサーバの設定によってはエラーになることがあります。多くのWEBサーバでは静的ファイルへのPOSTメソッドを制限しているためです。

ですので、エラーが発生するときは、次のようにオリジナルのソースコードを修正して使います。Ajax.RequestのオプションにGETメソッドを使うように指定します。(ソースコードを修正せずにWEBサーバの設定を見直すという方法もありますが、ここでは割愛します。)

lyase_view.js
//template = (new Ajax.Request(options.file,{asynchronous : false})).transport.responseText;
template = (new Ajax.Request(options.file,{asynchronous : false, method: 'get'})).transport.responseText;

参考として「using text(JavaScriptの文字列として)」の方法にも触れておきます。この方法はJavaScriptコード上の文字列をテンプレートとする最も単純な方法です。例えばusing innerHTMLの例は、次のように書き換えることもできます。

  var request = new Ajax.Request('lyase_view_ajax.txt', {
    method: 'get', asynchronous : false
  }).transport;
  if (request.status!=200)
    return alert(request.status+' '+request.statusText);
  var text = request.responseText;
  
  document.getElementById('results').innerHTML =
    Lyase.View.render({text: text}, AOLVS.VideoSet.Video);

↓その結果がこちらです。こんな感じです。


View Example

Lyase.Viewの使い方はここまで。ただまだ続けます。後はLyase.Viewをより深く理解するため、Lyase.Viewのソースコードを引用しながらその特徴を紹介します。きっと。。。


Lyase.View Ajax指向のシンプルテンプレートエンジン(その2)

2006年11月10日 | prototype cast

Lyase.View Ajax指向のシンプルテンプレートエンジン(その1)」の続きです。

Lyase.Viewを使って、さらに次のソースコードのテンプレート化を推し進めます。

  var html = '<ol>';
  for (var i=0; i<AOLVS.VideoSet.totalResultsReturned; i++) {
    var video = Object.extend({
      description: '(description)',
      category: '(category)',
      filesize: '(filesize)',
      runtime: '(runtime)'
    }, AOLVS.VideoSet.Video[i]);
    html += '<li>';
    html += Lyase.View.render({element: 'template'}, video);
    html += '</li>';
  }
  html += '</ol>';
  document.getElementById('results').innerHTML = html;

↓その結果がこちらです。こんな感じ。


View Example

「ol」「li」のリストタグと、そのリストを作る「for」のループをテンプレートに加えます。また今回はprototype.jsを使ってforを「each」に置き換えています。

<textarea id="template" style="display:none;">
<ol>
<%
  context.each(function(video) {
    video = Object.extend({
      description: '(description)',
      category: '(category)',
      filesize: '(filesize)',
      runtime: '(runtime)'
    }, video);
%>
<li>
<img src="<%=video.thumbnailUrl%>" />
</div><div>
<a href="<%=video.videoUrl%>" target="_blank"><%=video.title%></a>
</div><div>
<a href="<%=video.channelUrl%>" target="_blank"><%=video.channel%></a> - <%=video.dateFound%>
</div><div>
<%=video.description%> ...
</div><div>
<%=video.category%> - <%=video.filesize%> - <%=video.runtime%> - <%=video.formats%>
</div>
</li>
<%
  });
%>
</ol>
</textarea>

Lyase.View.renderメソッドを使ってテンプレートにパラメータを適用します。このときパラメータとしてビデオの配列を指定しています。

  document.getElementById('results').innerHTML =
    Lyase.View.render({element: 'template'}, AOLVS.VideoSet.Video);

テンプレート中に「<% javascript code %>」(<%=でないことに注目!)を埋め込むと、その埋め込んだJavaScriptコードをそのまま評価して実行されます。またここでのテンプレート中の「context」という変数は「AOLVS.VideoSet.Video」が対応します。

今回はここまで。Lyase.Viewはまだ続けます。次は「using a template file(外部ファイルとして)」というAjax指向の方法を紹介します。


Lyase.View Ajax指向のシンプルテンプレートエンジン(その1)

2006年11月08日 | prototype cast

JavaScriptテンプレートエンジンの比較サイトを探していると「blog@slightlyblue javascriptテンプレートエンジンまとめ」というブログを発見しました。このブログは各テンプレートエンジンのコーディングスタイルを実例で紹介しています。ネットでよく見かけるテンプレートエンジンは網羅していると思います。

ただその中に「Lyase.View」という知らないテンプレートエンジンがあったのでさっそく使ってみました。Lyase.Viewは「inforno ? 埋め込みjavascriptを実装してみました。」というブログで配布されています。ライセンスは「Articstic License 2.0」です。ダウンロードするとアーカイブにいくつかのサンプルが入っているので、まずはサンプルを眺めて雰囲気を掴むとよいでしょう。

Lyase.Viewはテンプレートを「using text(JavaScriptの文字列として)」「using innerHTML(ページ中の要素として)」「using a template file(外部ファイルとして)」の3通りの方法で扱えます。今回はusing innerHTMLの方法を使って「AOLVS Ajax APIを使ってビデオの主要なデータを表示してみる」で作ったAjaxアプリをLayse.Viewで置き換えてみました。↓こんな感じです。

 
View Example

次のソースコードをLyase.Viewを使ってテンプレート化します。

    html += '<div>';
    html += '<img src="'+video.thumbnailUrl+'" />';
    html += '</div><div>';
    html += '<a href="'+video.videoUrl+'" target="_blank">'+video.title+'</a>';
    html += '</div><div>';
    html += '<a href="'+video.channelUrl+'" target="_blank">'+video.channel+'</a>';
    html += ' - ';
    html += video.dateFound;
    html += '</div><div>';
    html += video.description+' ...';
    html += '</div><div>';
    html += video.category;
    html += ' - ';
    html += video.filesize;
    html += ' - ';
    html += video.runtime;
    html += ' - ';
    html += video.formats;
    html += '</div>';

はじめに「prototype.js」と「lyase_view.js」を取り込みます。lyase_view.jsはprototype.jsに依存しています。

<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="lyase_view.js"></script>

テンプレートをページ中の要素として追加します。次のように要素はtextareaを使うとよいでしょう。このときテンプレートの要素を特定するためにidを付与しておきます。またテンプレートを表示しないようにstyleで非表示にします。

<textarea id="template" style="display:none;">
<div>
<img src="<%=context.thumbnailUrl%>" />
</div><div>
<a href="<%=context.videoUrl%>" target="_blank"><%=context.title%></a>
</div><div>
<a href="<%=context.channelUrl%>" target="_blank"><%=context.channel%></a> - <%=context.dateFound%>
</div><div>
<%=context.description%> ...
</div><div>
<%=context.category%> - <%=context.filesize%> - <%=context.runtime%> - <%=context.formats%>
</div>
</textarea>

Lyase.View.renderメソッドを使ってテンプレートにパラメータを適用します。このとき引数としてテンプレートのidとパラメータを指定します。

    html += Lyase.View.render({element: 'template'}, video);

見てのとおり、Lyase.Viewのテンプレートの書式は「PHP」「JSP」「ASP」などページ中にソースコードを埋め込むものによく似ています。

テンプレート中に「<%=javascript expression%>」を埋め込むと、その埋め込んだJavaScriptコードの結果が文字列として置き換わります。またLyase.View.renderメソッドの引数に指定したパラメータは、テンプレート中で「context」という変数で参照できます。

とりあえず今回はここまで。Lyase.Viewの紹介はまだ続けます。きっと。。。


Array.rotate 配列の要素をローテーションする

2006年11月07日 | prototype cast

Array.rotateは配列の要素をローテーションするメソッドです。Array.rotateメソッドはprototype.js v1.4.0を前提としています。

配列の先頭から要素を1つ取り出し、その要素を配列の末尾に追加します。このとき、取り出した先頭の要素を返却します。なお、配列が空のときはundefinedを返却します。

Object.extend(Array.prototype, {
  rotate: function() {
    var v;
    if (this.length > 0)
      this.push(v = this.shift());
    return v;
  }
});

Array.rotateの使用例です。

  var foo = [30, 20, 50, 30, 10, 10, 40, 50];
  alert(foo.rotate()); //=> 30
  alert(foo); //=> [20, 50, 30, 10, 10, 40, 50, 30]

  var bar = ['/tmp', '/home', '/etc', '/tmp'];
  alert(bar.rotate()); //=> '/tmp'
  alert(bar); //=> ['/home', '/etc', '/tmp', '/tmp']

配列のインデックスを意識せず、直感的に1ステップでローテーションできますね。「Googleマップ上のレジャースポットを自動クルーズする」のように配列の内容にしたがって繰り返し処理するようなケースできっと役に立ちます。


Number.toDate 数値をDateオブジェクトに変換する

2006年11月06日 | prototype cast

Number.toDateは数値をDateオブジェクトに変換するメソッドです。Number.toDateメソッドはprototype.js v1.4.0を前提としています。

Numberオブジェクトの値でDateオブジェクトを初期化してそのまま返却します。このときの値は1970年1月1日からの経過秒数(ミリ秒)として扱われます。

Object.extend(Number.prototype, {
  toDate: function() {
    var v = new Date();
    v.setTime(this);
    return v;
  }
});

数値がミリ秒のときはそのまま変換すればよい。

  var mills = 1162738800000; // 2006-11-06 00:00:00
  alert(mills.toDate().toLocaleString());

数値がUNIX Timestampのときは1000ms単位にしてから変換すればよい。

  var sec = 1162738800; // 2006-11-06 00:00:00
  alert((sec*1000).toDate().toLocaleString());

1ステップでDateオブジェクトが生成できるので、使いどころではソースコードがスマートになるかもしれません。


Array.uniq 配列の重複した要素を取り除く

2006年10月17日 | prototype cast

Array.uniqは配列の重複した要素を取り除くメソッドです。Array.uniwメソッドはprototype.js v1.4.0を前提としています。

配列の中を走査しながら重複した要素を取り除き、その結果を新しい配列として返します。要素の重複はprototype.jsのEnumerable.includeを使って判断しています。

Object.extend(Array.prototype, {
  uniq: function() {
    return this.inject([], function(dest, value) {
      if (!dest.include(value))
        dest.push(value);
      return dest;
    });
  }
});

var foo = [30, 20, 50, 30, 10, 10, 40, 50];
alert(foo.uniq()); //=> [30, 20, 50, 10, 40]

var bar = ['/tmp', '/home', '/etc', '/tmp'];
alert(bar.uniq()); //=> ['/tmp', '/home', '/etc']

なおArray.uniqメソッドはprototype.js v1.5.0から追加されるようです。


Array.assoc 配列の配列を検索する

2006年10月17日 | prototype cast

Array.assocは配列の配列を検索するメソッドです。Array.assocメソッドはprototype.js v1.4.0を前提としています。

配列の中の配列を検索して0番目の値がobjectと等しい要素を返します。2つ以上が該当するときは1つ目の要素を返します。該当する要素がないときはundefinedを返します。

Object.extend(Array.prototype, {
  assoc: function(object) {
    return this.find(function(value) {
      return (value 
        && value.constructor==Array
        && value.length>0
        && value[0]==object);
    });
  }
});

var foo = [['tokyo', 100], ['kanagawa', 200], ['saitama', 300]];
alert(foo.assoc('tokyo')); //=> ['tokyo', 100]
alert(foo.assoc('saitama')); //=> ['saitama', 300]

Array.freq 配列要素の出現数を求める

2006年10月12日 | prototype cast

配列の重複した要素があるとき、その要素の出現数を求めるArray.freqメソッドです。Array.freqメソッドはprototype.js v1.4.0を使うことを前提としています。


Array.freqメソッドは配列の要素をキー、出現数を値としたオブジェクトを返却します。

Object.extend(Array.prototype, {
  freq: function() {
    return this.inject({}, function(dest, value) {
      var key = value.toString();
      if (dest[key]==undefined)
        dest[key] = 0;
      dest[key] += 1;
      return dest;
    });
  }
});


キーは配列の要素をtoStringしたものを使います。ですので、配列の要素が「null」「undefined」「Object」のときその結果は曖昧です。要素が「constractor」や「toString」のときプロパティとメソッドを上書きしてしまう問題も残ります。どうすんべかな。


Function.applyを知ればprototype.jsのFunction.bindを理解できる

2006年10月05日 | prototype cast
[ajax] prototype.jsの活用にはFunction.applyがどう振舞うか知っておきたいの続きです。prototype.jsを使ったコードではfunctionを指定した繰返しの構文をよく見かけます。

次の例はNumber.timesを使って2の8剰を計算し、その結果を「this.v」に保存しています。もちろん結果は256です。
var Foo = Class.create();
Foo.prototype = {
  initialize: function() {
    this.v = 1;
  },
  f1: function() {
    (8).times(function() {
      this.v *= 2;
    }.bind(this));
    alert('Are you ready?: '+this.v);
  }
}

var foo = new Foo();
foo.f1();



前述の例ではfunction{・・・}.bind(this)という馴染みのない構文を使っています。この「.bind(this)」とはどんな意味があるのでしょうか。これからその意味を確かめてみます。

前述の例から「.bind(this)」を削除してみます。結果は256と思いきや1になります。とても結果が1になるようには見えません。
var Foo = Class.create();
Foo.prototype = {
  initialize: function() {
    this.v = 1;
  },
  f1: function() {
    (8).times(function() {
      this.v *= 2;
    });
    alert('Are you ready?: '+this.v);
  }
}



前述の例を別の書き方で表現すると次のようになります。書き方を変えると結果が256でなく1になるのが当然とわかります。
function g1() {
  this.v *= 2;
}
var Foo = Class.create();
Foo.prototype = {
  initialize: function() {
    this.v = 1;
  },
  f1: function() {
    (8).times(g1);
    alert('Are you ready?: '+this.v);
  }
}



前述の例に「.bind(this)」を加えてみます。結果は1でなく256になります。Function.bindの振る舞いとその効果が見えてきましたね。
function g1() {
  this.v *= 2;
}
var Foo = Class.create();
Foo.prototype = {
  initialize: function() {
    this.v = 1;
  },
  f1: function() {
    (8).times(g1.bind(this));
    alert('Are you ready?: '+this.v);
  }
}



次の関数(メソッド)はFunction.bindのソースコードを抜粋したものです。bindは新しいfunctionを生成しています。生成したfunctionは自functionをFunction.applyを使って実行しますが、そのときbindの引数を指定しています。この引数は前述の例では「this」に相当します。
Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}


[ajax] prototype.jsの活用にはFunction.applyがどう振舞うか知っておきたいを振り返りながら読み取ってください。Function.bindの理解が深まり、prototype.jsの活用の幅が広がります。

動きそうで動かないJavaScriptを取り込む事例

2006年10月05日 | prototype cast
JavaScriptの中からJavaScriptを取り込もうと次のコードを書いてみました。

myscript.jsの中からprototype.jsを取り込み、そのprototype.jsのEvent.observeを使ってページロード後にメッセージの表示を試みます。myscript.htmlではmyscript.jsを取り込んでいます。

myscript.js

document.write('<script type="text/javascript" src="js/prototype.js><\/script>');
Event.observe(window, 'load', function() {
  alert('Are you ready?');
}, false);

myscript.html

<head>
<script type="text/javascript" src="myscript.js"></script>
</head>



パッと見だと動きそうな気がしますが「Event.observe is not a function」というエラーになります。Event.observeを使う時点ではprototype.jsが評価されないのですね。なんでだろう・・・とJavaScriptを使いはじめた頃、不に落ちない気持ちになったことを思い出します。

JavaScriptを使って大中規模のアプリケーションを開発するとき、JavaScriptの中からJavaScriptを取り込むような構成を考慮する機会があると思います。そのときこの動きそうで動かない事例を知っているとよいですね。

以前このことで再設計を迫られてちょっと恥ずかしい思いをした体験からの記事でした。(^^;

prototype.jsのObjectRangeで上限と下限の範囲をテストする

2006年10月05日 | prototype cast
[ajax] prototype.jsのObjectRangeでforなどの繰返し構文の不足点を補うの続きです。prototype.jsのObjectRangeを使うと、○以上△以下といった上限と下限の範囲をテストできます。

次のようなifをObjectRangeで代替します。xが10以上かつ20以下かどうかテストしています。
  if (x >= 10 && x <= 20) {
// TODO: }

上記のifをObjectRangeで表現すると次のようになります。ObjectRangeの第3引数(exclusive)には上限値を含める(除外しない)を指定していますので10以上かつ20以下の条件になります。
  var r = new ObjectRange(10, 20, false);
  if (r.include(x)) {
    // TODO:
  }

ObjectRangeは短く$Rともできます。
  if ($R(10, 20, false).include(x)) {
    // TODO:
  }

次のようなifをObjectRangeで代替します。xが10以上かつ20未満かどうかテストしています。
  if (x >= 10 && x <20) {
上記のforをObjectRangeで表現すると次のようになります。ObjectRangeの第3引数(exclusive)には上限値を含めない(除外する)を指定していますので10以上かつ20未満の条件になります。
  var r = new ObjectRange(10, 20, true);
  if (r.include(x)) {
    // TODO:
  }

ObjectRangeは短く$Rともできます。
  if ($R(10, 20, true).include(x)) {
    // TODO:
  }


prototype.jsのObjectRangeでforなどの繰返し構文の不足点を補う

2006年10月05日 | prototype cast
prototype.jsのObjectRangeを使うと、forなどの繰返し構文を代替したりその不足点を補えます。

次のようなforをObjectRangeで代替します。iを1から3まで3回繰り返します。
  for (var i=1; i<=3; i++) {
//TODO: }

上記のforをObjectRangeで表現すると次のようになります。ObjectRangeの第3引数(exclusive)には上限値を含める(除外しない)を指定しています。繰返し中のvは1~3に変化します。またiはインデックス相当で0~2に変化します。繰返し中の値とインデックス相当が得られるのがポイントです。
  var r = new ObjectRange(1, 3, false);
  r.each(function(v, i) {
    //TODO:
  });

ObjectRangeは短く$Rともできます。
  var r = $R(1, 3, false);
  r.each(function(v, i) {
    alert(v+':'+i);
  });

次のようなforをObjectRangeで代替します。iを0から2まで3回繰り返します。
  for (var i=0; i<3; i++) {
上記のforをObjectRangeで表現すると次のようになります。ObjectRangeの第3引数(exclusive)には上限値を含めない(除外する)を指定しています。繰返し中のvは0~2に変化します。第2引数のインデックス相当は同値のため省略しています。
  var r = new ObjectRange(0, 3, true);
  r.each(function(v) {
    //TODO:
  });

ObjectRangeは短く$Rともできます。
  var r = $R(0, 3, true);
  r.each(function(v) {
    //TODO:
  });

上記のforはNumber.timesでも表現できます。繰返し中のvは0~2に変化します。Numberオブジェクトがあって繰返しの始点が0のとき役立ちます。ちなみにNumber.timesの中ではObjectRangeが使われています。
  var r = Number(3);
  r.times(function(v) {
    //TODO:
  });


prototype.jsの活用にはFunction.applyがどう振舞うか知っておきたい

2006年10月05日 | prototype cast
prototype.jsを使いはじめたとき、スッキリと受け入れにくい機能や構文に出くわすことが多々ありました。

いま振り返ってみると、prototype.jsの中で使われるJavaScriptのFunction.applyの理解不足が原因だった気がします。つまりFunction.applyの振る舞いを理解しておくことがprototype.jsを活用する近道になります。

次のような簡単な関数を使ってFunction.applyの振る舞いを観察します。
function f() {
  alert(this.v);
}


次のようにそのまま関数fを呼び出すと「undefined」というメッセージが表示されます。
f();



次のようにFunction.applyを使って関数fを呼び出すと「Are you ready?: 1」というメッセージが表示されます。このときFunction.applyの引数にオブジェクトを指定したのがポイントです。
var foo = new Object();
foo.v = 'Are you ready?: 1';
f.apply(foo);



次のような簡単なクラスを使ってFunction.applyの振る舞いを観察します。


var Foo = Class.create();
Foo.F1 = function() {
  f();
}
Foo.F2 = function() {
  f.apply(this);
}
Foo.prototype = {
  initialize: function() {
    this.v = 'Are you ready?: 2';
  },
  f1: function() {
    f();
  },
  f2: function() {
    f.apply(this);
  }
}

次のようにstaticメソッド(風関数)から関数fを呼び出すと、両者とも「undefined」というメッセージが表示されます。このとき両者の結果が同じ(違いがない)ことがポイントです。
Foo.F1();
Foo.F2();



次のようにオブジェクトを生成してメソッドから関数fを呼び出すと、前者は「undefined」後者は「Are you ready?: 2」というメッセージが表示されます。このときFunction.applyの引数に自分自身のオブジェクトを指定したのがポイントです。
var foo = new Foo();
foo.f1();
foo.f2();



関数fを呼び出すときの状況、関数fを呼び出すときにFunction.applyを使うかどうかに注目してください。またそのときのthis.vの値を比較しながらFunction.applyの仕様を再確認してください。きっとFunction.applyの振る舞いがイメージしやすくなり理解が進むと思います。