ECMAScriptでのオブジェクトの変更

通过使用 ECMAScript,不仅可以创建对象,还可以修改已有对象的行为。

prototype 属性不仅可以定义构造函数的属性和方法,还可以为本地对象添加属性和方法。

新しいメソッドの作成

既存のメソッドを使って新しいメソッドの作成

prototype 属性を使って、既存のクラスに新しいメソッドを定義することができます。例えば、Number クラスの toString() メソッドを思い出してください。16に引数を渡すと、16進数の文字列を出力します。引数が2の場合、2進数の文字列を出力します。このように、数字オブジェクトを直接16進数の文字列に変換するメソッドを作成することができます。このメソッドを作成するのは非常に簡単です:

Number.prototype.toHexString = function() {
  return this.toString(16);
};

この環境では、キーワード this は Number インスタンスを指し、Number の全てのメソッドにアクセスできます。このコードにより以下のような操作が可能です:

var iNum = 15;
alert(iNum.toHexString());		// "F" 出力"

TIY

数字 15 が 16進数の F に等しいため、警告が "F" と表示されます。

既存のメソッドのリネーム

既存のメソッドにより理解しやすい名前を付けることができます。例えば、Array クラスに enqueue() と dequeue() という二つのメソッドを追加し、それらが push() と shift() メソッドを繰り返し呼び出すだけで構いません:

Array.prototype.enqueue = function(vItem) {
  this.push(vItem);
};
Array.prototype.dequeue = function() {
  return this.shift();
};

TIY

既存のメソッドとは関係のないメソッドの追加

もちろん、既存のメソッドとは関係のないメソッドを追加することもできます。例えば、配列内の項目の位置を判断するために、ローカルメソッドが存在しない場合、以下のようなメソッドを作成することができます:

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

该方法 indexOf() 与 String クラスの同名メソッドが一致しており、配列内の各項目を検索し、入力された項目と一致する項目が見つかるまでです。一致する項目が見つかった場合、その位置を返します。見つからない場合は、-1 を返します。この定義を持つことで、以下のようのコードを書くことができます:

var aColors = new Array("red","green","blue");
alert(aColors.indexOf("green"));	//「1」と表示

TIY

ローカルオブジェクトに新しいメソッドを追加する

最後に、ECMAScript 中のすべてのローカルオブジェクトに新しいメソッドを追加するには、Object オブジェクトの prototype 属性上にそれを定義する必要があります。前の章で述べたように、すべてのローカルオブジェクトは Object オブジェクトを継承しており、Object オブジェクトに対するあらゆる変更はすべてのローカルオブジェクトに影響を与えます。例えば、オブジェクトの現在の値を警告で表示するメソッドを追加するには、以下のコードを使用できます:

Object.prototype.showValue = function () {
  alert(this.valueOf());
};
var str = "hello";
var iNum = 25;
str.showValue();		//「hello」と表示
iNum.showValue();		//「25」と表示

TIY

ここでは、String と Number オブジェクトは Object オブジェクトから showValue() メソッドを継承しており、それぞれのオブジェクト上で该方法を呼び出すと「hello」と「25」が表示されます。

既存のメソッドの再定義

既存のクラスに新しいメソッドを定義できるように、既存のメソッドも再定義できます。前の章で述べたように、関数名は関数へのポインタであり、他の関数に簡単に指せます。toString() などのローカルメソッドを変更するとどうなるでしょう?

Function.prototype.toString = function() {
  return "Function code hidden";
}

前のコードは完全に合法で、期待通りの結果が得られます:

function sayHi() {
  alert("hi");
}
alert(sayHi.toString());	//「Function code hidden」と表示

TIY

おそらく覚えているかもしれませんが、Function オブジェクトの章では Function の toString() メソッドが通常出力するのは関数のソースコードであることを紹介しました。このメソッドをオーバーライドすることで、別の文字列(この例では「Function code hidden」)を返すことができます。しかし、toString() が指している元の関数はどうなりましたか?それは完全に廃棄されたため、無用なメモリ領域によってプログラムが回収されます。元の関数を復元する方法はありませんので、元のメソッドをオーバーライドする前に、そのポインタを保存するのが安全です。時には、新しいメソッドの中で元のメソッドを呼び出すこともあります:

Function.prototype.originalToString = Function.prototype.toString;
Function.prototype.toString = function() {
  if (this.originalToString().length > 100) {
    return "Function too long to display.";
  } else {
    return this.originalToString();
  }
};

TIY

このコードでは、最初の行で現在の toString() メソッドの参照を属性 originalToString に保存しています。そして、カスタムメソッドで toString() メソッドをオーバーライドしています。新しいメソッドは、関数のソースコードの長さが100を超えているかどうかをチェックします。もしそうであれば、関数のソースコードが長すぎるとするエラーメッセージを返し、そうでなければ originalToString() メソッドを呼び出して、関数のソースコードを返します。

極端に遅いバインド(Very Late Binding)

技術的には、極端に遅いバインドは存在しません。この本では、ECMAScriptでの一種の現象をこの用語で説明しています。それは、オブジェクトがインスタンス化された後にそのメソッドを定義できるものです。例えば:

var o = new Object();
Object.prototype.sayHi = function () {
  alert("hi");
};
o.sayHi();

TIY

ほとんどのプログラミング言語では、オブジェクトをインスタンス化する前にオブジェクトのメソッドを定義する必要があります。ここでは、メソッド sayHi() は Object クラスのインスタンスが作成された後に追加されています。伝統的な言語ではこの操作が聞いたことがないだけでなく、そのメソッドが Object オブジェクトのインスタンスに自動的に与えられ、すぐに使用できることを知りませんでした(次の行)。

注意:極端に遅いバインド方法は使用しないことを推奨します。なぜなら、追跡および記録が難しいからです。しかし、その可能性を理解するべきです。