Recherche…


Syntaxe

  • classe Foo {}
  • classe Foo étend Bar {}
  • classe Foo {constructeur () {}}
  • classe Foo {myMethod () {}}
  • classe Foo {get myProperty () {}}
  • class Foo {set myProperty (newValue) {}}
  • classe Foo {static myStaticMethod () {}}
  • classe Foo {static get myStaticProperty () {}}
  • const Foo = classe Foo {};
  • const Foo = class {};

Remarques

class support de class n'a été ajouté à JavaScript que dans le cadre du standard 2015.

Les classes javascript sont du sucre syntaxique par rapport à l'héritage basé sur un prototype déjà existant de JavaScript. Cette nouvelle syntaxe n'introduit pas de nouveau modèle d'héritage orienté objet vers JavaScript, mais simplement un moyen plus simple de traiter les objets et l'héritage. Une déclaration de class est essentiellement un raccourci pour définir manuellement une function constructeur et ajouter des propriétés au prototype du constructeur. Une différence importante est que les fonctions peuvent être appelées directement (sans le new mot-clé), alors qu'une classe appelée directement lancera une exception.

class someClass {
    constructor () {}
    someMethod () {}
}
 
console.log(typeof someClass);               
console.log(someClass);
console.log(someClass === someClass.prototype.constructor);                         
console.log(someClass.prototype.someMethod);
 
// Output:
// function
// function someClass() { "use strict"; }
// true
// function () { "use strict"; }

Si vous utilisez une version antérieure de JavaScript, vous aurez besoin d'un transpiler tel que ou afin de compiler le code dans une version que la plate-forme cible pourra comprendre.

Constructeur de classe

La partie fondamentale de la plupart des classes est son constructeur, qui définit l'état initial de chaque instance et gère tous les paramètres transmis lors de l'appel de new .

Elle est définie dans un bloc de class comme si vous définissiez une méthode nommée constructor , bien qu'elle soit en réalité traitée comme un cas particulier.

class MyClass {
    constructor(option) {
        console.log(`Creating instance using ${option} option.`);
        this.option = option;
    }
}

Exemple d'utilisation:

const foo = new MyClass('speedy'); // logs: "Creating instance using speedy option"

Une petite chose à noter est qu'un constructeur de classes ne peut pas être rendu statique via le mot-clé static , comme décrit ci-dessous pour les autres méthodes.

Méthodes statiques

Les méthodes et propriétés statiques sont définies sur la classe / constructeur lui - même , et non sur les objets d'instance. Celles-ci sont spécifiées dans une définition de classe en utilisant le mot-clé static .

class MyClass {
    static myStaticMethod() {
        return 'Hello';
    }

    static get myStaticProperty() {
        return 'Goodbye';
    }
}

console.log(MyClass.myStaticMethod()); // logs: "Hello"
console.log(MyClass.myStaticProperty); // logs: "Goodbye"

Nous pouvons voir que les propriétés statiques ne sont pas définies sur les instances d'objet:

const myClassInstance = new MyClass();

console.log(myClassInstance.myStaticProperty); // logs: undefined

Cependant, ils sont définis sur des sous-classes:

class MySubClass extends MyClass {};

console.log(MySubClass.myStaticMethod()); // logs: "Hello"
console.log(MySubClass.myStaticProperty); // logs: "Goodbye"

Getters et Setters

Les getters et setters vous permettent de définir un comportement personnalisé pour lire et écrire une propriété donnée sur votre classe. Pour l'utilisateur, ils apparaissent comme n'importe quelle propriété typique. Cependant, en interne, une fonction personnalisée que vous fournissez est utilisée pour déterminer la valeur à laquelle la propriété est accédée (le getter) et pour effectuer les modifications nécessaires lors de l'attribution de la propriété (le setter).

Dans une définition de class , un getter est écrit comme une méthode sans argument préfixée par le mot clé get . Un setter est similaire, sauf qu'il accepte un argument (la nouvelle valeur étant assignée) et que le mot-clé set est utilisé à la place.

Voici un exemple de classe qui fournit un getter et un setter pour sa propriété .name . Chaque fois qu'il est assigné, nous enregistrerons le nouveau nom dans un tableau .names_ interne. Chaque fois que vous y accédez, nous vous renverrons le dernier nom.

class MyClass {
    constructor() {
        this.names_ = [];
    }

    set name(value) {
        this.names_.push(value);
    }

    get name() {
        return this.names_[this.names_.length - 1];
    }
}

const myClassInstance = new MyClass();
myClassInstance.name = 'Joe';
myClassInstance.name = 'Bob';

console.log(myClassInstance.name); // logs: "Bob"
console.log(myClassInstance.names_); // logs: ["Joe", "Bob"]

Si vous définissez uniquement un setter, toute tentative d'accès à la propriété renverra toujours undefined .

const classInstance = new class {
    set prop(value) {
        console.log('setting', value);
    }
};

classInstance.prop = 10; // logs: "setting", 10

console.log(classInstance.prop); // logs: undefined

Si vous définissez uniquement un getter, toute tentative d'attribution de la propriété n'aura aucun effet.

const classInstance = new class {
    get prop() {
        return 5;
    }
};

classInstance.prop = 10;

console.log(classInstance.prop); // logs: 5

Héritage de classe

L'héritage fonctionne exactement comme dans d'autres langages orientés objet: les méthodes définies sur la superclasse sont accessibles dans la sous-classe d'extension.

Si la sous - classe déclare son constructeur , alors il doit appeler le constructeur des parents via super() avant de pouvoir accéder à this .

class SuperClass {

    constructor() {
        this.logger = console.log;
    }

    log() {
        this.logger(`Hello ${this.name}`);
    }

}

class SubClass extends SuperClass {

    constructor() {
        super();
        this.name = 'subclass';
    }

}

const subClass = new SubClass();

subClass.log(); // logs: "Hello subclass"

Membres privés

JavaScript ne prend pas en charge techniquement les membres privés en tant que fonctionnalité linguistique. La confidentialité - décrite par Douglas Crockford - est émulée à la place par des fermetures (portée de fonction préservée) qui seront générées à chaque appel d'instanciation d'une fonction constructeur.

L'exemple de Queue montre comment, avec les fonctions constructeur, l'état local peut être préservé et rendu accessible via des méthodes privilégiées.

class Queue {

  constructor () {                    // - does generate a closure with each instantiation.

    const list = [];                  // - local state ("private member").

    this.enqueue = function (type) {  // - privileged public method
                                      //   accessing the local state
      list.push(type);                //   "writing" alike.
      return type;
    };
    this.dequeue = function () {      // - privileged public method
                                      //   accessing the local state
      return list.shift();            //   "reading / writing" alike.
    };
  }
}


var q = new Queue;            //
                              //
q.enqueue(9);                 // ... first in ...
q.enqueue(8);                 //
q.enqueue(7);                 //
                              //
console.log(q.dequeue());     // 9 ... first out.
console.log(q.dequeue());     // 8
console.log(q.dequeue());     // 7
console.log(q);               // {}
console.log(Object.keys(q));  // ["enqueue","dequeue"]

À chaque instanciation d'un type de Queue le constructeur génère une fermeture.

Ainsi , les deux d'une Queue d' enqueue dequeue Object.keys(q) list Queue propres méthodes de Type enqueue et dequeue (voir Object.keys(q) ) font encore avoir accès à la list qui continue à vivre dans sa portée englobante qui, au moment de la construction, a été préservée.

En utilisant ce modèle - en émulant les membres privés via des méthodes publiques privilégiées - il faut garder à l’esprit qu’à chaque instance, de la mémoire supplémentaire sera consommée pour chaque propre méthode de propriété (car c’est du code qui ne peut pas être partagé / réutilisé). Il en va de même pour la quantité / la taille de l’état qui sera stockée dans une telle fermeture.

Noms de méthodes dynamiques

Il est également possible d’évaluer des expressions lorsqu’on nomme des méthodes similaires à la façon dont vous pouvez accéder aux propriétés d’un objet avec [] . Cela peut être utile pour avoir des noms de propriétés dynamiques, mais est souvent utilisé en conjonction avec les symboles.

let METADATA = Symbol('metadata');

class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
  
  // example using symbols
  [METADATA]() {
    return {
      make: this.make,
      model: this.model
    };
  }

  // you can also use any javascript expression

  // this one is just a string, and could also be defined with simply add()
  ["add"](a, b) {
    return a + b;
  }

  // this one is dynamically evaluated
  [1 + 2]() {
    return "three";
  }
}

let MazdaMPV = new Car("Mazda", "MPV");
MazdaMPV.add(4, 5); // 9
MazdaMPV[3](); // "three"
MazdaMPV[METADATA](); // { make: "Mazda", model: "MPV" }

Les méthodes

Des méthodes peuvent être définies dans les classes pour exécuter une fonction et renvoyer éventuellement un résultat.
Ils peuvent recevoir des arguments de l'appelant.

class Something {
    constructor(data) {
        this.data = data
    }

    doSomething(text) {
        return {
            data: this.data,
            text
        }
    }
}

var s = new Something({})
s.doSomething("hi") // returns: { data: {}, text: "hi" }

Gestion de données privées avec des classes

L'un des obstacles les plus courants à l'utilisation des classes est de trouver la bonne approche pour gérer les états privés. Il existe 4 solutions courantes pour gérer les états privés:

Utiliser des symboles

Les symboles sont un nouveau type primitif introduit dans ES2015, tel que défini dans MDN

Un symbole est un type de données unique et immuable qui peut être utilisé comme identifiant pour les propriétés de l'objet.

Lorsque vous utilisez le symbole comme clé de propriété, il n'est pas énumérable.

En tant que tels, ils ne seront pas révélés en utilisant for var in ou Object.keys .

Ainsi, nous pouvons utiliser des symboles pour stocker des données privées.

const topSecret = Symbol('topSecret'); // our private key; will only be accessible on the scope of the module file
export class SecretAgent{
    constructor(secret){
        this[topSecret] = secret; // we have access to the symbol key (closure)
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(topSecret[topSecret]); // we have access to topSecret
        };
    }
}

Comme les symbols sont uniques, nous devons nous référer au symbole d'origine pour accéder à la propriété privée.

import {SecretAgent} from 'SecretAgent.js'
const agent = new SecretAgent('steal all the ice cream');
// ok lets try to get the secret out of him!
Object.keys(agent); // ['coverStory'] only cover story is public, our secret is kept.
agent[Symbol('topSecret')]; // undefined, as we said, symbols are always unique, so only the original symbol will help us to get the data.

Mais ce n'est pas 100% privé; brisons cet agent! Nous pouvons utiliser la méthode Object.getOwnPropertySymbols pour obtenir les symboles d'objet.

const secretKeys = Object.getOwnPropertySymbols(agent);
agent[secretKeys[0]] // 'steal all the ice cream' , we got the secret.

Utiliser WeakMaps

WeakMap est un nouveau type d'objet qui a été ajouté pour es6.

Comme défini sur MDN

L'objet WeakMap est un ensemble de paires clé / valeur dans lesquelles les clés sont faiblement référencées. Les clés doivent être des objets et les valeurs peuvent être des valeurs arbitraires.

Une autre caractéristique importante de WeakMap est la définition de MDN .

La clé dans un WeakMap est maintenue faiblement. Cela signifie que, s’il n’ya pas d’autres références fortes à la clé, l’entrée entière sera supprimée de WeakMap par le garbage collector.

L'idée est d'utiliser le WeakMap, en tant que carte statique pour toute la classe, pour contenir chaque instance en tant que clé et conserver les données privées en tant que valeur pour cette clé d'instance.

Ainsi, seulement dans la classe, nous aurons accès à la collection WeakMap .

Essayons notre agent avec WeakMap :

const topSecret = new WeakMap(); // will hold all private data of all instances.
export class SecretAgent{
    constructor(secret){
        topSecret.set(this,secret); // we use this, as the key, to set it on our instance private data
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(topSecret.get(this)); // we have access to topSecret
        };
    }
}

Puisque le const topSecret est défini dans notre fermeture de module et que nous ne l'avons pas lié à nos propriétés d'instance, cette approche est totalement privée et nous ne pouvons pas atteindre l'agent topSecret .

Définir toutes les méthodes à l'intérieur du constructeur

L'idée ici est simplement de définir toutes nos méthodes et membres à l' intérieur du constructeur et utiliser la fermeture pour accéder aux membres privés sans les affecter à this .

   export class SecretAgent{
        constructor(secret){
            const topSecret = secret;
            this.coverStory = 'just a simple gardner';
            this.doMission = () => {
                figureWhatToDo(topSecret); // we have access to topSecret
            };
        }
    }

Dans cet exemple également, les données sont 100% privées et ne peuvent pas être atteintes en dehors de la classe. Notre agent est donc sécurisé.

Utilisation des conventions de dénomination

Nous déciderons que toute propriété privée sera préfixée par _ .

Notez que pour cette approche, les données ne sont pas vraiment privées.

export class SecretAgent{
    constructor(secret){
        this._topSecret = secret; // it private by convention
        this.coverStory = 'just a simple gardner';
        this.doMission = () => {
            figureWhatToDo(this_topSecret); 
        };
    }
}

Liaison de nom de classe

Le nom de ClassDeclaration est lié de différentes manières dans différentes portées -

  1. La portée dans laquelle la classe est définie - let liaison
  2. La portée de la classe elle-même - entre { et } dans la class {} - la liaison const
class Foo {
  // Foo inside this block is a const binding
}
// Foo here is a let binding

Par exemple,

class A {
  foo() {
    A = null; // will throw at runtime as A inside the class is a `const` binding
  }
}
A = null; // will NOT throw as A here is a `let` binding

Ce n'est pas la même chose pour une fonction -

function A() {
  A = null; // works
}
A.prototype.foo = function foo() {
  A = null; // works
}
A = null; // works


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow