[ES6] Ruan Yifeng ES6 学習クラスの継承

1.コンセプト

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

// Point是父类,ColorPoint是子类,它通过extends关键字,继承了Point类的所有属性和方法。
class Point {
    
    
}

class ColorPoint extends Point {
    
    
}

ただし、コードがデプロイされていないため、2 つのクラスはまったく同じであり、これはクラスをコピーすることと同じですPoint

ES6サブクラスはconstructor()メソッド内で呼び出す必要があると規定されておりsuper()、呼び出されないとエラーが報告されます。

class Point {
    
     /* ... */ }

class ColorPoint extends Point {
    
    
  constructor(x, y, color) {
    
    
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    
    
    return this.color + ' ' + super.toString(); // 调用父类的toString()
  }
}

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

superキーワードが必要な理由: サブクラスの this オブジェクトは、最初に親クラスのコンストラクターを通じて整形され、親クラスと同じインスタンス属性とメソッドを取得し、それからそれを処理してサブクラスの独自のインスタンスを追加する必要があるためです。プロパティとメソッド。super()メソッドが呼び出されない場合、サブクラスは独自のthisオブジェクトを取得できません。

class Point {
    
     /* ... */ }

class ColorPoint extends Point {
    
    
  constructor() {
    
    
  }
}

let cp = new ColorPoint(); // ReferenceError

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

注: 新しいサブクラス インスタンスを作成するときは、最初に親クラスのコンストラクターを 1 回実行する必要があります。

さあ、小さなプラム

class Foo {
    
    
  constructor() {
    
    
    console.log(1);
  }
}

class Bar extends Foo {
    
    
  constructor() {
    
    
    super();
    console.log(2);
  }
}

const bar = new Bar();
// 1
// 2

2.スーパーキーワード

superこのキーワードは、関数またはオブジェクトとして使用できます。どちらの場合も、まったく異なる意味で使用されます。

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

class A {
    
    }

class B extends A {
    
    
  constructor() {
    
    
    super();
  }
}

上記のコードでは、サブクラス B のコンストラクターのうちsuper()、親クラスのコンストラクターを呼び出すことを意味します。これは必要です。そうしないと、JavaScript エンジンがエラーを報告します。

知らせ:super親クラス A のコンストラクターを表しますが、サブクラス B のインスタンスを返します。つまり、内部superクラスはthisB のインスタンスを参照するため、super()次と同等です。

A.prototype.constructor.call(this)class A {
    
    
  constructor() {
    
    
    console.log(new.target.name);
  }
}
class B extends A {
    
    
  constructor() {
    
    
    super();
  }
}
new A() // A
new B() // B

上記のコードでは、new.target現在実行されている関数を指します。super()実行すると、親クラス A のコンストラクターではなく、サブクラス B のコンストラクターを指すことがわかります。つまり、super()内部の this は B を指します。

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

class A {
    
    }

class B extends A {
    
    
  m() {
    
    
    super(); // 报错
  }
}

上記コードにおいて、super()クラスBのmメソッド内で使用すると構文エラーとなります。

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

class A {
    
    
  p() {
    
    
    return 2;
  }
}

class B extends A {
    
    
  constructor() {
    
    
    super();
    console.log(super.p()); // 2
  }
}

let b = new B();

上記のコードでは、サブクラス B でオブジェクトとして使用されますsuper.p()superこのとき、super通常の方法では を指すA.prototypeのでsuper.p()同等ですA.prototype.p()

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

class A {
    
    
  constructor() {
    
    
    this.p = 2;
  }
}

class B extends A {
    
    
  get m() {
    
    
    return super.p;
  }
}

let b = new B();
b.m // undefined

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

親クラスのプロトタイプオブジェクトに属性が定義されていればsuper取得可能です。

class A {
    
    }
A.prototype.x = 2;

class B extends A {
    
    
  constructor() {
    
    
    super();
    console.log(super.x) // 2
  }
}

let b = new B();

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

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

class A {
    
    
  constructor() {
    
    
    this.x = 1;
  }
  print() {
    
    
    console.log(this.x);
  }
}

class B extends A {
    
    
  constructor() {
    
    
    super();
    this.x = 2;
  }
  m() {
    
    
    super.print();
  }
}

let b = new B();
b.m() // 2

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

これはサブクラス インスタンスを指しているため、super特定の属性に値を割り当てると、割り当てられたsuper属性thisがサブクラス インスタンスの属性になります。

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

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

  • ブール値()
  • 番号()
  • 弦()
  • 配列()
  • 日にち()
  • 関数()
  • RegExp()
  • エラー()
  • 物体()

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

function MyArray() {
    
    
  Array.apply(this, arguments);
}

MyArray.prototype = Object.create(Array.prototype, {
    
    
  constructor: {
    
    
    value: MyArray,
    writable: true,
    configurable: true,
    enumerable: true
  }
});

上記のコードは、継承されたArrayクラスを定義しますMyArrayただし、このクラスの動作はArray完全に矛盾しています。

var colors = new MyArray();
colors[0] = "red";
colors.length  // 0

colors.length = 0;
colors[0]  // "red"

これは、サブクラスがプロトタイプ オブジェクトに渡すか代入することによって、ネイティブ コンストラクターの内部プロパティにアクセスできないために発生しますArray.apply()ネイティブ コンストラクターは、applyメソッドによって渡されたものを無視しますthis。つまり、ネイティブ コンストラクターをthisバインドできないため、内部プロパティを取得できなくなります。
ES5では、まずサブクラスのインスタンスオブジェクトを作成し、親クラスのプロパティをサブクラスに追加しますが、親クラスの内部プロパティが取得できないため、元のコンストラクタを継承できません。たとえば、Array コンストラクターには内部プロパティ [[DefineOwnProperty]] があり、新しいプロパティを定義するときに長さプロパティを更新するために使用されます。この内部プロパティはサブクラスでは取得できないため、サブクラスの長さプロパティが異常な動作をします。 。

次の例では、通常のオブジェクトがErrorobject から継承されるようにします。

var e = {
    
    };

Object.getOwnPropertyNames(Error.call(e))
// [ 'stack' ]

Object.getOwnPropertyNames(e)
// []

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

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

class MyArray extends Array {
    
    
  constructor(...args) {
    
    
    super(...args);
  }
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined

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

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

class VersionedArray extends Array {
    
    
  constructor() {
    
    
    super();
    this.history = [[]];
  }
  commit() {
    
    
    this.history.push(this.slice());
  }
  revert() {
    
    
    this.splice(0, this.length, ...this.history[this.history.length - 1]);
  }
}

var x = new 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 {
    
    
  constructor(message) {
    
    
    super();
    this.message = message;
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}

class MyError extends ExtendableError {
    
    
  constructor(m) {
    
    
    super(m);
  }
}

var myerror = new MyError('ll');
myerror.message // "ll"
myerror instanceof Error // true
myerror.name // "MyError"
myerror.stack
// Error
//     at MyError.ExtendableError
//     ...

Object を継承するサブクラスには動作の違いがあることに注意してください。

class NewObj extends Object{
    
    
  constructor(){
    
    
    super(...arguments);
  }
}
var o = new NewObj({
    
    attr: true});
o.attr === true  // false

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

おすすめ

転載: blog.csdn.net/Bon_nenul/article/details/128288689