LoginSignup
8
11

More than 5 years have passed since last update.

JavaScript のプロトタイプを理解する

Posted at

prototype はふわっと知っているけど、実はよくわかっていないので、調べて見た。

JavaScript は、クラスベースのオブジェクト指向と違って、あくまでプロトタイプベース。class キーワードがあるが、単なるシンタックスシュガーである。

prototype

継承に関して言えば、JavaScript はオブジェクトしかない。各オブジェクトはプライベートプロパティ([[Prototype]]と呼ばれる) を持っていて、これは、他のオブジェクト(これは、prototypeと呼ばれる) を持っている。プロトタイプオブジェクトは、自身にプロトタイプオブジェクトを持っている。そのチェーンが null (つまりプロトタイプオブジェクトがnull) に到達すると、そのチェーンが終了する。これをプロトタイプチェーンと呼ぶ。イメージとしてはこんな感じか?

Screen Shot 2017-09-17 at 9.30.38 AM.png

Object

JavaScript の全てのオブジェクトは、Object のインスタンスになっている。Object がプロトタイプチェーンのトップになる。JavaScript の オブジェクトは、ダイナミックなバッグみたいなもので、(own properties と呼ばれる) JavaScript のオブジェクトは、プロトタイプオブジェクトへのリンクをもつ。ある、オブジェクトのプロパティにアクセスすると、そのオブジェクトが持っているプロパティだけでなく、そのオブジェクトのプロパティになければ、プロトタイプのプロパティも参照される。そこになければ、その先のプロトタイプのプロパティ、、、といったようにそのチェーンを辿って参照する。それがnull になるまで。
 

prototype の参照

prototype は、obj.[[Prototype]] に格納されているが、それはプライベートになっている。Object.getPrototypeOf() や、Object.setPrototypeOf() 経由でアクセスされる。これは、__proto__ と同等である。

他にも、func.prototype というものもある。これは、この関数のコンストラクタを使った時に、全てのインスタンスオブジェクトに対して、[[Prototype]] をアサインするものになる。

prototype のサンプルコード

上記の理屈を理解すべく、サンプルコードを書いて見た。

var a = {
    a: 1,
    b: 2
}

var obj = Object.setPrototypeOf(a, {
    b: 3,
    c: 4
});

obj.__proto__ = {
     c: 5,
     d: 6
}

console.log(obj.a);
console.log(obj.b);
console.log(obj.c);
console.log(obj.d);
console.log(obj.e);

実行結果

1
2
5
6
undefined

Prototype のチェーンにないもの e のみが undefined になり、それ以外は、チェーンを辿って表示されている。興味深いものは、b の結果で、2番目のプロトタイプオブジェクトで、b=3 にしているが、先に、最初のオブジェクトですでに、bが存在するので、その先のチェーンまで調査されない。

メソッドと、prototype

次に、メソッドタイプのものを作成してみる。

function Graph() {
    this.vertices = [];
    this.edges = [];
}

Graph.prototype = {
    addVertex: function(v) {
        this.vertices.push(v);
    }
}

var g = new Graph();
console.log(g.hasOwnProperty('vertices'));
console.log(g.hasOwnProperty('edges'));
console.log(g.hasOwnProperty('addVertex'));
console.log(g.__proto__.hasOwnProperty('addVertex'));
console.log(Object.getPrototypeOf(g));

前回のポストでもある通り、Graph.prototype を用いると、インスタンス作成時に、毎回メソッド分のメモリが必要にならない。これは、addVertex のメソッドが、プロトタイプからのリンクになっているからで、新しいインスタンスを作っても、単にそこにリンクが貼られるだけだからだ。だから、上記のプログラムを作って、どこにプロパティがあるかを調査すると、メソッドのみが、プロトタイプに存在することがわかる。

true
true
false
true
{ addVertex: [Function: addVertex] }

試しに、同じ方式で、プロパティもいけるか試してみる。

function SomeObj(name, age) {
    this.name = name;
    this.age = age;
}

SomeObj.prototype = {
    name: "Proto desu",
    salary: 100, 
    print: function() {
        console.log(this.name + " " + this.age);
    }
}

var some = new SomeObj("Tsuyoshi", 46);
some.print();
console.log(some.salary);
console.log(Object.getPrototypeOf(some));

実行結果

Tsuyoshi 46
100
{ name: 'Proto desu', salary: 100, print: [Function: print] }

しっかり、想定通りの結果になっている。プロパティも同じ方法が使える。

まとめ

これでかなりプロトタイプに関して学べた気がします。挙動がわかってスッキリ!次は、JavaScript の関数に関して書いて見ます。

リソース

今回の記事はほぼこのページから学びました。

8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11