JavaScript: le guide définitif des cours

Bonne journée, mes amis!



JavaScript utilise le modÚle d'héritage prototypique: chaque objet hérite des champs (propriétés) et des méthodes de l'objet prototype.



Les classes utilisées dans Java ou Swift comme modÚles ou schémas pour créer des objets n'existent pas dans JavaScript. Il n'y a que des objets dans l'héritage prototypique.



L'héritage prototypique peut imiter le modÚle classique de l'héritage de classe. Pour ce faire, ES6 a introduit le mot-clé class: sucre syntaxique pour l'héritage prototypique.



Dans cet article, nous allons apprendre à travailler avec des classes: définir des classes, leurs champs et méthodes privés (privés) et publics (publics), et créer des instances.



1. Définition: le mot-clé de classe



Le mot-clé class est utilisé pour définir une classe:



class User {
    //  
}


Cette syntaxe est appelée déclaration de classe.



La classe peut ne pas avoir de nom. À l'aide d'une expression de classe, vous pouvez affecter une classe à une variable:



const UserClass = class {
    //  
}


Les classes peuvent ĂȘtre exportĂ©es sous forme de modules. Voici un exemple de l'exportation par dĂ©faut:



export default class User {
    //  
}


Et voici un exemple d'export nommé:



export class User {
    //  
}


Les classes sont utilisées pour créer des instances. Une instance est un objet qui contient les données et la logique d'une classe.







Les instances sont créées à l'aide de l'opérateur new: instance = new Class ().



Voici comment créer une instance de la classe User:



const myUser = new User()


2. Initialisation: constructeur ()



constructeur (param1, param2, ...) est une méthode spéciale à l'intérieur d'une classe qui initialise une instance. C'est là que les valeurs initiales des champs d'instance sont définies et configurées.



Dans l'exemple suivant, le constructeur définit la valeur initiale du champ de nom:



class User {
    constructor(name) {
        this.name = name
    }
}


Le constructeur prend un paramÚtre, nom, qui est utilisé pour définir la valeur initiale du champ this.name.



ceci dans le constructeur pointe vers l'instance en cours de création.



L'argument utilisé pour instancier la classe devient un paramÚtre de son constructeur:



class User {
    constructor(name) {
        name // 
        this.name = name
    }
}

const user = new User('')


Le paramÚtre de nom à l'intérieur du constructeur a la valeur «Pechorin».



Si vous ne définissez pas votre propre constructeur, un constructeur standard est créé, qui est une fonction vide qui n'affecte pas l'instance.



3. Champs



Les champs de classe sont des variables contenant des informations spĂ©cifiques. Les champs peuvent ĂȘtre divisĂ©s en deux groupes:



  1. Champs d'instance de classe
  2. Champs de la classe elle-mĂȘme (statique)


Les champs ont Ă©galement deux niveaux d'accĂšs:



  1. Public (public): les champs sont disponibles Ă  la fois dans la classe et dans les instances
  2. Privé (privé): les champs ne sont accessibles qu'au sein de la classe


3.1. Champs publics des instances de classe



class User {
    constructor(name) {
        this.name = name
    }
}


L'expression this.name = name crée un nom de champ d'instance et lui affecte une valeur initiale.



Ce champ est accessible à l'aide d'un accesseur de propriété:



const user = new User('')
user.name // 


Dans ce cas, le nom est un champ public car il est accessible en dehors de la classe User.



Lors de la crĂ©ation de champs implicitement dans un constructeur, il est difficile d'obtenir une liste de tous les champs. Pour cela, les champs doivent ĂȘtre rĂ©cupĂ©rĂ©s auprĂšs du constructeur.



Le meilleur moyen est de dĂ©finir explicitement les champs de la classe. Peu importe ce que fait le constructeur, l'instance a toujours le mĂȘme ensemble de champs.



La proposition de création de champs de classe vous permet de définir des champs au sein d'une classe. De plus, ici, vous pouvez attribuer des valeurs initiales aux champs:



class SomeClass {
    field1
    field2 = ' '

    // ...
}


Modifions le code de la classe User en y définissant un champ de nom public:



class User {
    name

    constructor(name) {
        this.name = name
    }
}

const user = new User('')
user.name // 


Ces champs publics sont trĂšs descriptifs, un rapide coup d'Ɠil sur la classe permet de comprendre la structure de ses donnĂ©es.



De plus, un champ de classe peut ĂȘtre initialisĂ© au moment de la dĂ©finition:



class User {
    name = ''

    constructor() {
        //  
    }
}

const user = new User()
user.name // 


Il n'y a aucune restriction sur l'accÚs aux champs ouverts et leur modification. Vous pouvez lire et attribuer des valeurs à ces champs dans le constructeur, les méthodes et en dehors de la classe.



3.2. Champs privés des instances de classe



L'encapsulation vous permet de masquer les détails d'implémentation interne d'une classe. Quiconque utilise la classe encapsulée s'appuie sur l'interface publique sans entrer dans les détails de l'implémentation de la classe.



Ces classes sont plus faciles à mettre à jour lorsque les détails d'implémentation changent.



Un bon moyen de masquer les dĂ©tails est d'utiliser des champs privĂ©s. Ces champs ne peuvent ĂȘtre lus et modifiĂ©s que dans la classe Ă  laquelle ils appartiennent. Les champs privĂ©s ne sont pas disponibles en dehors de la classe.



Pour rendre un champ privĂ©, faites prĂ©cĂ©der son nom d'un symbole #, par exemple #myPrivateField. Lorsque vous faites rĂ©fĂ©rence Ă  un tel champ, le prĂ©fixe spĂ©cifiĂ© doit toujours ĂȘtre utilisĂ©.



Rendons le champ de nom privé:



class User {
    #name

    constructor(name) {
        this.#name = name
    }

    getName() {
        return this.#name
    }
}

const user = new User('')
user.getName() // 
user.#name // SyntaxError


#name est un champ privé. Il n'est accessible qu'à l'intérieur de la classe User. La méthode getName () fait cela.



Cependant, essayer d'accĂ©der Ă  #name en dehors de la classe User gĂ©nĂ©rera une erreur de syntaxe: SyntaxError: Le champ privĂ© '#name' doit ĂȘtre dĂ©clarĂ© dans une classe englobante.



3.3. Champs statiques publics



Dans une classe, vous pouvez dĂ©finir des champs qui appartiennent Ă  la classe elle-mĂȘme: les champs statiques. Ces champs sont utilisĂ©s pour crĂ©er des constantes qui stockent les informations dont la classe a besoin.



Pour créer des champs statiques, utilisez le mot clé static avant le nom du champ: static myStaticField.



Ajoutons un nouveau champ de type pour définir le type d'utilisateur: administrateur ou régulier. Les champs statiques TYPE_ADMIN et TYPE_REGULAR sont des constantes pour chaque type d'utilisateur:



class User {
    static TYPE_ADMIN = 'admin'
    static TYPE_REGULAR = 'regular'

    name
    type

    constructor(name, type) {
        this.name = name
        this.type = type
    }
}

const admin = new User(' ', User.TYPE_ADMIN)
admin.type === User.TYPE_ADMIN // true


Pour accéder aux champs statiques, utilisez le nom de classe et le nom de propriété: User.TYPE_ADMIN et User.TYPE_REGULAR.



3.4. Champs statiques privés



Parfois, les champs statiques font également partie de l'implémentation interne de la classe. Pour encapsuler ces champs, vous pouvez les rendre privés.



Pour ce faire, préfixez le nom du champ avec #: static #myPrivateStaticFiled.



Disons que nous voulons limiter le nombre d'instances de la classe User. Des champs statiques privĂ©s peuvent ĂȘtre crĂ©Ă©s pour masquer les informations sur le nombre d'instances:



class User {
    static #MAX_INSTANCES = 2
    static #instances = 0
}

name

constructor(name) {
    User.#instances++
    if (User.#instances > User.#MAX_INSTANCES) {
        throw new Error('    User')
    }
    this.name = name
}

new User('')
new User('')
new User('') //     User


Le champ statique User. # MAX_INSTANCES définit le nombre d'instances autorisé et User. # Instances définit le nombre d'instances créées.



Ces champs statiques privés ne sont disponibles que dans la classe User. Rien du monde extérieur ne peut influencer les contraintes: c'est l'un des avantages de l'encapsulation.



Environ. lane: si vous limitez le nombre d'instances à une, vous obtenez une implémentation intéressante du modÚle de conception Singleton.



4. MĂ©thodes



Les champs contiennent des données. La possibilité de modifier les données est fournie par des fonctions spéciales qui font partie de la classe: méthodes.



JavaScript prend en charge les méthodes d'instance et les méthodes statiques.



4.1. MĂ©thodes d'instance



Les méthodes d'une instance d'une classe peuvent modifier ses données. Les méthodes d'instance peuvent appeler d'autres méthodes d'instance ainsi que des méthodes statiques.



Par exemple, définissons une méthode getName () qui renvoie le nom de l'utilisateur:



class User {
    name = ''

    constructor(name) {
        this.name = name
    }

    getName() {
        return this.name
    }
}

const user = new User('')
user.getName() // 


Dans une méthode de classe, ainsi que dans un constructeur, cela pointe vers l'instance en cours de création. Utilisez ceci pour obtenir des données d'instance: this.field, ou pour appeler des méthodes: this.method ().



Ajoutons une nouvelle méthode nameContains (str) qui prend un argument et appelle une autre méthode:



class User {
    name

    constructor(name) {
        this.name = name
    }

    getName() {
        return this.name
    }

    nameContains(str) {
        return this.getName().includes(str)
    }
}

const user = new User('')
user.nameContains('') // true
user.nameContains('') // false


nameContains (str) est une méthode de la classe User qui prend un argument. Il appelle une autre méthode d'instance getName () pour obtenir le nom d'utilisateur.



La mĂ©thode peut Ă©galement ĂȘtre privĂ©e. Pour rendre une mĂ©thode privĂ©e, utilisez le prĂ©fixe #.



Rendons la méthode getName () privée:



class User {
    #name

    constructor(name) {
        this.#name = name
    }

    #getName() {
        return this.#name
    }

    nameContains(str) {
        return this.#getName().includes(str)
    }
}

const user = new User('')
user.nameContains('') // true
user.nameContains('') // false

user.#getName // SyntaxError


#getName () est une méthode privée. Dans la méthode nameContains (str), nous l'appelons comme ceci: # GetName ().



Étant privĂ©e, la mĂ©thode #getName () ne peut pas ĂȘtre appelĂ©e en dehors de la classe User.



4.2. Getters et Setters



Les getters et les setters sont des accesseurs ou des propriétés calculées. Ce sont des méthodes qui imitent les champs, mais vous permettent de lire et d'écrire des données.



Les getters sont utilisés pour recevoir des données, les setters sont utilisés pour les modifier.



Pour éviter d'attribuer une chaßne vide au champ de nom, enveloppez le champ privé #nameValue dans un getter et un setter:



class User {
    #nameValue

    constructor(name) {
        this.name = name
    }

    get name() {
        return this.#nameValue
    }

    set name(name) {
        if (name === '') {
            throw new Error('     ')
        }
        this.#nameValue = name
    }
}

const user = new User('')
user.name //  , 
user.name = '' //  

user.name = '' //      


4.3. MĂ©thodes statiques



Les mĂ©thodes statiques sont des fonctions appartenant Ă  la classe elle-mĂȘme. Ils dĂ©finissent la logique de la classe, pas ses instances.



Pour créer une méthode statique, utilisez le mot-clé static devant le nom de la méthode: static myStaticMethod ().



Lorsque vous travaillez avec des méthodes statiques, vous devez garder à l'esprit deux rÚgles simples:



  1. Une méthode statique a accÚs aux champs statiques
  2. Il n'a pas accĂšs aux champs d'instance


Créons une méthode statique pour vérifier qu'un utilisateur avec le nom spécifié a déjà été créé:



class User {
    static #takenNames = []

    static isNameTaken(name) {
        return User.#takenNames.includes(name)
    }

    name = ''

    constructor(name) {
        this.name = name
        User.#takenNames.push(name)
    }
}

const user = new User('')

User.isNameTaken('') // true
User.isNameTaken('') // false


isNameTaken () est une méthode statique qui utilise le champ statique privé User. # takeNames pour déterminer quels noms ont été utilisés.



Les mĂ©thodes statiques peuvent Ă©galement ĂȘtre privĂ©es: static #myPrivateStaticMethod (). Ces mĂ©thodes ne peuvent ĂȘtre appelĂ©es que dans la classe.



5. HĂ©ritage: Ă©tend



Les classes dans JavaScript prennent en charge l'héritage à l'aide du mot clé extend.



Dans l'expression, la classe Child s'étend à Parent {}, la classe Child hérite des constructeurs, des champs et des méthodes de Parent.



Créons une classe enfant ContentWriter qui étend la classe parent User:



class User {
    name

    constructor(name) {
        this.name = name
    }

    getName() {
        return this.name
    }
}

class ContentWriter extends User {
    posts = []
}

const writer = new ContentWriter('')

writer.name // 
writer.getName() // 
writer.posts // []


ContentWriter hĂ©rite de User un constructeur, une mĂ©thode getName () et un champ de nom. ContentWriter lui-mĂȘme dĂ©finit un nouveau champ de messages.



Notez que les champs privés et les méthodes de la classe parent ne sont pas hérités par les classes enfants.



5.1. Constructeur parent: super () dans le constructeur ()


Afin d'appeler le constructeur de la classe parent dans la classe enfant, utilisez la fonction spéciale super () disponible dans le constructeur de la classe enfant.



Laissez le constructeur ContentWriter appeler le constructeur parent et initialiser le champ posts:



class User {
    name

    constructor(name) {
        this.name = name
    }

    getName() {
        return this.name
    }
}

class ContentWriter extends User {
    posts = []

    constructor(name, posts) {
        super(name)
        this.posts = posts
    }
}

const writer = new ContentWriter('', ['  '])
writer.name // 
writer.posts // ['  ']


super (nom) dans la classe enfant ContentWriter appelle le constructeur de classe parent User.



Notez que super () est appelé dans le constructeur enfant avant d'utiliser le mot-clé this. L'appel super () "lie" le constructeur parent à l'instance.



class Child extends Parent {
    constructor(value1, value2) {
        //  !
        this.prop2 = value2
        super(value1)
    }
}


5.2. Instance parent: super dans les méthodes


Afin d'accéder à la méthode parente dans la classe enfant, utilisez le super raccourci spécial:



class User {
    name

    constructor(name) {
        this.name = name
    }

    getName() {
        return this.name
    }
}

class ContentWriter extends User {
    posts = []

    constructor(name, posts) {
        super(name)
        this.posts = posts
    }

    getName() {
        const name = super.getName()
        if (name === '') {
            return ''
        }
        return name
    }
}

const writer = new ContentWriter('', ['  '])
writer.getName() // 


getName () de la classe ContentWriter enfant appelle la méthode getName () de la classe parent User.



C'est ce qu'on appelle le remplacement de méthode.



Notez que super peut Ă©galement ĂȘtre utilisĂ© pour les mĂ©thodes statiques de la classe parente.



6. VĂ©rification du type d'objet: instanceof



L'expression objet instanceof Class détermine si un objet est une instance de la classe spécifiée.



Prenons un exemple:



class User {
    name

    constructor(name) {
        this.name = name
    }

    getName() {
        return this.name
    }
}

const user = new User('')
const obj = {}

user instanceof User // true
obj instanceof User // false


L'opérateur instanceof est polymorphe: il examine toute la chaßne de classes.



class User {
    name

    constructor(name) {
        this.name = name
    }

    getName() {
        return this.name
    }
}

class ContentWriter extends User {
    posts = []

    constructor(name, posts) {
        super(name)
        this.posts = posts
    }
}

const writer = new ContentWriter('', ['  '])

writer instanceof ContentWriter // true
writer instanceof User // true


Et si nous avons besoin de dĂ©finir une classe d'instance spĂ©cifique? La propriĂ©tĂ© constructeur peut ĂȘtre utilisĂ©e pour cela:



writer.constructor === ContentWriter // true
writer.constructor === User // false
// 
writer.__proto__ === ContentWriter.prototype // true
writer.__proto__ === User.prototype // false


7. Classes et prototypes



Il faut dire que la syntaxe de classe est une belle abstraction par rapport à l'héritage prototypique. Vous n'avez pas besoin de référencer des prototypes pour utiliser des classes.



Cependant, les classes ne sont qu'une superstructure sur l'héritage prototypique. Toute classe est une fonction qui crée une instance lorsqu'un constructeur est appelé.



Les deux exemples suivants sont identiques.



Des classes:



class User {
    constructor(name) {
        this.name = name
    }

    getName() {
        return this.name
    }
}

const user = new User('')

user.getName() // 
user instanceof User // true


Prototypes:



function User(name) {
    this.name = name
}

User.prototype.getName = function () {
    return this.name
}

const user = new User('')

user.getName() // 
user instanceof User // true


Par conséquent, la compréhension des classes nécessite une bonne connaissance de l'héritage prototypique.



8. Disponibilité des capacités de classe



Les capacités de classe présentées dans cet article sont réparties entre la spécification ES6 et les propositions de la troisiÚme étape de considération:







Environ. Par: selon Puis-je utiliser, la prise en charge des champs de classe privée est actuellement de 68%.



9. Conclusion



Les classes en JavaScript sont utilisĂ©es pour initialiser les instances Ă  l'aide d'un constructeur et dĂ©finir leurs champs et mĂ©thodes. Le mot-clĂ© static peut ĂȘtre utilisĂ© pour dĂ©finir les champs et les mĂ©thodes de la classe elle-mĂȘme.



L'héritage est implémenté à l'aide du mot clé extend. Le mot clé super vous permet d'accéder à la classe parente depuis l'enfant.



Afin de profiter de l'encapsulation, c'est-à-dire masquer les détails d'implémentation internes, rendre les champs et les méthodes privés. Les noms de ces champs et méthodes doivent commencer par le symbole #.



Les classes sont omniprésentes dans le JavaScript moderne.



J'espÚre que cet article vous a été utile. Merci de votre attention.



All Articles