クラスの継承

導入

クラスは「extends」キーワードを通じて継承を実現し、サブクラスが親クラスのプロパティとメソッドを継承できるようにします。 extends の記述方法は、ES5 のプロトタイプ チェーンの継承よりもはるかに明確で便利です。

クラスポイント{
}

class ColorPoint extends Point {
}

上記の例では、Point が親クラスであり、ColorPoint がサブクラスであり、extends キーワードを通じて Point クラスのすべてのプロパティとメソッドを継承します。ただし、コードがデプロイされていないため、2 つのクラスはまったく同じであり、これは「Point」クラスをコピーするのと同じです。

次に、「ColorPoint」内にコードを追加します。

クラスポイント { /* ... */ }

class ColorPoint extends Point {
  コンストラクター(x, y, color) {
    super(x, y); // 親クラスのコンストラクター(x, y)を呼び出します。
    this.color = 色;
  }

  toString() {
    return this.color + ' ' + super.toString() // 親クラスの toString() を呼び出します。
  }
}

上記の例では、super キーワードが constructor() メソッドと toString() メソッド内に表示されます。ここでの「super」は、親クラスのインスタンス オブジェクトを作成するために使用される親クラスのコンストラクターを表します。

ES6 では、サブクラスが constructor() メソッドで super() を呼び出す必要があると規定しています。そうしないと、エラーが報告されます。これは、サブクラス独自の「this」オブジェクトを、まず親クラスのコンストラクターを通じて整形して、親クラスと同じインスタンス プロパティとメソッドを取得し、次に処理してサブクラス独自のインスタンス プロパティとメソッドを追加する必要があるためです。 super() メソッドが呼び出されない場合、サブクラスは独自の this オブジェクトを取得しません。

クラスポイント { /* ... */ }

class ColorPoint extends Point {
  コンストラクター() {
  }
}

let cp = new ColorPoint(); // ReferenceError

上記のコードでは、ColorPoint は親クラス Point を継承しますが、そのコンストラクターは super() を呼び出していないため、新しいインスタンスの作成時にエラーが発生します。

サブクラスのコンストラクターはなぜ super() を呼び出す必要があるのでしょうか?その理由は、ES6 の継承メカニズムが ES5 とはまったく異なるためです。 ES5 の継承メカニズムは、最初に独立したサブクラスのインスタンス オブジェクトを作成し、次にこのオブジェクトに親クラスのメソッドを追加します。つまり、「インスタンスが最初、継承が最後」です。 ES6 の継承メカニズムは、まず親クラスの属性とメソッドを空のオブジェクトに追加し、次にそのオブジェクトをサブクラスのインスタンスとして使用します。つまり、「継承が最初、インスタンスが最後」です。このステップが親クラスを継承する this オブジェクトを生成するため、ES6 の継承では最初に super() メソッドを呼び出す必要があるのはこのためです。このステップがないと親クラスを継承できません。

これは、新しいサブクラス インスタンスを作成するときに、親クラスのコンストラクターを最初に実行する必要があることを意味することに注意してください。

クラス Foo {
  コンストラクター() {
    コンソール.ログ(1);
  }
}

クラス Bar extends Foo {
  コンストラクター() {
    素晴らしい();
    コンソール.ログ(2);
  }
}

const bar = 新しい Bar();
// 1
// 2

上記の例では、サブクラス Bar が新しいインスタンスを作成すると、1 と 2 が出力されます。その理由は、サブクラスのコンストラクターが super() を呼び出すと、親クラスのコンストラクターが 1 回実行されるためです。

もう 1 つ注意すべきことは、サブクラスのコンストラクターでは、this キーワードは super() を呼び出した後にのみ使用でき、それ以外の場合はエラーが報告されるということです。これは、サブクラス インスタンスの構築では、まず親クラスの継承を完了する必要があるため、サブクラス インスタンスが親クラスを継承できるのは「super()」メソッドだけです。

クラスポイント{
  コンストラクター(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  コンストラクター(x, y, color) {
    this.color = color; // 参照エラー;
    スーパー(x, y);
    this.color = 色; //
  }
}

上記のコードでは、サブクラスの constructor() メソッドが super() を呼び出す前に this キーワードを使用しており、エラーが報告されていますが、これを super() の後に置くのは正しいです。

サブクラスが constructor() メソッドを定義していない場合、このメソッドがデフォルトで追加され、その中で super() が呼び出されます。言い換えれば、明示的に定義されているかどうかに関係なく、どのサブクラスにも constructor() メソッドがあります。

class ColorPoint extends Point {
}

// と同等
class ColorPoint extends Point {
  コンストラクター(...args) {
    super(...args);
  }
}

サブクラスを定義すると、サブクラスのインスタンスを生成できます。

cp = new ColorPoint(25, 8, 'green'); とします。

cp instanceof ColorPoint // true
cpinstanceofPoint // true

上記の例では、インスタンス オブジェクト cp は、ColorPoint クラスと Point クラスの両方のインスタンスであり、ES5 の動作と完全に一致しています。

プライベート プロパティとプライベート メソッドの継承

プライベート プロパティとメソッドを除き、親クラスのすべてのプロパティとメソッドがサブクラスに継承されます。

サブクラスは親クラスのプライベート プロパティを継承できません。つまり、プライベート プロパティは、サブクラスが定義されているクラス内でのみ使用できます。

クラス Foo {
  #p = 1;
  #m() {
    console.log('こんにちは');
  }
}

クラス Bar extends Foo {
  コンストラクター() {
    素晴らしい();
    console.log(this.#p); // エラーレポート
    this.#m(); // エラーを報告する
  }
}

上記の例では、サブクラス Bar が親クラス Foo のプライベート プロパティまたはプライベート メソッドを呼び出すと、エラーが報告されます。

親クラスがプライベート プロパティの読み取りおよび書き込みメソッドを定義している場合、サブクラスはこれらのメソッドを通じてプライベート プロパティの読み取りおよび書き込みを行うことができます。

クラス Foo {
  #p = 1;
  getP() {
    これを返します。#p;
  }
}

クラス Bar extends Foo {
  コンストラクター() {
    素晴らしい();
    console.log(this.getP()); // 1
  }
}

上記の例では、getP() は親クラスがプライベート プロパティを読み取るために使用するメソッドであり、このメソッドを通じてサブクラスは親クラスのプライベート プロパティを読み取ることができます。

静的プロパティと静的メソッドの継承

親クラスの静的プロパティと静的メソッドもサブクラスに継承されます。

クラスA {
  静的 hello() {
    console.log('hello world');
  }
}

クラス B は A を拡張します {
}

B.hello() // こんにちは、世界

上記のコードでは、「hello()」はクラス「A」の静的メソッドであり、「B」は「A」を継承し、「A」の静的メソッドも継承します。

静的プロパティは浅いコピーの実装を通じて継承されることに注意してください。

クラス A { 静的 foo = 100 }
クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    B.foo--;
  }
}

const b = 新しい B();
B.foo // 99
A.foo // 100

上の例では、foo はクラス A の静的プロパティです。クラス B はクラス A を継承するため、このプロパティも継承します。ただし、クラス B 内で静的プロパティ B.foo を操作しても、A.foo には影響しません。その理由は、クラス B が静的プロパティを継承するときに、浅いコピーを使用して静的プロパティの値をコピーするためです。したがって、A .fooB.foo は 2 つの独立したプロパティです。

ただし、このコピーは浅いコピーであるため、親クラスの静的プロパティの値がオブジェクトの場合、浅いコピーはメモリ アドレスのみをコピーするため、サブクラスの静的プロパティもこのオブジェクトを指します。オブジェクト。

クラスA {
  静的 foo = { n: 100 };
}

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    B.foo.n--;
  }
}

const b = 新しい B();
B.foo.n // 99
A.foo.n // 99

上記の例では、A.foo の値はオブジェクトであり、浅いコピーによって B.fooA.foo が同じオブジェクトを指すようになります。したがって、サブクラス B がこのオブジェクトの属性値を変更すると、親クラス A に影響します。

Object.getPrototypeOf()

Object.getPrototypeOf() メソッドを使用して、子クラスから親クラスを取得できます。

クラス ポイント { /*...*/ }

class ColorPoint extends Point { /*...*/ }

Object.getPrototypeOf(ColorPoint) === ポイント
// 真実

したがって、このメソッドを使用して、クラスが別のクラスを継承しているかどうかを判断できます。

スーパーキーワード

キーワード super は関数またはオブジェクトとして使用できます。どちらの場合も、その使い方はまったく異なります。

最初のケースでは、super が関数として呼び出されるとき、それは親クラスのコンストラクターを表します。 ES6 では、サブクラスのコンストラクターが super() 関数を 1 回実行する必要があります。

クラス A {}

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
  }
}

上記のコードでは、サブクラス B のコンストラクター内の super() は、親クラスのコンストラクターを呼び出すことを表しています。これは必須です。そうでないとエラーが報告されます。

super() を呼び出す機能は、サブクラスの this オブジェクトを形成し、親クラスのインスタンス属性とメソッドをこの this オブジェクトに配置することです。サブクラスには、「super()」を呼び出す前に「this」オブジェクトがありません。また、「this」に対する操作はすべて「super()」の後に配置する必要があります。

ここでの super は親クラスのコンストラクターを表しますが、サブクラスの this (つまり、サブクラスのインスタンス オブジェクト) を返すため、super 内の this は、 subclass. 、親クラスのインスタンスの代わりに、ここでの super()A.prototype.constructor.call(this) と同等です (子クラスの this で親クラスのコンストラクターを実行します)。

クラスA {
  コンストラクター() {
    console.log(新しい.ターゲット名);
  }
}
クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
  }
}
new A() // A
new B() // B

上の例では、「new.target」は現在実行中の関数を指します。 super() が実行されると (new B())、親クラス A のコンストラクターではなく、サブクラス B のコンストラクターを指すことがわかります。言い換えれば、「super()」内の「this」は「B」を指します。

ただし、サブクラスのコンストラクタ内で super() が実行されるため、まだサブクラスのプロパティやメソッドが this にバインドされていないため、同名のプロパティが存在する場合、この時点で得られるものは親クラスのプロパティ。

クラスA {
  名前 = 'A';
  コンストラクター() {
    console.log('私の名前は ' + this.name);
  }
}

クラス B は A を拡張します {
  名前 = 'B';
}

const b = new B(); // 私の名前は A です。

上記の例では、出力の最後の行は B ではなく A です。これは、super() が実行されたときに、Bname 属性が this にバインドされていないためです。したがって、this .name はクラス Aname 属性を取得します。

関数としての super() はサブクラスのコンストラクター内でのみ使用でき、他の場所で使用するとエラーが報告されます。

クラス A {}

クラス B は A を拡張します {
  m() {
    super(); // エラーを報告する
  }
}

上記のコードでは、クラス Bm メソッドで super() が使用されているため、構文エラーが発生します。

2 番目のケースでは、super がオブジェクトとして使用される場合、通常のメソッドでは親クラスのプロトタイプ オブジェクトを指しますが、静的メソッドでは親クラスを指します。

クラスA {
  p() {
    2を返します。
  }
}

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    console.log(super.p()); // 2
  }
}

b = 新しい B(); とします。

上記のコードでは、サブクラス Bsuper.p() がオブジェクトとして super を使用しています。このとき、通常のメソッドでは super は A.prototype を指しているので、 super.p() は A.prototype.p() と等価になります。

ここで、「super」は親クラスのプロトタイプオブジェクトを指しているため、親クラスのインスタンスに定義されているメソッドやプロパティは「super」を介して呼び出すことができないことに注意してください。

クラスA {
  コンストラクター() {
    this.p = 2;
  }
}

クラス B は A を拡張します {
  get m() {
    super.p を返します。
  }
}

b = 新しい B(); とします。
b.m // 未定義

上記のコードでは、p は親クラス A のインスタンスの属性であり、super.p はそれを参照できません。

親クラスのプロトタイプオブジェクトにプロパティが定義されている場合は、「super」を取得できます。

クラス A {}
A.prototype.x = 2;

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    console.log(super.x) // 2
  }
}

b = 新しい B(); とします。

上記のコードでは、属性 xA.prototype で定義されているため、super.x はその値を取得できます。

ES6 では、サブクラスの通常メソッドの super を介して親クラスのメソッドを呼び出す場合、メソッド内の this が現在のサブクラス インスタンスを指すことが規定されています。

クラスA {
  コンストラクター() {
    this.x = 1;
  }
  print() {
    console.log(this.x);
  }
}

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    this.x = 2;
  }
  m() {
    super.print();
  }
}

b = 新しい B(); とします。
b.m() // 2

上記のコードでは、「super.print()」が「A.prototype.print()」を呼び出していますが、「A.prototype.print()」内の「this」はサブクラス「B」のインスタンスを指しているため、次のようになります。出力は「1」ではなく「2」です。つまり、実際に実行されるのは「super.print.call(this)」ということになります。

this はサブクラス インスタンスを指すため、プロパティに super を介して値が割り当てられている場合、superthis となり、割り当てられたプロパティはサブクラス インスタンスのプロパティになります。

クラスA {
  コンストラクター() {
    this.x = 1;
  }
}

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    this.x = 2;
    super.x = 3;
    console.log(super.x); // 未定義
    console.log(this.x); // 3
  }
}

b = 新しい B(); とします。

上記のコードでは、「super.x」に「3」の値が割り当てられています。これは、「this.x」に「3」の値を割り当てるのと同じです。 super.xを読み込むと、A.prototype.xを読み込むため、unknownが返されます。

super が静的メソッドのオブジェクトとして使用される場合、 super は親クラスのプロトタイプ オブジェクトではなく、親クラスを指します。

親クラス {
  静的 myMethod(msg) {
    console.log('静的', msg);
  }

  myMethod(msg) {
    console.log('インスタンス', msg);
  }
}

class Child extends Parent {
  静的 myMethod(msg) {
    super.myMethod(msg);
  }

  myMethod(msg) {
    super.myMethod(msg);
  }
}

Child.myMethod(1); // 静的 1

var child = 新しい Child();
child.myMethod(2); // インスタンス 2

上記のコードでは、「super」は静的メソッドでは親クラスを指し、通常のメソッドでは親クラスのプロトタイプ オブジェクトを指します。

さらに、サブクラスの静的メソッドの super を介して親クラスのメソッドを呼び出す場合、メソッド内の this はサブクラスのインスタンスではなく、現在のサブクラスを指します。

クラスA {
  コンストラクター() {
    this.x = 1;
  }
  静的プリント() {
    console.log(this.x);
  }
}

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    this.x = 2;
  }
  静的 m() {
    super.print();
  }
}

B.x = 3;
B.m() // 3

上記のコードでは、静的メソッド B.msuper.print が親クラスの静的メソッドを指します。このメソッドの this は、B のインスタンスではなく、B を指します。

super を使用する場合は、それを関数として使用するかオブジェクトとして使用するかを明示的に指定する必要があることに注意してください。そうしないと、エラーが報告されます。

クラス A {}

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    console.log(super); // エラーレポート
  }
}

上記のコードでは、「console.log(super)」の「super」が関数として使用されているかオブジェクトとして使用されているかが確認できないため、JavaScript エンジンはコードを解析するときにエラーを報告します。このとき、データ型「super」を明示できればエラーは報告されません。

クラス A {}

クラス B は A を拡張します {
  コンストラクター() {
    素晴らしい();
    console.log(super.valueOf()instanceofB); // true
  }
}

b = 新しい B(); とします。

上記のコードでは、super.valueOf() は、super がオブジェクトであることを示しているため、エラーは報告されません。同時に、「super」は「this」が「B」のインスタンスを指すようにするため、「super.valueOf()」は「B」のインスタンスを返します。

最後に、オブジェクトは常に他のオブジェクトを継承するため、どのオブジェクトでも super キーワードを使用できます。

var obj = {
  toString() {
    return "MyObject: " + super.toString();
  }
};

obj.toString(); // MyObject: [オブジェクト オブジェクト]

クラスのプロトタイプ プロパティと __proto__ プロパティ

ほとんどのブラウザの ES5 実装では、各オブジェクトには、対応するコンストラクターの prototype 属性を指す __proto__ 属性があります。コンストラクターの構文糖としてのクラスは、prototype 属性と __proto__ 属性の両方を持っているため、同時に 2 つの継承チェーンが存在します。

(1) サブクラスの __proto__ 属性はコンストラクターの継承を表し、常に親クラスを指します。

(2) サブクラスの prototype 属性の __proto__ 属性はメソッドの継承を表し、常に親クラスの prototype 属性を指します。

クラスA {
}

クラス B は A を拡張します {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true

上記のコードでは、サブクラス B__proto__ 属性は親クラス Aprototype 属性を指し、サブクラス Bprototype 属性の __proto__ 属性は親クラス Bprototype 属性を指します。親クラス Aprototype 属性に追加します。

この結果は、クラスの継承が次のパターンに従って実装されているためです。

クラスA {
}

クラス B {
}

// B のインスタンスは A のインスタンスを継承します
Object.setPrototypeOf(B.prototype, A.prototype);

// B は A の静的プロパティを継承します
Object.setPrototypeOf(B, A);

const b = 新しい B();

Object.setPrototypeOf メソッドの実装については、「オブジェクト拡張」の章で説明します。

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = プロト;
  オブジェクトを返します。
}

したがって、上記の結果が得られます。

Object.setPrototypeOf(B.prototype, A.prototype);
// と同等
B.prototype.__proto__ = A.prototype;

Object.setPrototypeOf(B, A);
// と同等
B.__proto__ = A;

これら 2 つの継承チェーンは次のように理解できます。オブジェクトとしては、サブクラス (B) のプロトタイプ (__proto__ 属性) は、コンストラクターとしては親クラス (A)、サブクラス (B) です。 ) プロトタイプ オブジェクト (prototype プロパティ) は、親クラスのプロトタイプ オブジェクト (prototype プロパティ) のインスタンスです。

B.prototype = Object.create(A.prototype);
// と同等
B.prototype.__proto__ = A.prototype;

extends キーワードの後に​​は、複数のタイプの値を続けることができます。

クラス B は A を拡張します {
}

上記コードの「A」は、「prototype」属性を持つ関数であれば「B」に継承できます。関数には prototype 属性があるため (Function.prototype 関数を除く)、A は任意の関数になります。

以下では 2 つの状況について説明します。まず、サブクラスは Object クラスを継承します。

クラス A はオブジェクト { を拡張します
}

A.__proto__ === オブジェクト // true
A.prototype.__proto__ === Object.prototype // true

この場合、「A」は実際にはコンストラクター「Object」のコピーであり、「A」のインスタンスは「Object」のインスタンスです。

2 番目のケースでは、継承はありません。

クラスA {
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true

この場合、基底クラスである (つまり継承がない) A は通常の関数であるため、Function.prototype を直接継承します。ただし、A は呼び出された後に空のオブジェクト (つまり、Object インスタンス) を返すため、A.prototype.__proto__ はコンストラクター (Object) の prototype 属性を指します。

インスタンスの __proto__ 属性

サブクラス インスタンスの proto 属性の proto 属性は、親クラス インスタンスの proto 属性を指します。言い換えれば、サブクラスのプロトタイプのプロトタイプは、親クラスのプロトタイプです。

var p1 = 新しいポイント(2, 3);
var p2 = 新しい ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

上記のコードでは、ColorPointPoint を継承しているため、前者のプロトタイプのプロトタイプが後者のプロトタイプになります。

したがって、親クラス インスタンスの動作は、子クラス インスタンスの __proto__.__proto__ 属性を通じて変更できます。

p2.__proto__.__proto__.printName = function () {
  console.log('は');
};

p1.printName() // "は"

上記のコードは、ColorPoint インスタンス p2Point クラスにメソッドを追加します。これは、Point インスタンス p1 に影響を与えます。

ネイティブコンストラクターの継承

ネイティブ コンストラクターは、通常、データ構造を生成するために使用される言語の組み込みコンストラクターを指します。 ECMAScript のネイティブ コンストラクターは大まかに以下のとおりです。

-ブール値() -番号()

  • 弦() -配列() -日付() -関数() -RegExp() -エラー() -物体()

以前は、これらのネイティブ コンストラクターを継承することはできませんでした。たとえば、Array のサブクラスを自分で定義することはできませんでした。

関数 MyArray() {
  Array.apply(this, 引数);
}

MyArray.prototype = Object.create(Array.prototype, {
  コンストラクター: {
    値: MyArray、
    書き込み可能: true、
    構成可能: true、
    列挙可能: true
  }
});

上記のコードは、Array を継承する MyArray クラスを定義します。ただし、このクラスの動作は Array と完全に一致しません。

var color = new MyArray();
色[0] = "赤";
色.長さ // 0

色.長さ = 0;
Colors[0] // "赤"

これは、サブクラスが Array.apply() を通じても、プロトタイプ オブジェクトへの代入によっても、ネイティブ コンストラクターの内部プロパティを取得できないために発生します。ネイティブ コンストラクターは、「apply」メソッドによって渡された「this」を無視します。つまり、ネイティブ コンストラクターの「this」はバインドできないため、内部プロパティは生成されません。

ES5 は、まずサブクラスのインスタンス オブジェクト「this」を作成し、親クラスのプロパティをサブクラスに追加します。親クラスの内部プロパティが取得できないため、ネイティブ コンストラクターを継承できません。たとえば、「Array」コンストラクターには内部プロパティ「[[DefineOwnProperty]]」があります。これは、新しいプロパティを定義するときに「length」プロパティを更新するために使用されます。この内部プロパティはサブクラスでは取得できないため、「length」が返されます。 ` サブクラスのプロパティの動作が正常ではありません。

次の例では、通常のオブジェクトが Error オブジェクトを継承するようにします。

var e = {};

Object.getOwnPropertyNames(Error.call(e))
// [ 'スタック' ]

Object.getOwnPropertyNames(e)
//[]

上記のコードでは、Error.call(e) を使用して、通常のオブジェクト eError オブジェクトのインスタンス属性を持たせます。ただし、 Error.call() は渡された最初の引数を完全に無視し、代わりに e 自体を変更せずに新しいオブジェクトを返します。これは、Error.call(e) がネイティブ コンストラクターを継承できないことを証明しています。

ES6 では、ネイティブ コンストラクターを継承してサブクラスを定義できます。これは、ES6 が最初に親クラスのインスタンス オブジェクト「this」を作成し、次にサブクラスのコンストラクターで「this」を変更するため、親クラスのすべての動作を継承できるようになります。以下は「Array」を継承する例です。

class MyArray extends Array {
  コンストラクター(...args) {
    super(...args);
  }
}

var arr = 新しい MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // 未定義

上記のコードは MyArray クラスを定義し、Array コンストラクターを継承しているため、MyArray から配列インスタンスを生成できます。これは、ES6 がネイティブ データ構造 (「Array」、「String」など) のサブクラスをカスタマイズできることを意味しますが、これは ES5 では実行できません。

上記の例は、「extends」キーワードをクラスの継承だけでなく、ネイティブ コンストラクターの継承にも使用できることも示しています。したがって、ネイティブ データ構造に基づいて独自のデータ構造を定義できます。以下は version 関数が定義された配列です。

class VersionedArray extends Array {
  コンストラクター() {
    素晴らしい();
    this.history = [[]];
  }
  専念() {
    this.history.push(this.slice());
  }
  戻す() {
    this.splice(0, this.length, ...this.history[this.history.length - 1]);
  }
}

var x = 新しい VersionedArray();

x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]

x.commit();
x.history // [[], [1, 2]]

x.push(3);
x // [1、2、3]
x.history // [[], [1, 2]]

x.revert();
x // [1, 2]

上記のコードでは、VersionedArray は、commit メソッドを通じて現在のステータスのバージョン スナップショットを生成し、それを history 属性に保存します。 「revert」メソッドは、配列を最新の保存バージョンにリセットするために使用されます。さらに、VersionedArray は通常の配列のままであり、すべてのネイティブ配列メソッドを呼び出すことができます。

以下は、「Error」サブクラスをカスタマイズする例です。これは、エラーを報告するときの動作をカスタマイズするために使用できます。

class ExtendableError extends Error {
  コンストラクター(メッセージ) {
    素晴らしい();
    this.message = メッセージ;
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}

class MyError extends ExtendableError {
  コンストラクター(m) {
    スーパー(m);
  }
}

var myerror = new MyError('ll');
myerror.message // "ll"
myerror エラーのインスタンス // true
myerror.name // "MyError"
私のエラースタック
// エラー
// MyError.ExtendableError で
// ...

[動作の違い](http://stackoverflow.com/questions/36203614/super-does-not-pass-arguments-when-instantiating-a-class-extended-from -object) があることに注意してください。

class NewObj extends Object{
  コンストラクタ(){
    super(...引数);
  }
}
var o = 新しい NewObj({attr: true});
o.attr === true // false

上記のコードでは、NewObjObject を継承しますが、super メソッドを通じて親クラス Object にパラメータを渡すことはできません。これは、ES6 が Object コンストラクターの動作を変更し、Object メソッドが new Object() を通じて呼び出されないことが判明すると、ES6 は Object コンストラクターがパラメーターを無視することを規定したためです。

Mixin パターンの実装

ミックスインとは、複数のオブジェクトを新しいオブジェクトに合成することを指し、新しいオブジェクトは各コンポーネント メンバーのインターフェイスを持ちます。最も単純な実装は次のとおりです。

定数 a = {
  a:「あ」
};
const b = {
  b: 「b」
};
const c = {...a, ...b} // {a: 'a', b: 'b'};

上記のコードでは、「c」オブジェクトは「a」オブジェクトと「b」オブジェクトの複合体であり、両方のインターフェースを持っています。

ここでは、複数のクラスのインターフェイスを別のクラスに「混合」する、より完全な実装を示します。

関数 mix(...mixins) {
  クラスミックス{
    コンストラクター() {
      for (ミックスインのミックスインを許可) {
        copyProperties(this, new mixin()); // インスタンスのプロパティをコピーします。
      }
    }
  }

  for (ミックスインのミックスインを許可) {
    copyProperties(Mix, mixin); //静的プロパティをコピーします。
    copyProperties(Mix.prototype, mixin.prototype); // プロトタイプのプロパティをコピーします。
  }

  ミックスを返します。
}

関数 copyProperties(ターゲット, ソース) {
  for (Reflect.ownKeys(source) のキーを許可) {
    if (key !== 'コンストラクタ'
      && キー !== 'プロトタイプ'
      && キー !== '名前'
    ) {
      let desc = Object.getOwnPropertyDescriptor(source, key);
      Object.defineProperty(ターゲット、キー、説明);
    }
  }
}

上記のコードの「mix」関数は、複数のオブジェクトを 1 つのクラスに結合できます。使用する場合はこのクラスを継承するだけです。

class DistributedEdit extends mix(Loggable, Serializable) {
  // ...
}

作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0