prototype.jsを読み解く

第1回 Prototypeライブラリ(1~197行目)

この記事を読むのに必要な時間:およそ 12 分

Object オブジェクトへの拡張

0051: Object.extend(Object, {
0052:   inspect: function(object) {
0053:     try {
0054:       if (object === undefined) return 'undefined';
0055:       if (object === null) return 'null';
0056:       return object.inspect ? object.inspect() : object.toString();
0057:     } catch (e) {
0058:       if (e instanceof RangeError) return '...';
0059:       throw e;
0060:     }
0061:   },
0062: 
0063:   toJSON: function(object) {
0064:     var type = typeof object;
0065:     switch(type) {
0066:       case 'undefined':
0067:       case 'function':
0068:       case 'unknown': return;
0069:       case 'boolean': return object.toString();
0070:     }
0071:     if (object === null) return 'null';
0072:     if (object.toJSON) return object.toJSON();
0073:     if (object.ownerDocument === document) return;
0074:     var results = [];
0075:     for (var property in object) {
0076:       var value = Object.toJSON(object[property]);
0077:       if (value !== undefined)
0078:         results.push(property.toJSON() + ': ' + value);
0079:     }
0080:     return '{' + results.join(', ') + '}';
0081:   },
0082: 
0083:   keys: function(object) {
0084:     var keys = [];
0085:     for (var property in object)
0086:       keys.push(property);
0087:     return keys;
0088:   },
0089: 
0090:   values: function(object) {
0091:     var values = [];
0092:     for (var property in object)
0093:       values.push(object[property]);
0094:     return values;
0095:   },
0096: 
0097:   clone: function(object) {
0098:     return Object.extend({}, object);
0099:   }
0100: });
0101: 

先程定義したObject.extend()関数を使って,ECMA-232で定義されたObjectオブジェクトを拡張しています。形式はObject.extend()の例と同様ですが,第二引数に渡しているオブジェクトリテラルが大きいのでちょっと把握しづらいかもしれません。

ここではObjectオブジェクトに対して,inspect,toJSON,keys,values,cloneというプロパティを定義しており,各々には関数オブジェクトが代入されています。

52行目からのinspect()は,デバッグ用にオブジェクトの文字列表記を返すメソッドです。Prototypeライブラリの中では,String,Array,Enumerable,Hashというクラス内でinspect()メソッドを上書きしており,その場合はそちらの関数が使われます。

まず54行目で,===演算子を使ってundefinedかどうかを確認しています。undefined値は,nullとは異なる値で,変数に値が代入されていない時に用いられます。等しければ'undefined'という文字列を返します。

55行目ではnull値と比較しています。こちらも等しければ'null'という文字列を返します。

56行目では,対象となるオブジェクトにinspectプロパティがある場合は,それを関数とみなして呼び出し,そうでなければtoString()メソッドがあることを仮定して呼び出します。基本的には全てのオブジェクトにはtoString()メソッドが存在するはずなので,それが適切な文字列を返すようになっています。

複雑な型の場合は,独自にinspect()を実装して,適切な文字列を返すようにすることが想定されているようです。

これらの三行での挙動が,try {}で括られています。ただし,catchされているのは例外がRangeErrorだった場合のみであり,その場合は'...'が返されます。なぜここでRangeErrorだけをcatchしているのかは不明です。

63行目からはオブジェクトをJSON化するためのtoJSON()メソッドの定義です。

まず,引数として渡されたオブジェクトの型をtypeof演算子で取得します。65行目からのswitch文で,undefined,function,unknownの場合は何も返さずに帰ります。booleanの場合にはtoString()メソッドの返り値を返しています。

ここで,switch文の中で,単にreturnとしている文とreturn object.toString()と値を明示的に返している文が混在しています。単にreturnだけを書いた場合,undefined を返す,ということは仕様として定義されているのですが,この書き方をしてしまうとぱっと見で関数の呼出し側にはどんな値が返るのかがわからなくなってしまうので,何かしら明示的に返すように記述した方がいいでしょう。FirefoxなどでJavaScriptの警告を厳格にすると,function ... does not always return a valueという警告が出力されてしまいます。

  • ECMA-262 第三版 - 12.9 The return statement

83行目からはkeys()メソッドです。オブジェクトが保持しているプロパティのうち,DontEnum属性が付いていないものを列挙して,文字列の配列として返します。

コード上は特に癖もなく,for (variable in object) でループさせて,事前に準備した配列keysにpush()していきそれをreturnで返す,となっています。

90行目から,values()メソッドです。これは他の言語でもkeys()と対で提供されていることが多いでしょう。keys()がプロパティ名を配列に入れているのに対して,values()ではプロパティ値を配列に入れ,それを返しています。

97行目からはclone()メソッドです。単純にオブジェクトの(浅い)コピーを作る関数です。

JavaScriptにおいて,native object以外は代入演算子でコピーされるものは参照渡しになります。オブジェクトの複製を作りたい場合にはこの関数を使うといいでしょう。

著者プロフィール

栗山淳(くりやまじゅん)

S2ファクトリー株式会社株式会社イメージソース所属。
本業はWeb制作会社の裏方。得意分野はFreeBSDやPerlのはずだが,必要に迫られるとHTML/CSSやJavaScriptも書く。