Maison  >  Article  >  interface Web  >  Introduction à l'héritage et au super classe ES6

Introduction à l'héritage et au super classe ES6

不言
不言original
2018-07-09 10:47:322005parcourir

Cet article présente principalement l'introduction de l'héritage de classe ES6 et du super. Maintenant, je le partage avec tout le monde. Les amis dans le besoin peuvent s'y référer

L'héritage de classe et le super

<.>Le cours peut s'étendre à partir d'un autre cours. C'est une belle syntaxe, techniquement basée sur l'héritage prototypique.

Pour hériter d'un objet, vous devez spécifier

et l'objet parent avant {..}. extends

Ce

hérite de Rabbit : Animal

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!
Comme vous pouvez le constater, comme vous pouvez l'imaginer, le mot-clé

est en fait ajouté à extend >, cité à Rabbit.prototype. [Prototype]]Animal.prototype

Introduction à lhéritage et au super classe ES6Alors maintenant,

peut accéder à la fois à ses propres méthodes et à celles de

. rabbitAnimal

peut être suivi d'une expression

extendsL'extension de la syntaxe Class ne se limite pas à spécifier une classe, mais peut également être une expression.

Par exemple, une fonction qui génère une classe parent :

Dans l'exemple,
function f(phrase) {
  return class {
    sayHi() { alert(phrase) }
  }
}


class User extends f("Hello") {}


new User().sayHi(); // Hello
hérite du résultat renvoyé par f('Hello').

class UserPour les modèles de programmation avancés, cela est utile lorsque nous utilisons des classes générées à l'aide de fonctions basées sur de nombreuses conditions.

Remplacer une méthode

Passons maintenant à l'étape suivante et remplaçons une méthode. Jusqu'à présent,

hérite des méthodes

de Rabbit, Animal. stopthis.speed = 0Si nous spécifions notre propre

dans

, il sera utilisé en premier : Rabbitstop

... Mais généralement nous ne voulons pas remplacer complètement la méthode parent , mais ajustez ou étendez ses fonctionnalités en fonction de la méthode parent. Nous faisons quelque chose pour qu'il appelle la méthode parent avant/après ou dans la procédure.
class Rabbit extends Animal {
  stop() {
    // ...this will be used for rabbit.stop()
  }
}

Class fournit le mot-clé

à cet effet.

super

    Utilisez
  • pour appeler la méthode parent.

    super.method(...)

  • Utilisez
  • pour appeler le constructeur parent (uniquement dans la fonction constructeur).

    super(...)

  • Par exemple, laissez le lapin se cacher automatiquement lorsque
 :

stop

Maintenant, la méthode
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!
de

appelle la classe parent via Rabbit méthode. stopsuper.stop()Les fonctions fléchées n'ont pas de

superComme mentionné dans le chapitre sur les fonctions fléchées, les fonctions fléchées n'ont pas de

.

superIl obtiendra

de la fonction externe. Par exemple :

super

Le
class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}
dans la fonction flèche est le même que le

qu'elle contient, donc cela fonctionne comme prévu. Si nous utilisons ici une fonction normale, nous obtiendrons une erreur : superstop()

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

Pour les constructeurs, c'est un peu délicat.

Jusqu'à présent,

n'avait pas de

propre. RabbitJusqu'à présent, constructor n'avait pas son propre
.RabbitconstructorSelon la spécification, si une classe étend une autre classe et qu'il n'y a pas de

, alors le

suivant sera être généré automatiquement : constructorconstructor

On voit qu'il appelle le parent
class Rabbit extends Animal {
  // generated for extending classes without own constructors

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

}
en lui passant tous les paramètres. Cela se produira si nous n'écrivons pas le constructeur nous-mêmes.

constructorNous ajoutons maintenant un constructeur personnalisé à

. En plus de

, nous définirons également Rabbit : nameearLength

Oups, quelque chose s'est mal passé ! Maintenant, nous ne pouvons plus engendrer de lapins, pourquoi ?
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.

En termes simples : le constructeur de la classe héritée doit appeler

, (!) et l'exécuter avant d'utiliser

. super(...)this...mais pourquoi ? Que se passe-t-il? Hmm... cette demande semble étrange.

Parlons maintenant des détails afin que vous puissiez vraiment comprendre pourquoi -

En JavaScript, les constructeurs qui héritent d'autres classes sont spéciaux. Dans les classes héritées, le constructeur correspondant est marqué comme un attribut interne spécial

.

[[ConstructorKind]]:“derived”La différence est :

    Lorsqu'un constructeur normal s'exécute, il crée un objet vide comme celui-ci et continue de s'exécuter.
  • Mais lorsque le constructeur dérivé s'exécute, contrairement à ce qui précède, il se tourne vers le constructeur parent pour faire le travail.
  • Donc, si nous construisons notre propre constructeur, alors nous devons appeler
, sinon l'objet avec

ne sera pas créé et une erreur sera signalée. superthisPour

, nous devons appeler

avant d'utiliser Rabbit, comme suit : thissuper()

L'implémentation de Super est la même que celle de [[HomeObject]]
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

Approfondissons la mise en œuvre sous-jacente de

et nous verrons des choses intéressantes.

superLa première chose à dire est qu'avec ce que nous avons appris jusqu'à présent, mettre en œuvre un super est impossible.

那么思考一下,这是什么原理?当一个对象方法运行时,它将当前对象作为 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,于是就这么无限循环下去。

情况如图:

Introduction à lhéritage et au super classe ES6

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]

Introduction à lhéritage et au super classe ES6

所以,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 的结构:

Introduction à lhéritage et au super classe ES6

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 并行请求和错误处理

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn