Garden Place TOP 技術ガーデン JavaScript.com ビルトインオブジェクトの拡張

いまをときめくJavaScriptに関する知識と技術の向上を目指しながら、
インフォニック発JavaScriptライブラリを世界に発信することを目論むプロジェクトです。

他にもWebアプリケーションにおけるUI構築技術全般をターゲットにしています。

公開日:2008.01.28

ビルトインオブジェクトの拡張

prototypeの応用的な使い方

オブジェクト指向言語で「拡張」というと、一般的にクラスを継承して 拡張したサブクラスを作る事を指すと思います。 しかし動的なオブジェクト指向言語であるJavaScriptでは直接、 既存のクラスに対しての拡張を行う事ができます。 今回はprototypeを使ったビルトインオブジェクトの拡張をお見せしましょう。

基本型の拡張

基本型と呼ばれるデータ型の値にはそれぞれ対応したラッパークラスが ビルトインオブジェクトとして定義されています。 基本型を拡張する場合はそのラッパークラスを拡張します。

String.prototype.countChar = function(c) {
   var result = 0;
   for(var i = 0; i < this.length; i++) {
       if (this.charAt(i) == c) result++;
   }
   return result;
};

"hogehogefoo".countChar("o"); //=> 4

Stringクラスを拡張してcountCharというメソッドを追加してみました。 数えたい1文字を引数にcountCharメソッドを呼び出すと この文字列中に存在するその文字の個数を返してくれます。

Number.prototype.add = function(num) {
    return this + num;
};

var n1 = 100;
n1.add(10); //=> 110

// リテラルから直接呼ぶ場合
(10).add(5); //=> 15

※数値は.(ドット)を付けると小数点だと判断してしまうので括弧で括る。 引数に関数を渡すような関数を追加してやるとより実用的になります。

Number.prototype.times = function(func) {
   for(var i = 0; i < this; i++) {
      func(i);
   }
};

(5).times(function() { alert("Hello") });
//=> 5回Helloと表示

オブジェクト型の拡張

配列の場合はArrayクラスに追加する事で拡張できます。

Array.prototype.indexOf = function(item) {
    for(var i = 0; i < this.length; i++)
        if(this[i] == item) return i;
    return -1;
};

[1, 2, 3, 4, 5].indexOf(4); //=> 3

Objectクラスは全てのクラスの親クラスとなるので、 拡張する事で自作クラス含めて全てのクラスに対しての拡張ができます。

Object.prototype.isArray = function() {
    return this instanceof Array;
}

{ a: 1 }.isArray(); //=> false
[1,2,3].isArray(); //=> true
"hoge".isArray(); //=> false

但し、Objectのprototypeへの拡張は余程の事が無ければやらないでください。 全てに対して拡張するという事は影響が計り知れないからです。 プロパティ名のバッティング等で予期しない動きなども考えられますが、 一番大きな問題はfor in 構文で列挙されてしまうからです。

for in 構文

普段あまり使わない文法でfor in構文というのがあります。 これはオブジェクトのプロパティをループにより列挙するというものです。 具体的に使い方を見てみましょう。

var obj = {a: "hoge", b: "foo", c: 10};

for(var p in obj) {
    alert(p);
}

これで「a」「b」「c」と順番に表示されます。 objのプロパティ名を順番にin の前に定義した変数に入れながら、 プロパティの個数分ループするというものです。

プロパティ名がわかるので勿論、その値も参照できます。

for(var p in obj) {
    alert(obj[p]);
}
//=> "hoge", "foo", 10 と順に表示

そしてこれを使えばあるオブジェクトから別のオブジェクトへ 全てのプロパティのコピーなんかもできます。

function copyTo(source, destination) {
    for(var p in source) {
        destination[p] = source[p];
    }
    return destination;
}

var obj1 = {a: "hoge", b: "foo"};
var obj2 = {x: 1, y: 2, z: true};

copyTo(obj1, obj2);
//=> {a: "hoge", b: "foo", x: 1, y: 2, z: true}

for in 構文ではオブジェクトのprototypeも列挙対象になっているので、 Object.prototypeを拡張しているとコピー対象に拡張したプロパティも 含まれてしまいます。 すると上記のコピーで結果オブジェクトのプロパティ数が 5個だと思ってたはずが作成したisArrayプロパティまで含んで6個になり 期待通りに動かないという事が発生します。

自分だけがわかって利用する場合は良いが汎用ライブラリを作成したり、 複数人で開発するような場合は充分に注意する必要があります。

また、ObjectクラスにはデフォルトでtoStringや valueOfといったプロパティがあるのですが、 これらは内部属性により列挙されない事になっています。 自分で追加したプロパティにこの内部属性を設定する事はできません。

まとめ

  • ビルトインオブジェクトのprototypeにプロパティを追加する事で拡張ができる。
  • 数値リテラルから直接メソッドを呼ぶ場合は数値を括弧で括る。
  • Object.prototypeに対する拡張は極力控える。
  • for in を使ってオブジェクトのプロパティを列挙できる。