JavaScript継承方式(12種類)まとめとまとめ

15355 ワード

本稿では,継承を実現するための12種類のJavaScriptの方法をまとめ,それらは大きく2つに分類できる:構築器に基づいて動作するモード;オブジェクトに基づいて動作するモード.
また、これらのモードは、プロトタイプを使用するかどうか、プロトタイプを使用するかどうか、属性コピーを実行するかどうか.     ◆両方あります(すなわち、プロトタイプ属性コピーを実行します).
1.プロトタイプチェーン法(従来のもの)●コンストラクタに基づいて動作するモード ●プロトタイプチェーンモードを使用する(方法と属性セットの再利用可能な部分をプロトタイプチェーンに移行し、再利用できない部分をオブジェクトの自己属性に設定することができる).サンプルコードは次のとおりです.
function Shape () {}
    Shape.prototype.name = 'Shape';
    Shape.prototype.toString = function () {
    return this.name; 
}

function TwoDShape () {}
//        ,            
TwoDShape.prototype = new Shape()
/*
 *            ,      
 *constructor                  
 */
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.prototype.name = '2D shape';

function Triangle (side, height) {
    /*
     *   new Triangle()        
     *               ,      
     *side height             ,           
     */
    this.side = side;
    this.height = height; 
}
//                 
Triangle.prototype = new TwoDShape()
Triangle.prototype.constructor = Triangle;

Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function () {
    return this.side * this.height / 2;  
}

//  
var my = new Triangle(5, 10);
console.log(my.getArea());
console.log(my.toString());
//    hasOwnProperty()                  
console.log(my.hasOwnProperty('side'));
console.log(my.hasOwnProperty('name'));
console.log(TwoDShape.prototype.isPrototypeOf(my));
console.log(my instanceof Shape);


2.プロトタイプ継承法のみ●コンストラクタベースで動作するモード ●プロトタイプコピーモード(プロトタイプチェーンは存在せず、すべてのオブジェクトが1つのプロトタイプオブジェクトを共有する)(このモードでは、継承関係を構築するときに新しいオブジェクトインスタンスを作成する必要がなく、効率的に表現できます.プロトタイプチェーンが存在しないため、プロトタイプチェーン上のクエリーも高速になります.欠点:サブオブジェクトの変更が親に影響します).サンプルコードは次のとおりです.
function Shape() {}
/*                      ,           
*                  。                
*      Shape.prototype    new Shape()          
*  new Shape
*/
Shape.prototype.name = 'shape';
Shape.prototype.toString = function () {
     return this.name;      
};

function TwoDShape () {}
/*
*              ,      Shape.prototype    
*new Shape()          .  new Shape()    Shape     
*       ,          (           )
*/
TwoDShape.prototype = Shape.prototype;//             
TwoDShape.prototype.constructor = TwoDShape;//constructor      
TwoDShape.prototype.name = '2D shape';

function Triangle (side, height) {
     this.side = side;
     this.height = height;
}
Triangle.prototype = TwoDShape.prototype;
Triangle.prototype.constructor = Triangle;//constructor      
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function () {
    return this.side * this.height / 2; 
}

//  
var my = new Triangle(5, 10);
console.log(my.getArea());
console.log(my.toString());


3.テンポラリコンストラクタ法(1番との違いは、親の自身の属性が継承されていないこと)●コンストラクタに基づいて動作するモード ●プロトタイプチェーンモードを使用する(親のプロトタイプ属性のみを継承し、このモードは親へのアクセスに便利な方法、すなわちuber属性を提供する)
前述したように、すべてのprototypeプロパティが同じオブジェクトを指している場合、親オブジェクトはサブオブジェクトプロパティの影響を受けます.この問題を解決するには、あるブローカを使用して連鎖関係を打破します.ブローカとして一時コンストラクタ関数を使用できます.例コードは次のとおりです.
//              ,        ,                 
function extend(Child, Parent) {
    /*
     *       F(),             。      
     *new F()                ,      
     *   prototype        
     */
    var F = function () {};//            
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;//constructor    
    /*
     *             uber  ,            
     *  ,                 
     */
    Child.uber = Parent.prototype;
}

function Shape () {}
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
    return this.constructor.uber ? this.constructor.uber.toString()
             + ', ' + this.name : this.name; 
};

function TwoDShape () {}
extend(TwoDShape, Shape);
TwoDShape.prototype.name = '2D Shape';

function Triangle (side, height) {
    this.side = side;
    this.height = height; 
}
extend(Triangle, TwoDShape);
Triangle.prototype.name = 'Triangle';
Triangle.prototype.getArea = function () {
    return this.side * this.height / 2; 
}

//  
var my = new Triangle(5, 10);
console.log(my.getArea());
console.log(my.toString());


4.プロトタイプ属性コピー法●コンストラクタ動作に基づくモード ●コピー属性モード ●プロトタイプコピーモード(親オブジェクトプロトタイプの内容はすべて子オブジェクトプロトタイプ属性に変換され、継承のために個別にオブジェクトインスタンスを作成する必要がなく、プロトタイプチェーン自体もより短い)
従来の方法と比較して、この方法は効率的にはやや劣っています.単純なプロトタイプチェーンクエリーではなく、サブオブジェクトプロトタイプを1つずつコピーするためです.この方法は、基本データ型のみを含むオブジェクトにのみ適用され、すべてのオブジェクトタイプ(関数と配列を含む)は参照伝達をサポートするため、コピーできません.サンプルコードは次のとおりです.
function extend2 (Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    /*
     *            ,                
     *         ,      ,                 
     */
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p;
    /*
     *         Child       ,       Child.prototype.constructor
     *            ,    constructor           
     */
}

Shape = function () {};
Shape.prototype.name = 'shape';
Shape.prototype.toString = function () {
    return this.uber ? this.uber.toString() + ', ' + this.name : this.name;
};

var TwoDShape = function () {};
//         toString()  ,          ,            
extend2(TwoDShape, Shape);

//  
var td = new TwoDShape();
console.log(td.__proto__.hasOwnProperty('name'));
console.log(td.__proto__.hasOwnProperty('toString'));
//   toString()             
console.log(td.__proto__.toString === Shape.prototype.toString);
console.log(td.toString());
TwoDShape.prototype.name = '2D shape';
console.log(td.toString());


5.全属性コピー法(すなわち、浅いコピー法)●オブジェクトベース動作モード ●属性コピーモード(プロトタイプ属性を使用していない)
これまでの方法は、コンストラクタがオブジェクトを作成することを前提としており、他のコンストラクタから継承されたプロパティが、オブジェクトを作成するコンストラクタに導入されていました.実際には、コンストラクタを捨てて、オブジェクト識別法によって直接オブジェクトを作成することもでき、実際の入力を減らすことができます.サンプルコードは次のとおりです.
function extendCopy(p) {
    //             “ ”    “  ”,          
    var c = {};
    for (var i in p) {//              
        c[i] = p[i];
    }
    c.uber = p;
    return c;
}

var shape = {
    name: 'Shape',
    toString: function () {
        return this.name; 
    }
}

var twoDee = extendCopy(shape);
twoDee.name = '2D shape';
twoDee.toString = function () {
    return this.uber.toString() + ', ' + this.name; 
}

var triangle = extendCopy(twoDee);
triangle.name = 'Triangle';
triangle.getArea = function () {
    return this.side * this.height / 2;  
}

//  
triangle.side = 5;
triangle.height = 10;
console.log(triangle.getArea());
console.log(triangle.toString());

6.ディープコピー法●対象作業に基づくモード ●属性コピーモード
ライト・コピー:コピー・オブジェクトが変更された場合は、元のオブジェクトが変更された場合と同じです.この問題を回避するには、ディープ・コピーを使用します.実装方法は、5番の方法(ライト・コピー)と似ていますが、すべてのオブジェクトが値伝達を実行します.つまり、オブジェクトの参照プロパティに遭遇した場合、もう一度ディープ・コピー関数を呼び出す必要があります.サンプル・コードは次のとおりです.
function extendCopy (p) { 
    var c = {};
    for (var i in p) {
       c[i] = p[i];
    }
    c.uber = p;
       return c;
    }

    function deepCopy (p, c) {
       c = c || {};
       for (var i in p) {
           if (p.hasOwnProperty(i)) {//              
              if (typeof p[i] === 'object') {
                 //             ,             
                 c[i] = Array.isArray(p[i]) ? [] : {};
                    deepCopy(p[i], c[i]);
               }
               else {
                  c[i] = p[i];
               }
            }
        }
        return c;
    }          

    var parent = {
        numbers: [1, 2, 3],
        letters: ['a', 'b', 'c'],
        obj: {
            prop: 1
        },
        bool: true
    }

    //  (         )
    var mydeep = deepCopy(parent);
    var myshallow = extendCopy(parent);
    mydeep.numbers.push(4, 5, 6);
    console.log(mydeep.numbers);
    console.log(parent.numbers);
    myshallow.numbers.push(10);
    console.log(myshallow.numbers);
    console.log(parent.numbers);
    console.log(mydeep.numbers);


7.プロトタイプ継承法(親オブジェクトを子オブジェクトに設定したプロトタイプ)●オブジェクトに基づいて動作するモード ●プロトタイプチェーンモードを使用する(模倣系機構を無くし、直接オブジェクト間で継承関係を構築する.プロトタイプ固有の利点を発揮する).サンプルコードは以下の通りである.
function extendCopy(p) {
    var c = {};
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p;
    return c;
}
var shape = {
    name: 'Shape',
    toString: function() {
        return this.name;
    }
}
var twoDee = extendCopy(shape);
twoDee.name = '2D shape';
twoDee.toString = function() {
    return this.uber.toString() + ', ' + this.name;
}

// function object(o) {
//     function F () {}
//     F.prototype = o;
//     return new F();
// } 
/*
 *      uber  ,    object()  
 */
function object(o) { //     
    var n;
    function F() {}
    F.prototype = o; //         
    n = new F();
    n.uber = o;
    return n;
}

var triangle = object(twoDee);
triangle.name = 'Triangle';
triangle.getArea = function() {
    return this.side * this.height / 2;
};

//  
console.log(triangle.toString());

8.拡張モードと拡張モード(プロトタイプ継承と属性コピー)●オブジェクトベースで動作するモード ●プロトタイプチェーンモード ●属性コピーモード(実際には7番方法と5番方法の混合応用であり、1つの関数でオブジェクトの継承と拡張を一度に完了する).サンプルコードは以下の通りである.
継承の主な目標は、既存の機能を自分のものにすることです.つまり、新しいオブジェクトを作成するときは、通常、既存のオブジェクトに継承してから、追加のメソッドとプロパティを追加する必要があります.
function objectPlus (o,stuff) {//  o    ,     stuff          
    var n;
    function F () {}
    F.prototype = o;//              (       )
    n = new F();

    n.uber = o;
    for (var i in stuff) {//                 
        n[i] = stuff[i];
    }

    return n;
}

var shape = {
    name: 'shape',
    toString: function () {
        return this.name; 
    }
}

var twoDee = objectPlus(shape, {
    name: '2D shape',
    toString: function () {
        return this.uber.toString() + ', ' + this.name; 
    }
});

var triangle = objectPlus(twoDee, {
    name: 'Triangle',
    getArea: function () {
        return this.side * this.height / 2; 
    },
    side: 0,
    height: 0
});

//  
var my = objectPlus(triangle, {
    side: 4,
    height: 4,
    name: 'My 4x4'
});
console.log(my.getArea());
console.log(my.toString());


9.多重継承法●オブジェクトに基づいて動作するモード ●属性コピーモード(親の出現順に属性フルコピーを順次実行)
多重継承とは、通常、1つのサブオブジェクトのうち1つ以上の親オブジェクトがある継承パターンを指す.多重継承の実現は簡単で、属性コピー法(5番)の継承構想を継続して順次オブジェクトを拡張するだけでよいが、パラメータで継承されたオブジェクトの数に制限はない.サンプルコードは以下の通りである.
function multi () {
    var n = {}, stuff, len = arguments.length;
    for (var i = 0; i < len; i++) {//                  
        stuff = arguments[i];
        for (var j in stuff) {//          
            if (stuff.hasOwnProperty(j)) {
                //                ,           
                n[j] = stuff[j];
            }
        }
    }
    return n;
}

//  
var shape = {
    name: 'shape',
    toString: function () {
      return this.name;
    }
};
var twoDee = {
    name: '2D shape',
    dimensions: 2
};
var triangle = multi(shape, twoDee, {
    name: 'Triangle',
    getArea: function () {
        return this.side * this.height / 2; 
    },
    side: 5,
    height: 10
});
console.log(triangle.getArea());
console.log(triangle.dimensions);
console.log(triangle.toString());


10.寄生継承法●オブジェクトに基づいて動作するモード ●プロトタイプチェーンモードを使用する(この関数は対応するオブジェクトコピーを実行し、拡張してコピーに戻る)
この方法の基本的な考え方は、オブジェクトを作成する関数で他のオブジェクトの機能を直接吸収し、拡張して返すことです.サンプルコードは次のとおりです.
function object (o) {
    var n;
    function F () {}
    F.prototype = o;
    n = new F();
    n.uber = o; 
    return n;
} 

var twoD = {
    name: '2D shape',
    dimensions: 2
};

/*
 * twoD         that   ,                ,
 *    object()          。
 *  that  ,       。  that  。
 */
function triangle(s, h) {//        ,      
    var that = object(twoD);//     that   
    //  that  ,       
    that.name = 'Triangle';
    that.getArea = function () {
        return this.side * this.height / 2;
    };
    that.side = s;
    that.height = h;
    return that;
}

//  
var t = triangle(5, 10);
console.log(t.name);
console.log(t.dimensions);
console.log(t.getArea());


11.コンストラクタ借用法●コンストラクタが動作するモードに基づく(この方法は親オブジェクトの自身の属性のみを継承することができる.1番の方法と組み合わせて使用して、プロトタイプから関連コンテンツを継承することができるが、親オブジェクトのコンストラクタが2回呼び出されるという欠点がある.サブオブジェクトが特定の属性を継承する際に最も簡単な処理方法を選択する).
この継承モードでは、サブオブジェクトコンストラクタは、call()またはapply()メソッドによって親オブジェクトのコンストラクタを呼び出すことができるため、通常、コンストラクタ盗用法(stealing a constructor)、またはコンストラクタ借用法(borrowing a constructor)と呼ばれる.この2つの方法では、指定したオブジェクトのthis値を関数の呼び出しにバインドできます.これは、継承にとって、子オブジェクトのコンストラクタが親オブジェクトを呼び出すコンストラクタであることを意味します.また、子オブジェクトに新しく作成されたthisオブジェクトを親オブジェクトのthis値にバインドすることもできます.例コードは次のとおりです.
//         Shape
    function Shape(id) {
        this.id = id;
    }
    Shape.prototype.name = 'shape';
    Shape.prototype.toString = function () {
        return this.name;  
    }

    function Triangle () {
        Shape.apply(this, arguments);//       call() apply()          
    }
    Triangle.prototype.name = 'Triangle';

    //  
    var t = new Triangle(101);
    console.log(t.name);
    console.log(t.id);
    console.log(t.toString());//    triangle                 

上記の例では、triangleオブジェクトにShapeのプロトタイプ属性が含まれていないのは、new Shape()を呼び出してインスタンスを作成していないためであり、自然にプロトタイプも使用されていないため、Triangle()コンストラクタを次のように再定義することが容易です.
    function Triangle () {
          Shape.apply(this, arguments);  
      }
      Triangle.prototype = new Shape();
      Triangle.prototype.name = 'Triangle';


この継承モードでは、親オブジェクトのプロパティは、オブジェクトの独自のプロパティとして再構築されます.これは、コンストラクタ借用法の利点です.配列または他のオブジェクトタイプに継承されたサブオブジェクトを作成すると、完全な新しい値(参照ではありません)が得られ、親オブジェクトに影響を与えません.
しかし、このモードには、親オブジェクトのコンストラクタが2回呼び出されることが多いという欠点もあります.apply()メソッドで自身のプロパティを継承する場合、newオペレータでプロトタイプを継承する場合に発生します.これにより、親オブジェクトの自己プロパティが2回継承されます.簡単な例を見てください.
function Shape (id) {
    this.id = id;
}
function Triangle () {
    Shape.apply(this, arguments);
} 
Triangle.prototype = new Shape(101);

//  
var t = new Triangle(202);
console.log(t.id);//    ,        id  ,          
//  
console.log(t.__proto__.id);
delete t.id;
console.log(t.id);


12.コンストラクタ借用と属性コピー法●コンストラクタに基づいて動作するモード ●プロトタイプチェーンモード ●属性コピーモード(本方法は11番方法と4番方法の結合体である.これにより、親オブジェクトコンストラクタを繰り返し呼び出さずに、自身の属性とプロトタイプ属性を同時に継承することができる)
11番のメソッドでコンストラクタの2重呼び出しによる重複実行の問題は、簡単に修正できます.親オブジェクトコンストラクタでapply()メソッドを呼び出して、すべての自己属性を取得し、簡単な反復器を呼び出してプロトタイプ属性を項目ごとにコピーできます(4番のメソッドで完了することもできます).サンプルコードは次のとおりです.
function extend2 (Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
        c[i] = p[i];
    }
    c.uber = p;
}
   
//         Shape
function Shape (id) {
    this.id = id;
}
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
    return this.name;
}

function Triangle () {
    Shape.apply(this, arguments); 
}
extend2(Triangle, Shape);//            
Triangle.prototype.name = 'Triangle';

//  
var t = new Triangle(101);
console.log(t.toString());
console.log(t.id);
console.log(typeof t.__proto__.id);//   "undefined",           
console.log(t.uber.name);//    ,extend2()        uber  


以上のような継承方法に直面して、どのように正しい選択をすればいいのでしょうか.実際には、開発者の設計スタイル、パフォーマンス要件、特定のプロジェクトタスクに依存します.たとえば、開発者がクラスの観点から問題を解決することに慣れている場合、コンストラクタベースのワークモデルが適しています.または、開発者がこの「クラス」に関心を持っている場合の特定のインスタンスでは、オブジェクトベースのモードが適している可能性があります.