MENU

お知らせ:2021/2/18 ツールサイト(affi-sapo-sv.com)から、開発ノートを独立させました。

 

JavaScriptクラス構文

【JavaScript】 JSにおける継承とは何を指しているのか

更新日:2021/02/04

 

オブジェクト指向言語には重要な概念として継承があります。
JavaScriptにもありますが、他の言語とは少し異なります。

 

ここでは、JavaScriptの継承についてお伝えします。

 

 

継承とはprototypeへの参照である

 

多くの言語において継承とは、クラスに定義されているメソッド等を他クラスで引継ぎ、利用可能とする仕組みを指します。

 

しかしJavaScriptはクラスが存在せず、継承とは元となるオブジェクトのprototypeプロパティを、新しいオブジェクトのプロトタイプチェーンに組み込むことで、元となるオブジェクトの機能を使用可能にする仕組みを指します。

 

JavaScriptはclass構文が存在しますが、厳密な意味では他言語のクラスとは異なり、コンストラクター機能を持ったオブジェクトを作成します。

 

javascript 継承

 

JavaScriptの解説ではコンストラクター関数をクラスとして位置づけることが多いです。
そしてコンストラクター関数をnew演算子で呼び出すことで、インスタンスを作成すると説明されています。

 

この場合、継承とはコンストラクター関数の機能を他のコンストラクター関数に引き継ぐことになります。

 

しかしnew演算子の機能の一つは、コンストラクター関数のprototypeプロパティを、新しいオブジェクトのプロトタイプチェーンに組み込むことです。

 

コンストラクター関数からインスタンスを作成

 


function a(){};
a.prototype.method = function(){};
  // a{ 
  //    prototype:{ method : function(){} }
  // }

const obj = new a;
  // obj{ 
  //    [[プロトタイプ]]:a.prototype ← コンストラクター関数aを継承している
  // }

 

 

つまりクラス一つで継承していることになります。

 

つじつまが合わなくなるので、JavaScriptに他言語のクラスを持ち込まない方がいいですね。

JavaScriptの継承パターン

 

JavaScriptは、コンストラクター関数からインスタンスを作成することを継承と呼ぶことができますが、ここでは多階層の継承パターンを考えてみます。

 

class構文を使用した多階層継承

 

class構文で多階層の継承をおこなう場合、extendsキーワードを使用します。

 


class a{
    constructor() { this.a = "class a"; }
    methodA(){ return this.a; }
}
class b extends a{
    constructor() { super(); this.b = "class b"; }
    methodB(){ return this.b; }
}
class c extends b{
    constructor() { super(); this.c = "class c"; }
    methodC(){ return this.c;}
}

const obj = new c;

 

上のコードを実行すると、次のようなオブジェクトが作成されます。

 


obj: {
    a:  "class a"
    b:  "class b"
    c:  "class c"

    [[プロトタイプ]]: {
        methodC : function(){ return this.c; }

        [[プロトタイプ]]: {
            methodB : function(){ return this.b; }

            [[プロトタイプ]]: {
                methodA : function(){ return this.a; }
            }
        }
    }
}

 

class構文で定義したメソッドが、プロトタイプチェーン内でネストされているのがわかります。

 

このような構造が簡単に構築できるのが、class構文の少ない利点の一つです。

 

コンストラクター関数を使用した多階層継承

 

上と同じ動作をコンストラクター関数で行ってみます。

 


function a(){
    this.a = "class a";
}
a.prototype.methodA = function (){ return this.a; };

function b(){
    a.call(this);  // (B)通常の関数として呼び出す
    this.b = "class b";
}
b.prototype.methodB = function (){ return this.b; };
Object.setPrototypeOf( b.prototype, a.prototype ); // (A)

function c(){
    b.call(this);  // (B)通常の関数として呼び出す
    this.c = "class c";
}
c.prototype.methodC = function (){ return this.c; };
Object.setPrototypeOf( c.prototype, b.prototype ); //  (A)

const obj = new c;

 

まず注目するのが、コメントで(A)としてある行です。
この2行を実行することでコンストラクター関数cのprototypeプロパティ内に、プロトタイプチェーンのネスト構造が構築されます。

 


c.prototype:{
    methodC : function(){ return this.c; }

        [[プロトタイプ]]: { // b.prototypeへの参照
            methodB : function(){ return this.b; }

            [[プロトタイプ]]: { // a.prototypeへの参照
                methodA : function(){ return this.a; }
            }
        }
    }
}

 

次にコンストラクター関数cをnew演算子で呼び出します。

 

関数cでは、関数bをコンストラクター関数ではなくて、関数として呼び出しています。
この時点で既にプロトタイプチェーンが構築済みなので、関数bにはthis値のセットのみを任せれば十分なためです。

 

ただし関数cのthisにセットしてもらいたいので、callメソッドを使用しています。

記事の内容について

 

こんにちはけーちゃんです。
説明するのって難しいですね。


「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

そんなときは、ご意見もらえたら嬉しいです。

ご意見はこちら。
https://note.affi-sapo-sv.com/info.php

【お願い】

お願い

■このページのURL


■このページのタイトル


■リンクタグ