Prototypeプロパティ
JavaScriptのオブジェクトは、自身が持っているプロパティだけでなく、プロトタイプのプロパティにもアクセスすることが出来る。
オブジェクトがどんなプロトタイプを持っているかは、[[Prototype]]
プロパティを見れば分かる。
[[Prototype]]
プロパティは、Object.getPrototypeOf()
で取得できる。
引数に、[[Prototype]]
プロパティを調べたいオブジェクトを渡せばよい。
あるいは、全てのオブジェクトが持っている__proto__
プロパティで確認することも出来る。
__proto__
プロパティは[[Prototype]]
プロパティの値を格納しており、これにアクセスすることで、参照するプロトタイプオブジェクトを変更することも出来る。
__proto__
プロパティは、ES5の時点では標準化はされていないが、Node.jsや多くのブラウザで実装されている。
var obj = {}; // 両方共同じもの、つまりobjの[[Prototype]]を参照している console.log(Object.getPrototypeOf(obj) === obj.__proto__); // true var prototype = Object.getPrototypeOf(obj); console.log(Object.getOwnPropertyNames(prototype)); // 以下の配列が返ってくる。これが、objの[[Prototype]]が持っているプロパティ。 // [ 'constructor', // 'toString', // 'toLocaleString', // 'valueOf', // 'hasOwnProperty', // 'isPrototypeOf', // 'propertyIsEnumerable', // '__defineGetter__', // '__lookupGetter__', // '__defineSetter__', // '__lookupSetter__', // '__proto__' ] // objはtoStringメソッドを使えるが…… console.log(obj.toString()); // [object Object] // obj自身はtoStringを持っていない // objの[[Prototype]]が参照しているプロトタイプオブジェクトが、toStringを持っているに過ぎない console.log(obj.hasOwnProperty('toString')); // false console.log(obj.__proto__.hasOwnProperty('toString')); // true
プロパティの検索
プロパティを読み込もうとした際、まず、オブジェクト自身がそのプロパティを持っていないか確認する。
持っていた場合はその値を返すが、持っていなかった場合、そのオブジェクトの[[Prototype]]
が参照しているプロトタイプオブジェクトの中から、同名のプロパティがないかを検索する。
見つかった場合は、そのプロパティの値を返す。
var obj = { toString: function(){ return 'これは、オブジェクト自身のプロパティです。'; } }; console.log(obj.toString()); // これは、オブジェクト自身のプロパティです。 delete obj.toString; console.log(obj.toString()); // [object Object] console.log(obj.__proto__.hasOwnProperty('toString')); // true
上記の例では、最初にobj.toString
を呼び出した時はオブジェクト自身がそのプロパティを持っていたため、その内容を返している。
だが直後にこのプロパティを削除しているため、二番目にそれを呼び出した際は、プロトタイプオブジェクトが持っているtoString
が呼び出されている。
プロトタイプはどのように決まるのか
ほぼ全ての関数は、prototype
プロパティを持っている。明示しなくても、このプロパティが自動的に作られる。
そして、インスタンスオブジェクトは、自身の[[Prototype]]
プロパティに、コンストラクタのprototype
プロパティへの参照を格納する。
function Person(){}; // Personは、prototypeというプロパティを持っている console.log(Person.hasOwnProperty('prototype')); // true // インスタンスを作成 var person1 = new Person(); // person1の[[Prototype]]には、Personのprototypeが格納されている console.log(person1.__proto__ === Person.prototype); // true
全てのオブジェクトで使えるisPrototypeOf()
メソッドで、そのオブジェクトが、引数に渡したオブジェクトのプロトタイプかどうかを調べることも出来る。
function Person(){}; console.log(Person.hasOwnProperty('prototype')); // true var person1 = new Person(); console.log(person1.__proto__ === Person.prototype); // true // Person.prototypeが、person1のプロトタイプである console.log(Person.prototype.isPrototypeOf(person1)); // true
上記のような性質から、あるコンストラクタのインスタンスは全て、共通のプロトタイプオブジェクトを参照する。
そのため、プロトタイプのプロパティは、全てのインスタンスからアクセスできる。
function Person(name){ this.name = name; }; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person('Tom'); var person2 = new Person('Bob'); person1.sayName(); // Tom person2.sayName(); // Bob // person1とperson2のプロトタイプは、共通 console.log(person1.__proto__ === person2.__proto__); // true
リテラルで作られたオブジェクトの[[Prototype]]
には、Object.prototype
が設定される。
var obj = {}; console.log(obj.__proto__ === Object.prototype); // true console.log(Object.prototype.isPrototypeOf(obj)); // true
プロトタイプの拡張と再定義
プロトタイプオブジェクトは、任意のタイミングで拡張できる。その変更は、既に作成済みのインスタンスにも反映される。
プロパティを使おうとする度に検索するため、そのような挙動になる。
function Person(name){ this.name = name; }; var person1 = new Person('Tom'); // この時点ではperson1は、自分自身もプロトタイプも、sayNameプロパティを持っていない console.log('sayName' in person1); // false // ここで、プロトタイプにsayNameプロパティを作る Person.prototype.sayName = function(){ console.log(this.name); }; console.log('sayName' in person1); // true person1.sayName(); // Tom
プロトタイプオブジェクトそのものを新しく定義することも出来る。
その場合、定義し直した後に作成したインスタンスは新しいプロトタイプオブジェクトを参照するが、定義し直す前に作成したインスタンスは、古いプロトタイプオブジェクトは参照し続ける。
function Person(){ }; Person.prototype.sayMessage = function(){ console.log('これは、最初に作ったsayMessage'); }; var person1 = new Person(); person1.sayMessage(); // これは、最初に作ったsayMessage // ここで、プロトタイプオブジェクト自体を書き換える Person.prototype = { sayMessage: function(){ console.log('これは、プロトタイプオブジェクト定義し直した際に作ったsayMessage') } }; var person2 = new Person(); person2.sayMessage(); // これは、プロトタイプオブジェクト定義し直した際に作ったsayMessage // person1は、書き換えられる前のプロトタイプオブジェクトを参照し続ける person1.sayMessage(); // これは、最初に作ったsayMessage console.log(person1.__proto__ === person2.__proto__); // false