Quels sont les avantages de la syntaxe ES2015 (ES6) `class`?

87

J'ai beaucoup de questions sur les classes ES6.

Quel est l'avantage d'utiliser la classsyntaxe? J'ai lu que public / privé / statique fera partie d'ES7, est-ce une raison?

De plus, est-ce classun autre type de POO ou est-ce toujours l'héritage prototypique de JavaScript? Puis-je le modifier en utilisant .prototype? Ou est-ce juste le même objet mais deux manières différentes de le déclarer.

Y a-t-il des avantages de vitesse? Peut-être est-il plus facile de maintenir / comprendre si vous avez une grosse application comme une grosse application?

ssbb
la source
Juste mon avis: la nouvelle syntaxe est beaucoup plus facile à comprendre et ne nécessiterait pas beaucoup d'expérience préalable (d'autant plus que la plupart des gens viendront d'un langage POO). Mais le modèle d'objet prototype vaut la peine d'être connu et vous n'apprendrez jamais qu'en utilisant la nouvelle syntaxe, il sera également plus difficile de travailler sur du code prototype à moins que vous ne le sachiez déjà. Je choisirais donc d'écrire tout mon propre code avec l'ancienne syntaxe lorsque j'ai ce choix
Zach Smith

Réponses:

120

La nouvelle classsyntaxe est, pour l' instant , principalement du sucre syntaxique. (Mais, vous savez, le bon type de sucre.) Il n'y a rien dans ES2015-ES2020 qui classpuisse faire que vous ne puissiez pas faire avec les fonctions de constructeur et Reflect.construct(y compris les sous Error- classes et Array¹). (Il est probable qu'il y aura des choses à ES2021 que vous pouvez faire avec classce que vous ne pouvez pas faire autrement: champs privés , des méthodes privées et les champs statiques / méthodes de statiques privées .)

De plus, est-ce classun autre type de POO ou est-ce toujours l'héritage prototypique de JavaScript?

C'est le même héritage prototypique que nous avons toujours eu, juste avec une syntaxe plus propre et plus pratique si vous aimez utiliser des fonctions de constructeur ( new Foo, etc.). (En particulier dans le cas de la dérivation de Arrayou Error, ce que vous ne pouviez pas faire dans ES5 et les versions antérieures. Vous pouvez maintenant avec Reflect.construct[ spec , MDN ], mais pas avec l'ancien style ES5.)

Puis-je le modifier en utilisant .prototype?

Oui, vous pouvez toujours modifier l' prototypeobjet sur le constructeur de la classe une fois que vous avez créé la classe. Par exemple, c'est parfaitement légal:

class Foo {
    constructor(name) {
        this.name = name;
    }
    
    test1() {
        console.log("test1: name = " + this.name);
    }
}
Foo.prototype.test2 = function() {
    console.log("test2: name = " + this.name);
};

Y a-t-il des avantages de vitesse?

En fournissant un idiome spécifique pour cela, je suppose qu'il est possible que le moteur puisse faire un meilleur travail d'optimisation. Mais ils sont déjà extrêmement bons pour l'optimisation, je ne m'attendrais pas à une différence significative.

Quels sont les avantages de la classsyntaxe ES2015 (ES6) ?

En bref: si vous n'utilisez pas de fonctions de constructeur en premier lieu, préférer Object.createou similaire classne vous est pas utile.

Si vous utilisez des fonctions de constructeur, il y a quelques avantages à class:

  • La syntaxe est plus simple et moins sujette aux erreurs.

  • Il est beaucoup plus facile (et encore moins sujet aux erreurs) de configurer des hiérarchies d'héritage en utilisant la nouvelle syntaxe qu'avec l'ancienne.

  • classvous défend de l'erreur courante de ne pas utiliser newavec la fonction constructeur (en demandant au constructeur de lancer une exception si ce thisn'est pas un objet valide pour le constructeur).

  • L'appel de la version du prototype parent d'une méthode est beaucoup plus simple avec la nouvelle syntaxe que l'ancienne ( super.method()au lieu de ParentConstructor.prototype.method.call(this)ou Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)).

Voici une comparaison de syntaxe pour une hiérarchie:

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        // ...
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        // ...use `result` for something...
        return result;
    }

    managerMethod() {
        // ...
    }
}

Exemple:

contre.

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    // ...
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    // ...use `result` for something...
    return result;
};
Manager.prototype.managerMethod = function() {
    // ...
};

Exemple en direct:

Comme vous pouvez le voir, il y a beaucoup de choses répétées et verbeuses qui sont faciles à se tromper et ennuyeuses à retaper (c'est pourquoi j'ai écrit un script pour le faire , à l'époque).


¹ "Il n'y a rien dans ES2015-ES2018 qui classpuisse faire que vous ne puissiez pas faire avec les fonctions de constructeur et Reflect.construct(y compris les sous Error- classes et Array)"

Exemple:

TJ Crowder
la source
9
Je ne sais pas si c'est une comparaison juste. Vous pouvez réaliser un objet similaire sans toute la cruauté. Il utilise la méthode JS de liaison d'objets via des chaînes de prototypes plutôt que d'approximer l'héritage classique. J'ai fait un résumé montrant un exemple simple basé sur le vôtre pour montrer comment faire cela.
Jason Cust du
8
@JasonCust: Oui, c'est une comparaison juste, car je montre la manière ES5 de faire ce que ES6 fait avec class. JavaScript est suffisamment flexible pour que vous n'ayez pas à utiliser des fonctions de constructeur si vous ne le souhaitez pas, ce que vous faites, mais c'est différent de class- le but principal classest de simplifier l'héritage pseudo-classique en JavaScript.
TJ Crowder du
3
@JasonCust: Oui, c'est une manière différente. Je n'appellerais pas ça "plus natif" (je sais que Kyle le fait, mais c'est Kyle). Les fonctions du constructeur sont fondamentales pour JavaScript, regardez tous les types intégrés (sauf que la faction anti-constructeur a réussi à se faire Symbolfoirer). Mais le grand avantage, encore une fois, c'est que si vous ne voulez pas les utiliser, vous n'êtes pas obligé de le faire. Je trouve dans mon travail que les fonctions de constructeur ont un sens dans de nombreux endroits et que la sémantique newaméliore la clarté; et a du Object.createsens dans de nombreux autres endroits, en particulier. situations de façade. Ils sont complémentaires. :-)
TJ Crowder
4
meh. Je n'en souscris pas à la nécessité. Je me suis éloigné de c # pour m'éloigner de oop et maintenant oop rampe sur javascript. Je n'aime pas les types. tout devrait être un var / object / function. c'est ça. plus de mots clés superflus. et l'héritage est une arnaque. si vous voulez voir un bon combat entre les types et les non-types. jetez un oeil à savon vs repos. et nous savons tous qui a gagné cela.
foreyez
3
@foreyez: la classsyntaxe ne rend pas JavaScript plus typé qu'auparavant. Comme je l'ai dit dans la réponse, il s'agit principalement d'une syntaxe (beaucoup) plus simple pour ce qui était déjà là. C'était absolument nécessaire, les gens se trompaient constamment sur l'ancienne syntaxe. Cela ne signifie pas non plus que vous devez utiliser l'héritage plus qu'auparavant. (Et bien sûr, si vous ne configurez jamais de «classes» avec l'ancienne syntaxe, il n'est pas nécessaire de le faire non plus avec la nouvelle syntaxe.)
TJ Crowder
22

Les classes ES6 sont du sucre syntaxique pour le système de classes prototypique que nous utilisons aujourd'hui. Ils rendent votre code plus concis et auto-documenté, ce qui est une raison suffisante pour les utiliser (à mon avis).

Utilisation de Babel pour transpiler cette classe ES6:

class Foo {
  constructor(bar) {
    this._bar = bar;
  }

  getBar() {
    return this._bar;
  }
}

vous donnera quelque chose comme:

var Foo = (function () {
  function Foo(bar) {    
    this._bar = bar;
  }

  Foo.prototype.getBar = function () {
    return this._bar;
  }

  return Foo;
})();

La deuxième version n'est pas beaucoup plus compliquée, c'est plus de code à maintenir. Lorsque vous impliquez l'héritage, ces modèles deviennent encore plus compliqués.

Étant donné que les classes se compilent selon les mêmes modèles prototypiques que nous utilisons, vous pouvez effectuer la même manipulation de prototype sur elles. Cela inclut l'ajout de méthodes et autres au moment de l'exécution, l'accès aux méthodes Foo.prototype.getBar, etc.

Il existe aujourd'hui une prise en charge de base de la confidentialité dans ES6, bien qu'elle soit basée sur le fait de ne pas exporter les objets que vous ne souhaitez pas accéder. Par exemple, vous pouvez:

const BAR_NAME = 'bar';

export default class Foo {
  static get name() {
    return BAR_NAME;
  }
}

et BAR_NAMEne sera pas disponible pour les autres modules à référencer directement.

De nombreuses bibliothèques ont essayé de prendre en charge ou de résoudre cela, comme Backbone avec son extendsassistant qui prend un hachage non validé de fonctions et de propriétés de type méthode, mais il n'y a pas de système cohérent pour exposer l'héritage prototypique qui n'implique pas de se débrouiller avec le prototype.

Au fur et à mesure que le code JS devient plus compliqué et les bases de code plus grandes, nous avons commencé à faire évoluer de nombreux modèles pour gérer des éléments tels que l'héritage et les modules. L'IIFE utilisé pour créer une portée privée pour les modules a beaucoup d'accolades et de parenthèses; manquer l'un de ceux-ci peut entraîner un script valide qui fait quelque chose d'entièrement différent (sauter le point-virgule après qu'un module peut lui passer le module suivant en tant que paramètre, ce qui est rarement bon).

tl; dr: c'est du sucre pour ce que nous faisons déjà et rend votre intention claire dans le code.

ssube
la source