できる気になっているJSを改めてチュートリアルからやってみる 15日目
~~ Objectのプロトタイプ ~~
最近ちょっとずつまた学びなおす必要が出てきたなぁと思い、いろいろプログラムを勉強しなおしているところなんですが、実はもう今使っている知識は古いのかもと思って、アップデートしようとおもいやってみる会。
実施するのは、この記事
完全な初心者向けと書かれたチュートリアルは全然初心者向けではないって話。アップデートしていきましょう。
JavaScript オブジェクト入門 をやってます。
Objectのプロトタイプ
JavaScriptにはプロトタイプベースの言語と呼ばれることが多いです。各オブジェクトにはプロトタイプオブジェクトがあり、メソッドとプロパティを継承するテンプレートオブジェクトとして機能します。
これはよくプロトタイプチェーンと呼ばれ、様々なオブジェクトが利用可能な他のオブジェクトで定義されたプロパティとメソッドを持つ理由を説明します。
正確には、プロパティとメソッドはオブジェクトのインスタンスではなく、オブジェクトのコンストラクタ関数のprototypeプロパティで定義されています。
JavaScriptでは、オブジェクトインスタンスとそのプロトタイプ(その__proto__プロパティで、コンストラクタのprototypeプロパティから派生しています)との間にリンクがあり、プロパティとメソッドはプロトタイプのチェーンをたどることで見つけられます。
プロトタイプオブジェクトの理解
function Person(first, last, age, gender, interests) {
this.name = {
first : first,
last : last
};
this.age = age;
this.gender = gender;
this.interests = interests;
this.bio = function() {
alert(this.name.first + ' ' + this.name.last + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
};
this.greeting = function() {
alert('Hi! I\'m ' + this.name.first + '.');
};
}
こんな感じでコンストラクタ関数を定義しています。
var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
コンソールに打ち込むとこんな感じで表示されます。では、実際にObjectで定義されているperson1のメソッドを呼び出すとどうなるでしょうか?
valueOf() メソッドは呼び出されたオブジェクトの値を返します。すると以下のようになります。
・ブラウザは、person1オブジェクトがvalueOf()メソッドを利用できるかどうかを最初にチェックします
・そうではないので、ブラウザはperson1オブジェクトのプロトタイプオブジェクト (Person()コンストラクタのプロトタイプ) にvalueOf()メソッドがあるかどうかを調べます
・ブラウザは、Person()コンストラクタのプロトタイプオブジェクトのプロトタイプオブジェクト (Object() の constructor のprototype) に valueOf()メソッドがあるかどうかをチェックします。Object() には存在するので、実行される
ここでのポイントは、
※1 - プロトタイプチェーンでメソッドとプロパティがオブジェクト間でコピーされていない
※2 - オブジェクトのプロトタイプオブジェクトに直接アクセスする正式な方法はありません。最新のブラウザでは、オブジェクトのコンストラクタのプロトタイプオブジェクトを含む __proto__ という名前のプロパティがあります。
ECMAScript 2015移行、Object.getPrototypeOf(obj)を介してオブジェクトのプロトタイプオブジェクトに間接的にアクセスできます。
prototypeプロパティ:継承されたメンバーが定義されている場所
いくつかは継承されていますが、一部は継承されていません。これはなぜでしょうか?
継承されたものは prototypeプロパティ(サブネームスペースと呼ぶことができます)で定義されているものです。つまり、Object.prototype. で始まるもので単に Object. ではじまるものではないです。
したがって、Object.prototype.watch() , Object.prototype.valueOf() などはコンストラクタから作成された新しいオブジェクトインスタンスを含め、Object.prototypeを継承するすべてのオブジェクトタイプで使用できます。(逆にいうと Object.is() , Object.keys() およびprototypeバケット内で定義されていないほかのメンバーはObject.prototypeから継承したオブジェクトインスタンまたはオブジェクト型に継承されません。)
コンストラクタのプロパティ
すべてのコンストラクタ関数には prototype プロパティがあり、その値は constructor プロパティを含むオブジェクトです。
Person.prototype プロパティで定義されたプロパティが、Person() コンストラクタを使用して作成されたすべてのインスタンスオブジェクトで使用できるようになります。
したがって、constructor プロパティは person1 と person2 の両方のオブジェクトで使用できます。
person1.constructor
person2.constructor
と入力すると、どちらもPerson() コンストラクタを返します。ただこれの面白いのが、 constructor プロパティの末尾にカッコを入れて(パラメータを含む)コンストラクたから別のオブジェクトを作成できることです。
var person3 = new person1.constructor('Karen', 'Stephenson', 26, 'female', ['playing drums', 'mountain climbing'])
とすることで、
person3.name.first
person3.age
person3.bio()
が、person1,person2同様、Personコンストラクタで作成されたオブジェクトと同じようにアクセスができます。新しいインスタンスを作成したい場合や、何らかの理由で簡単に利用できる元のコンストラクターへの参照がない場合は本当に便利です。
constructor プロパティにはほかの用途もあり、そのインスタンスであるコンストラクタの名前を確認することができます。
// instanceName.constructor.name とかくことでアクセスできます
// "Person" と返す。(コンストラクターの名前を返す)
person1.constructor.name
プロトタイプの変更
いままで作ってきたコードの下に以下の文を追加する。
Person.prototype.farewell = function(){
alert(this.name.first + ' has left the building. Bye for now!');
}
その後、過去作成したperson1から次のように入力しても継承して動いてくれます。
person1.farewell()
これが便利なのは、継承チェーン全体が動的に更新され、コンストラクタから派生したすべてのオブジェクトインスタンスでこの新しいメソッドを自動的に利用できることです。
function Person(first, last, age, gender, interests) {
// property and method definitions
}
var person1 = new Person('Tammi', 'Smith', 32, 'neutral', ['music', 'skiing', 'kickboxing']);
Person.prototype.farewell = function() {
alert(this.name.first + ' has left the building. Bye for now!');
};
コードではコンストラクタを定義し、次にコンストラクタからインスタンスオブジェクトを作成し、コンストラクタのプロトタイプに新しいメソッドを追加します。
一般的には、コンストラクタでプロパティを定義し、プロトタイプでメソッドを定義する書き方をします。下記に示します。
// Constructor with property definitions
function Test(a, b, c, d) {
// property definitions
}
// First method definition
Test.prototype.x = function() { ... };
// Second method definition
Test.prototype.y = function() { ... };
// etc.