JavaScriptプログラムにおける継承特性の実現方法のまとめ

9576 ワード

JavaScriptのすべての対象について説明すると、自分の継承チェーンがあります.つまり、オブジェクトごとに別のオブジェクトを継承し、そのオブジェクトを「原型」オブジェクトと呼びます.nullだけ除いて、自分の原型の対象がありません.
プロトタイプオブジェクトの重要性は、AオブジェクトがBオブジェクトのプロトタイプであれば、BオブジェクトはAオブジェクトのすべての属性と方法を得ることができるということです.Object.get Prottypof法は、現在のオブジェクトの原型オブジェクトを取得するために使用されます.

var p = Object.getPrototypeOf(obj);
上のコードでは、オブジェクトpがオブジェクトobjの原型オブジェクトです.
Object.createメソッドは、新しいオブジェクトを生成し、指定されたオブジェクトを継承するために使用されます.

var obj = Object.create(p);
上のコードの中で新たに生成されたobjオブジェクトの原型がオブジェクトpです.
非標準的な_うプロト.属性(前後各2つの下線)は、あるオブジェクトの原型オブジェクトを書き換えることができます.しかし、この属性はできるだけ少なくするべきで、Object.get Prottypeof()とObject.set ProttypeOf()を使って、原型の対象の読み書き操作を行うべきです.

var obj = {};
var p = {};

obj.__proto__ = p;
Object.getPrototypeOf(obj) === p // true

上のコードは___u uを通りますプロト.属性は、pオブジェクトをobjオブジェクトの原型とします.
次は実際の例です.

var a = {x: 1};
var b = {__proto__: a};
b.x // 1
上のコードの中で、bオブジェクトは__u u uを通過します.プロト.属性は、自分のプロトタイプのオブジェクトをaオブジェクトに設定するので、bオブジェクトは、aオブジェクトの属性と方法のすべてを取得することができます.b対象自体はx属性ではないが、JavaScriptエンジンは__uを通過する.プロト.属性は、そのプロトタイプのオブジェクトaを見つけて、aのx属性を読みます.
newコマンドは、構造関数によってインスタンスオブジェクトを新規作成し、実際にはインスタンスオブジェクトのプロトタイプバインディング構造関数のプロトタイプ属性をインスタンスオブジェクトに実行します.

var o = new Foo();

//    
var o = new Object();
o.__proto__ = Foo.prototype;
Foo.call(o);

原型対象は自分のプロト.属性は、他のオブジェクトを指すこともでき、「プロトタイプチェーン」を段階的に形成します.

var a = { x: 1 };
var b = { __proto__: a };
var c = { __proto__: b };

c.x // 1

注意したいのは、一級上のプロトタイプチェーンである属性を探しています.性能に影響があります.探した属性は上層にあるプロトタイプのオブジェクトで、性能に対する影響が大きいです.存在しない属性を探すと、プロトタイプチェーン全体を巡回します.
thisの動作は、thisがどこで定義されていても、使う時は常に現在のオブジェクトを指し、原型のオブジェクトではありません.

var o = {
 a: 2,
 m: function(b) {
  return this.a + 1;
 }
};

var p = Object.create(o);
p.a = 12;

p.m() // 13

上のコードの中で、pオブジェクトのmメソッドはそのプロトタイプのオブジェクトoから来ています.この場合、mメソッド内部のthisオブジェクトは、oではなく、pを指します.
コンストラクタの継承については、この小節を紹介します.どのようにコンストラクタを与え、もう一つのコンストラクタを継承しますか?
Shape構造関数があると仮定した.

function Shape() {
 this.x = 0;
 this.y = 0;
}

Shape.prototype.move = function (x, y) {
 this.x += x;
 this.y += y;
 console.info('Shape moved.');
};
Rectangle      Shape。

function Rectangle() {
 Shape.call(this); //         
}
//      
function Rectangle() {
 this.base = Shape;
 this.base();
}

//          
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

rect instanceof Rectangle // true
rect instanceof Shape // true

rect.move(1, 1) // 'Shape moved.'

上のコードによると、構造関数の継承は二つの部分に分けられています.一部は子類が父類を呼び出す構造方法で、もう一つは子類の原型が父類の原型を指しています.
上のコードのうち、子類は親類を全体的に継承します.場合によっては、1つの方法の継承のみが必要となる場合は、次のような書き方をすることができます.

ClassB.prototype.print = function() {
 ClassA.prototype.print.call(this);
 // some code
}
上記のコードの中で、サブクラスBのprint方法は、先に親クラスAのprint方法を呼び出して、自分のコードを展開します.これは親Aのprint方法を継承したことに等しい.
同前プロト.プロパティプロト.属性は現在のオブジェクトのプロトタイプオブジェクト、すなわち構造関数のプロトタイプ属性を指します.

var obj = new Object();

obj.__proto__ === Object.prototype
// true
obj.__proto__ === obj.constructor.prototype
// true

上のコードはまず新しいオブジェクトobjを作成しました.プロト.属性は、コンストラクタ(ObjectまたはObj.com nstructor)のプロポーチ属性を指します.ですから、両者を比較した後、trueに戻ります.
したがって、インスタンスオブジェクトobjのプロトタイプオブジェクトを取得するには、3つの方法がある.
  • obj._プロト.
  • obj.com nstructor.prototype
  • Object.get ProttypeOf(Obj)
  • 上の3つの方法のうち、前の2つはあまり信頼できません.最新のES 6標準規定は、__uプロト.属性はブラウザのみで展開が必要です.他の環境は展開しなくてもいいです.Obj.com nstructor.prototypeは、原型のオブジェクトを手動で変更した場合、無効になる場合があります.
    
    var P = function () {};
    var p = new P();
    
    var C = function () {};
    C.prototype = p;
    var c = new C();
    
    c.constructor.prototype === p // false
    
    
    上のコードの中でCコンストラクタのプロトタイプのオブジェクトがpに変更されましたが、c.com nstructor.prototypeが歪んでしまいました.ですから、原型の対象を変える時は、コンストラクタの属性を同時に設定するのが一般的です.
    
    C.prototype = p;
    C.prototype.constructor = C;
    
    c.constructor.prototype === p // true
    
    
    そこで、3つ目のObject.get ProttypeOf法を使って、原型オブジェクトを取得することを推奨します.この方法の使い方は以下の通りです.
    
    var o = new Object();
    
    Object.getPrototypeOf(o) === Object.prototype
    // true
    
    
    Object.get ProttypeOfメソッドを使って、ブラウザがサポートされているかどうかを確認します.プロト.属性は、旧式のブラウザではサポートされていません.
    
    Object.getPrototypeOf({ __proto__: null }) === null
    
    上のコードは一つの対象の__u uを表します.プロト.属性をnullに設定し、Object.get ProttypeOf法でこのオブジェクトの原型を取得し、nullに等しいかどうかを判断します.現在の環境がサポートされればプロト.属性、両者の比較結果はtrueであるべきです.
    同前ができたプロト.属性は、インスタンスオブジェクトのプロトタイプを簡単に設定することができます.machine、vehicle、carの3つのオブジェクトがあると仮定して、その中のmachineはvehicleの原型で、vehicleはcarの原型で、2つのコードだけで設定できます.
    
    vehicle.__proto__ = machine;
    car.__proto__ = vehicle;
    
    以下は一例です.プロト.属性とconstructor.prototype属性の2つの方法で、それぞれ原型オブジェクトに定義されている属性を読みだします.
    
    Array.prototype.p = 'abc';
    var a = new Array();
    
    a.__proto__.p // abc
    a.constructor.prototype.p // abc
    
    
    あきらかにプロト.もっと簡潔に見える.
    コンストラクタによってインスタンスオブジェクトを生成する場合、インスタンスオブジェクトの__u uプロト.属性は自動的にコンストラクタのプロトタイプオブジェクトを指します.
    
    var f = function (){};
    var a = {};
    
    f.prototype = a;
    var o = new f();
    
    o.__proto__ === a
    // true
    
    
    属性の継承属性は2つに分類されます.一つは対象自身の原生属性、もう一つは原型から継承された属性です.
    オブジェクトの原生属性オブジェクト自体の属性は、Object.getOwn PropertyNames法で取得できます.
    
    Object.getOwnPropertyNames(Date)
    // ["parse", "arguments", "UTC", "caller", "name", "prototype", "now", "length"]
    
    対象自体の属性の中には、列挙できるものもあり、列挙できないものもあります.それらの列挙可能な属性だけを取得してObject.keys方法を使用します.
    
    Object.keys(Date) // []
    hasOwnProperty()
    
    ハスOwnProperty方法は、ある属性がオブジェクト自身に定義されているか、それともプロトタイプチェーンに定義されているかを判断するためのブール値を返します.
    
    Date.hasOwnProperty('length')
    // true
    
    Date.hasOwnProperty('toString')
    // false
    
    
    ハスOwnProperty方法はJavaScriptの中で唯一の処理対象属性の場合、プロトタイプチェーンを巡回しない方法です.
    オブジェクトの継承属性はObject.creat法で作成したオブジェクトで、すべての原型オブジェクトの属性を継承します.
    
    var proto = { p1: 123 };
    var o = Object.create(proto);
    
    o.p1 // 123
    o.hasOwnProperty("p1") // false
    
    
    すべての属性を取得して、オブジェクトがある属性を持つかどうかを判断します.
    
    "length" in Date // true
    "toString" in Date // true
    
    オブジェクトのすべてのエニュメレート・プロパティー(自分自身でも継承しても)を取得するには、for-innループが使用されます.
    
    var o1 = {p1: 123};
    
    var o2 = Object.create(o1,{
     p2: { value: "abc", enumerable: true }
    });
    
    for (p in o2) {console.info(p);}
    // p2
    // p1
    
    
    for…inサイクルで対象自身の属性を得るためには、ハスOwnProperty方法で判断してもいいです.
    
    for ( var name in object ) {
     if ( object.hasOwnProperty(name) ) {
      /* loop code */
     }
    }
    
    オブジェクトの属性をすべて取得するには、下記の関数が使用できます.
    
    function inheritedPropertyNames(obj) {
     var props = {};
     while(obj) {
      Object.getOwnPropertyNames(obj).forEach(function(p) {
       props[p] = true;
      });
      obj = Object.getPrototypeOf(obj);
     }
     return Object.getOwnPropertyNames(props);
    }
    
    使い方は以下の通りです
    
    inheritedPropertyNames(Date)
    // ["caller", "constructor", "toString", "UTC", "call", "parse", "prototype", "__defineSetter__", "__lookupSetter__", "length", "arguments", "bind", "__lookupGetter__", "isPrototypeOf", "toLocaleString", "propertyIsEnumerable", "valueOf", "apply", "__defineGetter__", "name", "now", "hasOwnProperty"]
    
    対象のコピーをコピーするには、次の二つのことが必要です.
    コピーしたオブジェクトが元のオブジェクトと同じプロトタイプのオブジェクトを持つことを確認します.コピーしたオブジェクトが元のオブジェクトと同じ属性を持つことを確認します.以下は上の2点に基づいて作成したオブジェクトコピーの関数です.
    
    function copyObject(orig) {
     var copy = Object.create(Object.getPrototypeOf(orig));
     copyOwnPropertiesFrom(copy, orig);
     return copy;
    }
    
    function copyOwnPropertiesFrom(target, source) {
     Object
     .getOwnPropertyNames(source)
     .forEach(function(propKey) {
      var desc = Object.getOwnPropertyDescriptor(source, propKey);
      Object.defineProperty(target, propKey, desc);
     });
     return target;
    }
    
    
    多重継承JavaScriptは複数の継承機能を提供しません.つまり、一つのオブジェクトが複数のオブジェクトを同時に継承することはできません.しかし、この機能は、融通方法により実現できます.
    
    function M1(prop) {
     this.hello = prop;
    }
    
    function M2(prop) {
     this.world = prop;
    }
    
    function S(p1, p2) {
     this.base1 = M1;
     this.base1(p1);
     this.base2 = M2;
     this.base2(p2);
    }
    S.prototype = new M1();
    
    var s = new S(111, 222);
    s.hello // 111
    s.world // 222
    
    
    上のコードでは、サブクラスSが親M 1とM 2を同時に継承しています.もちろん、継承チェーンから見ると、Sは親クラスM 1だけであるが、Sの例ではM 1とM 2の構造関数を同時に実行するので、これらの2つの方法を同時に継承している.