【JavaScript】プロトタイプ - オブジェクトの継承

【JavaScript】プロトタイプ - オブジェクトの継承

JavaScriptのプロトタイプについて解説します。

検証環境

プロトタイプ

JavaScriptはオブジェクトが他のオブジェクトから機能等を継承する(引き継ぐ)ことが可能です。

継承元のオブジェクトをプロトタイプと呼び、オブジェクトは必ずプロトタイプを持ちます。

また、プロトタイプのプロトタイプというような何層にも渡って階層化することもできます。

プロトタイプの確認

プロトタイプはオブジェクトをコンソールに出力することで確認できます。

let person = {};

console.log(person);
{}
  ___ih_hl_start
  [[Prototype]]: Object
    constructor: ƒ Object()
    hasOwnProperty: ƒ hasOwnProperty()
    ...
    省略...
  ___ih_hl_end

※ 出力結果は実行環境により異なる場合があります。

プロトタイプは[[Prototype]]: Objectの部分です。

オブジェクトはデフォルトではObjectというプロトタイプを持ちます。

また、Objectはいくつかのメソッドを持っていることが確認できます。

プロトタイプの取得

プロトタイプを取得するにはObjectクラスのgetPrototypeOfメソッドを使用します。

クラスが未学習の方はここでは記法のみ覚えていただければ問題ありません。

let person = {};

___ih_hl_start
console.log(Object.getPrototypeOf(person));
___ih_hl_end
{__defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, __lookupSetter__: ƒ, …}
  [[Prototype]]: Object
    constructor: ƒ Object()
    hasOwnProperty: ƒ hasOwnProperty()
    ...
    省略...

『プロトタイプの確認』で出力した[[Prototype]]: Objectと同等の内容が得られたことが確認できます。

プロトタイプの設定

オブジェクトのプロトタイプを任意のオブジェクトに設定できます。

設定にはObjectクラスのcreateメソッドを使用します。

クラスが未学習の方はここでは記法のみ覚えていただければ問題ありません。

let person = {
    name: "unknown",
    info: function() {
        console.log("Name : " + this.name);
    }
};

___ih_hl_start
let human = Object.create(person);
___ih_hl_end

console.log(human);
{}
  [[Prototype]]: Object
    info: ƒ ()
    name: "unknown"
    [[Prototype]]: Object

変数humanは変数personに記憶されたオブジェクトをプロトタイプとしたオブジェクトを記憶します。

出力結果のプロトタイプからnameプロパティやinfoメソッドが確認できることから、変数personのオブジェクトをプロトタイプに設定できたことが分かります。

プロパティ・メソッドの継承

オブジェクトはプロトタイプのプロパティやメソッドを継承(引き継ぎ)します。

そのため、オブジェクトからプロトタイプのプロパティやメソッドにアクセスすることが可能です。

let person = {
    name: "unknown",
    info: function() {
        console.log("Name : " + this.name);
    }
};

let human = Object.create(person);

console.log(human);

___ih_hl_start
human.info();
___ih_hl_end
{}
Name : unknown

通常、未定義のメソッドを呼び出すとエラーが発生します。

そのため、変数humanのオブジェクトからinfoメソッドを呼び出すとエラーが発生しそうですが、継承したオブジェクトにinfoメソッドがあるため正常に実行されます。

プロパティ・メソッドのアクセスでは最初にオブジェクト自身を探索し、未定義の場合はプロトタイプから探します。

もし、プロトタイプにも未定義の場合は、更にプロトタイプのプロトタイプへと階層を下げて探索します。

プロパティ・メソッドの重複

オブジェクトとプロトタイプのプロパティ名やメソッド名が重複する場合、上層の方が優先されます。

let person = {
    name: "unknown",
    info: function() {
        console.log("Name : " + this.name);
    }
};

let human = Object.create(person);
___ih_hl_start
human.name = "TANAKA";
___ih_hl_end

console.log(human);

human.info();
{name: 'TANAKA'}
  name: "TANAKA"
  [[Prototype]]: Object
    info: ƒ ()
    name: "unknown"
Name : TANAKA

9行目で変数humanのオブジェクトにnameプロパティの値を設定しています。

出力結果からinfoメソッドは変数humannameプロパティの値を参照していることが分かります。

※ プロトタイプのnameプロパティの値は変わりません。

また、メソッドにおいても同様です。

let person = {
    name: "unknown",
    info: function() {
        console.log("Name : " + this.name);
    }
};

let human = Object.create(person);
human.name = "TANAKA";
___ih_hl_start
human.info = function() {
    console.log("My name is " + this.name);
}
___ih_hl_end

console.log(human);

human.info();
{name: 'TANAKA', info: ƒ}
  info: ƒ ()
  name: "TANAKA"
  [[Prototype]]: Object
    info: ƒ ()
    name: "unknown"
My name is TANAKA

同名のメソッドを定義しつつ、プロトタイプのメソッドを呼び出す場合はプロトタイプを取得して呼び出します。

let person = {
    name: "unknown",
    info: function() {
        console.log("Name : " + this.name);
    }
};

let human = Object.create(person);
human.name = "TANAKA";
human.info = function() {
    ___ih_hl_start
    Object.getPrototypeOf(this).info();
    ___ih_hl_end
    console.log("My name is " + this.name);
}

console.log(human);

human.info();
{name: 'TANAKA', info: ƒ}
  info: ƒ ()
  name: "TANAKA"
  [[Prototype]]: Object
    info: ƒ ()
    name: "unknown"
Name : unknown
My name is TANAKA

11行目のObject.getPrototypeOf(this).info()の部分がプロトタイプの取得とメソッドの呼び出しです。

Object.getPrototypeOf(this)で取得したプロトタイプに対して、infoメソッドを呼び出しています。

このようにすることでプロトタイプのプロパティやメソッドにアクセスできますが、プロトタイプからメソッドを呼び出した場合、プロパティやメソッドはプロトタイプのモノを参照することに注意してください。

例えば、上記例では11行目のinfoメソッドはプロトタイプのnameプロパティを参照するため、値が"unknown"になっています。