Heim  >  Artikel  >  Web-Frontend  >  Einführung in die ES6-Klassenvererbung und Super

Einführung in die ES6-Klassenvererbung und Super

不言
不言Original
2018-07-09 10:47:321960Durchsuche

Dieser Artikel stellt hauptsächlich die Einführung von ES6-Klassenvererbung und -Super vor. Er hat einen gewissen Referenzwert. Jetzt kann ich ihn mit allen Freunden teilen, die ihn benötigen 🎜>Die Klasse kann von einer anderen Klasse ausgehen. Es ist eine schöne Syntax, die technisch auf prototypischer Vererbung basiert.

Um ein Objekt zu erben, müssen Sie

und das übergeordnete Objekt vor

angeben.

Dieses {..} erbt von extends:

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stopped.`);
  }

}


// Inherit from Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}


let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!

Wie Sie sehen können, fügt das Schlüsselwort Rabbit tatsächlich Animal in

hinzu, Zitat zu

. extendRabbit.prototype[Prototype]]Animal.prototype

Jetzt kann

sowohl auf seine eigenen Methoden als auch auf die Methoden von Einführung in die ES6-Klassenvererbung und Super zugreifen. Auf

rabbit kann ein Ausdruck folgen Animal

Die „extends“ der Klassensyntax beschränken sich nicht nur auf die Angabe einer Klasse, sondern können auch ein Ausdruck sein.

extendsZum Beispiel eine Funktion, die eine übergeordnete Klasse generiert:

function f(phrase) {
  return class {
    sayHi() { alert(phrase) }
  }
}


class User extends f("Hello") {}


new User().sayHi(); // Hello

Im Beispiel erbt

das von f('Hello') zurückgegebene Ergebnis.

Für fortgeschrittene Programmiermuster ist dies nützlich, wenn wir Klassen verwenden, die mithilfe von Funktionen generiert werden, die auf vielen Bedingungen basieren.

class UserEine Methode überschreiben

Jetzt gehen wir zum nächsten Schritt über und überschreiben eine Methode. Bisher erbt

die

-Methoden von

,

. RabbitAnimalWenn wir unser eigenes stop in this.speed = 0 angeben, wird es zuerst verwendet:

class Rabbit extends Animal {
  stop() {
    // ...this will be used for rabbit.stop()
  }
}

... Aber normalerweise wollen wir die übergeordnete Methode nicht vollständig ersetzen, sondern Es dient dazu, seine Funktionen basierend auf der übergeordneten Methode anzupassen oder zu erweitern. Wir tun etwas, damit die übergeordnete Methode vor/nach oder innerhalb der Prozedur aufgerufen wird. RabbitstopClass stellt zu diesem Zweck das Schlüsselwort

bereit.

superVerwenden Sie

, um die übergeordnete Methode aufzurufen.
  • super.method(...)Verwenden Sie

    , um den übergeordneten Konstruktor aufzurufen (nur in der Konstruktorfunktion).
  • super(...)Lassen Sie das Kaninchen beispielsweise automatisch verstecken, wenn

    :
class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stopped.`);
  }

}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }


  stop() {
    super.stop(); // call parent stop
    this.hide(); // and then hide
  }

}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stopped. White rabbit hides!
Jetzt ruft die

-Methode von stop die Methode der übergeordneten Klasse auf bis

.

RabbitPfeilfunktionen haben kein stopsuper.stop()

Wie im Kapitel über Pfeilfunktionen erwähnt, haben Pfeilfunktionen kein

. super

Es wird

von der externen Funktion erhalten. Beispiel: super

class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
Das

in der Pfeilfunktion ist dasselbe wie das darin enthaltene super, sodass es wie erwartet funktioniert. Wenn wir hier eine normale Funktion verwenden, erhalten wir eine Fehlermeldung:

// Unexpected super
setTimeout(function() { super.stop() }, 1000);

Überschreiben des Konstruktorssuperstop()Bei Konstruktoren ist das etwas knifflig.

Bisher hatte

kein eigenes

.

Bisher hatte

kein eigenes Rabbit.constructor
Wenn laut Spezifikation eine Klasse eine andere Klasse erweitert und es kein Rabbit gibt, dann wird das folgende constructor tun automatisch generiert werden:

class Rabbit extends Animal {
  // generated for extending classes without own constructors

  constructor(...args) {
    super(...args);
  }

}

Wir können sehen, dass es das übergeordnete Element aufruft constructor und alle Parameter übergibt. Dies wird passieren, wenn wir den Konstruktor nicht selbst schreiben. constructor

Jetzt fügen wir einen benutzerdefinierten Konstruktor zu

hinzu. Zusätzlich zu constructor werden wir auch

setzen:

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {


  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLength;
  }


  // ...
}


// Doesn't work!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.
Rabbit Ups, da ist etwas schief gelaufen! Jetzt können wir keine Kaninchen mehr züchten, warum? nameearLength Einfach ausgedrückt: Der Konstruktor in der geerbten Klasse muss (!) aufrufen und ausführen, bevor er

verwendet.

...aber warum? Was ist los? Hmm ... diese Bitte kommt mir seltsam vor. super(...)thisLassen Sie uns nun die Details besprechen, damit Sie wirklich verstehen, warum –

In JavaScript sind Konstruktoren, die andere Klassen erben, etwas Besonderes. In geerbten Klassen wird der entsprechende Konstruktor als spezielles internes Attribut

markiert.

Der Unterschied ist:

[[ConstructorKind]]:“derived”

Wenn ein normaler Konstruktor ausgeführt wird, erstellt er ein leeres Objekt wie dieses und läuft weiter.

  • Aber wenn der abgeleitete Konstruktor ausgeführt wird, erwartet er anders als oben, dass der übergeordnete Konstruktor die Aufgabe erledigt.

  • Wenn wir also unseren eigenen Konstruktor erstellen, müssen wir

    aufrufen, sonst wird das Objekt mit
  • nicht erstellt und ein Fehler gemeldet.

Für super müssen wir this aufrufen, bevor wir

verwenden, wie folgt:

class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {

    super(name);

    this.earLength = earLength;
  }

  // ...
}


// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10
RabbitDie Implementierung von Super ist die gleiche wie [[HomeObject]]this super()Lassen Sie uns tiefer in die zugrunde liegende Implementierung von eintauchen und wir werden einige interessante Dinge sehen.

Das erste, was ich sagen muss, ist, dass die Umsetzung von Super mit dem, was wir bisher gelernt haben, unmöglich ist.

那么思考一下,这是什么原理?当一个对象方法运行时,它将当前对象作为 this。如果我们调用 super.method(),那么如何检索 method?很容易想到,我们需要从当前对象的原型中取出 method。从技术上讲,我们(或JavaScript引擎)可以做到这一点吗?

也许我们可以从 this 的 [[Prototype]] 中获得方法,就像 this .__ proto __.method 一样?不幸的是,这是行不通的。

让我们试一试,简单起见,我们不使用 class 了,直接使用普通对象。

在这里,rabbit.eat() 调用父对象的 animal.eat() 方法:

let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {

    // that's how super.eat() could presumably work
    this.__proto__.eat.call(this); // (*)

  }
};

rabbit.eat(); // Rabbit eats.

(*) 这一行,我们从原型(animal)中取出 eat,并以当前对象的上下文中调用它。请注意,.call(this) 在这里很重要,因为只写 this .__ proto __.eat() 的话 eat 的调用对象将会是 animal,而不是当前对象。

以上代码的 alert 是正确的。

但是现在让我们再添加一个对象到原型链中,就要出事了:

let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  eat() {
    // ...bounce around rabbit-style and call parent (animal) method
    this.__proto__.eat.call(this); // (*)
  }
};

let longEar = {
  __proto__: rabbit,
  eat() {
    // ...do something with long ears and call parent (rabbit) method
    this.__proto__.eat.call(this); // (**)
  }
};


longEar.eat(); // Error: Maximum call stack size exceeded

噢,完蛋!调用 longEar.eat() 报错了!

这原因一眼可能看不透,但如果我们跟踪 longEar.eat() 调用,大概就知道为什么了。在 (*)(**) 两行中, this 的值是当前对象(longEar)。重点来了:所有方法都将当前对象作为 this,而不是原型或其他东西。

因此,在两行 (*)(**) 中,this.__ proto__ 的值都是 rabbit。他们都调用了 rabbit.eat,于是就这么无限循环下去。

情况如图:

Einführung in die ES6-Klassenvererbung und Super

1.在 longEar.eat() 里面,(**) 行中调用了 rabbit.eat,并且this = longEar

// inside longEar.eat() we have this = longEar
this.__proto__.eat.call(this) // (**)
// becomes
longEar.__proto__.eat.call(this)
// that is
rabbit.eat.call(this);

2.然后在rabbit.eat(*) 行中,我们希望传到原型链的下一层,但是 this = longEar,所以 this .__ proto __.eat又是 rabbit.eat

// inside rabbit.eat() we also have this = longEar
this.__proto__.eat.call(this) // (*)
// becomes
longEar.__proto__.eat.call(this)
// or (again)
rabbit.eat.call(this);
  1. ...因此 rabbit.eat 在无尽循环调动,无法进入下一层。

这个问题不能简单使用 this 解决。

[[HomeObject]]

为了提供解决方案,JavaScript 为函数添加了一个特殊的内部属性:[[HomeObject]]

当函数被指定为类或对象方法时,其 [[HomeObject]] 属性为该对象。

这实际上违反了 unbind 函数的思想,因为方法记住了它们的对象。并且 [[HomeObject]] 不能被改变,所以这是永久 bind(绑定)。所以在 JavaScript 这是一个很大的变化。

但是这种改变是安全的。 [[HomeObject]] 仅用于在 super 中获取下一层原型。所以它不会破坏兼容性。

让我们来看看它是如何在 super 中运作的:

let animal = {
  name: "Animal",
  eat() {         // [[HomeObject]] == animal
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {         // [[HomeObject]] == rabbit
    super.eat();
  }
};

let longEar = {
  __proto__: rabbit,
  name: "Long Ear",
  eat() {         // [[HomeObject]] == longEar
    super.eat();
  }
};


longEar.eat();  // Long Ear eats.

每个方法都会在内部 [[HomeObject]] 属性中记住它的对象。然后 super 使用它来解析原型。

在类和普通对象中定义的方法中都定义了 [[HomeObject]],但是对于对象,必须使用:method() 而不是 "method: function()"

在下面的例子中,使用非方法语法(non-method syntax)进行比较。这么做没有设置 [[HomeObject]] 属性,继承也不起作用:

let animal = {
  eat: function() { // should be the short syntax: eat() {...}
    // ...
  }
};

let rabbit = {
  __proto__: animal,
  eat: function() {
    super.eat();
  }
};


rabbit.eat();  // Error calling super (because there's no [[HomeObject]])

静态方法和继承

class 语法也支持静态属性的继承。

例如:

class Animal {

  constructor(name, speed) {
    this.speed = speed;
    this.name = name;
  }

  run(speed = 0) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }

}

// Inherit from Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbits = [
  new Rabbit("White Rabbit", 10),
  new Rabbit("Black Rabbit", 5)
];

rabbits.sort(Rabbit.compare);

rabbits[0].run(); // Black Rabbit runs with speed 5.

现在我们可以调用 Rabbit.compare,假设继承的 Animal.compare 将被调用。

它是如何工作的?再次使用原型。正如你猜到的那样,extends 同样给 Rabbit 提供了引用到 Animal[Prototype]

Einführung in die ES6-Klassenvererbung und Super

所以,Rabbit 函数现在继承 Animal 函数。Animal 自带引用到 Function.prototype[[Prototype]](因为它不 extend 其他类)。

看看这里:

class Animal {}
class Rabbit extends Animal {}

// for static propertites and methods
alert(Rabbit.__proto__ === Animal); // true

// and the next step is Function.prototype
alert(Animal.__proto__ === Function.prototype); // true

// that's in addition to the "normal" prototype chain for object methods
alert(Rabbit.prototype.__proto__ === Animal.prototype);

这样 Rabbit 可以访问 Animal 的所有静态方法。

在内置对象中没有静态继承

请注意,内置类没有静态 [[Prototype]] 引用。例如,Object 具有 Object.definePropertyObject.keys等方法,但 ArrayDate 不会继承它们。

DateObject 的结构:

Einführung in die ES6-Klassenvererbung und Super

DateObject 之间毫无关联,他们独立存在,不过 Date.prototype 继承于 Object.prototype,仅此而已。

造成这个情况是因为 JavaScript 在设计初期没有考虑使用 class 语法和继承静态方法。

原生拓展

Array,Map 等内置类也可以扩展。

举个例子,PowerArray 继承自原生 Array

// add one more method to it (can do more)
class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false

请注意一件非常有趣的事情。像 filtermap 和其他内置方法 - 返回新的继承类型的对象。他们依靠 constructor 属性来做到这一点。

在上面的例子中,

arr.constructor === PowerArray

所以当调用 arr.filter() 时,它自动创建新的结果数组,就像 new PowerArray 一样,于是我们可以继续使用 PowerArray 的方法。

我们甚至可以自定义这种行为。如果存在静态 getter Symbol.species,返回新建对象使用的 constructor。

下面的例子中,由于 Symbol.species 的存在,mapfilter等内置方法将返回普通的数组:

class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }


  // built-in methods will use this as the constructor
  static get [Symbol.species]() {
    return Array;
  }

}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

// filter creates new array using arr.constructor[Symbol.species] as constructor
let filteredArr = arr.filter(item => item >= 10);


// filteredArr is not PowerArray, but Array

alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function

我们可以在其他 key 使用 Symbol.species,可以用于剥离结果值中的无用方法,或是增加其他方法。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

用Node编写RESTful API接口

async/await 并行请求和错误处理

Das obige ist der detaillierte Inhalt vonEinführung in die ES6-Klassenvererbung und Super. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn