Catégories
Astuces et Design

Les saveurs de la programmation orientée objet (en JavaScript)

Dans mes recherches, j'ai trouvé qu'il existe quatre approches de la programmation orientée objet en JavaScript:

Quelles méthodes dois-je utiliser? Quelle est la «meilleure» façon? Ici, je vais présenter mes conclusions ainsi que des informations qui peuvent vous aider à décider ce qui vous convient.

Pour prendre cette décision, nous n'allons pas seulement examiner les différentes saveurs, mais comparer les aspects conceptuels entre eux:

Commençons par une base de la POO en JavaScript.

Qu'est-ce que la programmation orientée objet?

La programmation orientée objet est un moyen d'écrire du code qui vous permet de créer différents objets à partir d'un objet commun. L'objet commun est généralement appelé un plan tandis que les objets créés sont appelés instances.

Chaque instance a des propriétés qui ne sont pas partagées avec d'autres instances. Par exemple, si vous disposez d'un plan directeur humain, vous pouvez créer des instances humaines avec des noms différents.

Le deuxième aspect de la programmation orientée objet concerne structurant code lorsque vous avez plusieurs niveaux de plans. C'est ce qu'on appelle communément l'héritage ou le sous-classement.

Le troisième aspect de la programmation orientée objet concerne encapsulation où vous masquez certaines informations dans l'objet afin qu'elles ne soient pas accessibles.

Si vous avez besoin de plus que cette brève introduction, voici un article qui présente cet aspect de la programmation orientée objet si vous avez besoin d'aide.

Commençons par les bases – une introduction aux quatre saveurs de la programmation orientée objet.

Les quatre saveurs de la programmation orientée objet

Il existe quatre façons d'écrire la programmation orientée objet en JavaScript. Elles sont:

Utilisation des fonctions du constructeur

Les constructeurs sont des fonctions qui contiennent un this mot-clé.

function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

this vous permet de stocker (et d'accéder) aux valeurs uniques créées pour chaque instance. Vous pouvez créer une instance avec le new mot-clé.

const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

const zell = new Human('Zell', 'Liew')
console.log(zell.firstName) // Zell
console.log(zell.lastName) // Liew

Syntaxe de classe

On dit que les classes sont le «sucre syntaxique» des fonctions Constructor. Comme dans, les classes sont un moyen plus simple d'écrire des fonctions de constructeur.

Il y a de sérieuses disputes quant à savoir si les classes sont mauvaises (comme ceci et cela). Nous n’allons pas nous plonger dans ces arguments ici. Au lieu de cela, nous allons simplement regarder comment écrire du code avec des classes et décider si les classes sont meilleures que les constructeurs en fonction du code que nous écrivons.

Les classes peuvent être écrites avec la syntaxe suivante:

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Remarquez le constructor La fonction contient le même code que la syntaxe Constructor ci-dessus? Nous devons le faire car nous voulons initialiser les valeurs dans this. (Nous pouvons sauter constructor si nous n’avons pas besoin d’initialiser les valeurs. Plus d'informations à ce sujet plus tard sous Héritage).

À première vue, les classes semblent être inférieures aux constructeurs – il y a plus de code à écrire! Tenez vos chevaux et ne formez pas de conclusion à ce stade. Nous avons beaucoup plus à couvrir. Les cours commencent à briller plus tard.

Comme précédemment, vous pouvez créer une instance avec le new mot-clé.

const chris = new Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Objets liés à d'autres objets (OLOO)

OLOO a été inventé et popularisé par Kyle Simpson. Dans OLOO, vous définissez le plan directeur comme un objet normal. Vous utilisez ensuite une méthode (souvent nommée init, mais ce n’est pas obligatoire constructor est à une classe) pour initialiser l'instance.

const Human = {
  init () {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Tu utilises Object.create pour créer une instance. Après avoir créé l'instance, vous devez exécuter votre init une fonction.

const chris = Object.create(Human)
chris.init('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Vous pouvez enchaîner init après Object.create si tu es revenu this à l'intérieur init.

const Human = {
  init () {
    // ...
    return this 
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Fonctions d'usine

Les fonctions d'usine sont des fonctions qui renvoient un objet. Vous pouvez renvoyer n'importe quel objet. Vous pouvez même renvoyer une instance de classe ou une instance OLOO – et ce sera toujours une fonction Factory valide.

Voici le moyen le plus simple de créer des fonctions Factory:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName
  }
}

Tu n'as pas besoin new pour créer des instances avec des fonctions Factory. Vous appelez simplement la fonction.

const chris = Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Maintenant que nous avons vu ces quatre possibilités de configuration de la POO, voyons comment vous déclarez des propriétés et des méthodes sur chacune d'elles afin que nous puissions mieux comprendre comment travailler avec elles avant de passer aux comparaisons plus importantes que nous essayons de faire.


Déclaration des propriétés et des méthodes

Les méthodes sont des fonctions déclarées comme propriété d’un objet.

const someObject = {
  someMethod () { /* ... */ }
}

Dans la programmation orientée objet, il existe deux façons de déclarer des propriétés et des méthodes:

  1. Directement sur l'instance
  2. Dans le prototype

Apprenons à faire les deux.

Déclaration de propriétés et de méthodes avec des constructeurs

Si vous souhaitez déclarer une propriété directement sur une instance, vous pouvez écrire la propriété dans la fonction constructeur. Assurez-vous de le définir comme propriété pour this.

function Human (firstName, lastName) {
  // Declares properties
  this.firstName = firstName
  this.lastname = lastName

  // Declares methods
  this.sayHello = function () {
    console.log(`Hello, I'm ${firstName}`)
  }
}

const chris = new Human('Chris', 'Coyier')
console.log(chris)

Les méthodes sont généralement déclarées sur le prototype car Prototype permet aux instances d'utiliser la même méthode. Il s’agit d’une «empreinte de code» plus petite.

Pour déclarer des propriétés sur le prototype, vous devez utiliser le prototype propriété.

function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastname = lastName
}

// Declaring method on a prototype
Human.prototype.sayHello = function () {
  console.log(`Hello, I'm ${this.firstName}`)
}

Cela peut être maladroit si vous souhaitez déclarer plusieurs méthodes dans un prototype.

// Declaring methods on a prototype
Human.prototype.method1 = function () { /*...*/ }
Human.prototype.method2 = function () { /*...*/ }
Human.prototype.method3 = function () { /*...*/ }

Vous pouvez faciliter les choses en utilisant des fonctions de fusion telles que Object.assign.

Object.assign(Human.prototype, {
  method1 () { /*...*/ },
  method2 () { /*...*/ },
  method3 () { /*...*/ }
})

Object.assign ne prend pas en charge la fusion des fonctions Getter et Setter. Vous avez besoin d'un autre outil. Voici pourquoi. Et voici un outil que j'ai créé pour fusionner des objets avec des Getters et des Setters.

Déclarer des propriétés et des méthodes avec des classes

Vous pouvez déclarer des propriétés pour chaque instance dans le constructor une fonction.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
      this.lastname = lastName

      this.sayHello = function () {
        console.log(`Hello, I'm ${firstName}`)
      }
  }
}

Il est plus facile de déclarer des méthodes sur le prototype. Vous écrivez la méthode après constructor comme une fonction normale.

class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

Il est plus facile de déclarer plusieurs méthodes sur les classes que sur les constructeurs. Vous n’avez pas besoin du Object.assign syntaxe. Vous écrivez simplement plus de fonctions.

Remarque: il n'y a pas , entre les déclarations de méthode dans une classe.

class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  method1 () { /*...*/ }
  method2 () { /*...*/ }
  method3 () { /*...*/ }
}

Déclaration des propriétés et des méthodes avec OLOO

Vous utilisez le même processus pour déclarer des propriétés et des méthodes sur une instance. Vous les attribuez en tant que propriété de this.

const Human = {
  init (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.sayHello = function () {
      console.log(`Hello, I'm ${firstName}`)
    }

    return this
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris)

Pour déclarer des méthodes dans le prototype, vous écrivez la méthode comme un objet normal.

const Human = {
  init () { /*...*/ },
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

Déclaration des propriétés et des méthodes avec les fonctions Factory

Vous pouvez déclarer des propriétés et des méthodes directement en les incluant dans l'objet retourné.

function Human (firstName, lastName) {
  return {
    firstName,
    lastName, 
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

Vous ne pouvez pas déclarer de méthodes sur le prototype lorsque vous utilisez les fonctions Factory. Si vous voulez vraiment des méthodes sur le prototype, vous devez renvoyer une instance Constructor, Class ou OLOO. (Ne faites pas cela car cela n'a aucun sens.)

// Do not do this
function createHuman (...args) {
  return new Human(...args)
}

Où déclarer les propriétés et les méthodes

Devez-vous déclarer des propriétés et des méthodes directement sur l'instance? Ou devriez-vous utiliser prototype autant que vous le pouvez?

Beaucoup de gens sont fiers que JavaScript soit un «langage prototypique» (ce qui signifie qu'il utilise des prototypes). À partir de cette déclaration, vous pouvez faire l'hypothèse qu'il est préférable d'utiliser des «prototypes».

La vraie réponse est: Cela n’a pas d’importance.

Si vous déclarez des propriétés et des méthodes sur des instances, chaque instance prendra un peu plus de mémoire. Si vous déclarez des méthodes sur des prototypes, la mémoire utilisée par chaque instance diminuera, mais pas beaucoup. Cette différence est insignifiante avec la puissance de traitement informatique qu'elle est aujourd'hui. Au lieu de cela, vous voulez voir à quel point il est facile d'écrire du code – et s'il est possible d'utiliser des prototypes en premier lieu.

Par exemple, si vous utilisez des classes ou OLOO, vous ferez mieux d'utiliser des prototypes car le code est plus facile à écrire. Si vous utilisez les fonctions Factory, vous ne pouvez pas utiliser de prototypes. Vous ne pouvez créer des propriétés et des méthodes que directement sur l'instance.

J'ai écrit un article séparé sur la compréhension des prototypes JavaScript si vous souhaitez en savoir plus.

Verdict préliminaire

Nous pouvons prendre quelques notes à partir du code que nous avons écrit ci-dessus. Ces opinions sont les miennes!

  1. Les classes sont meilleures que les constructeurs car il est plus facile d'écrire plusieurs méthodes sur les classes.
  2. OLOO est bizarre à cause du Object.create partie. J'ai donné une course à OLOO pendant un moment, mais j'oublie toujours d'écrire Object.create. C’est assez bizarre pour moi de ne pas l’utiliser.
  3. Les classes et fonctions factry sont les plus faciles à utiliser. Le problème est que les fonctions Factory ne prennent pas en charge les prototypes. Mais comme je l'ai dit, cela n'a pas vraiment d'importance en production.

Nous en sommes à deux. Doit-on alors choisir des classes ou des fonctions d'usine? Comparons-les!


Classes vs fonctions d'usine – Héritage

Pour poursuivre la discussion sur les classes et les fonctions d'usine, nous devons comprendre trois autres concepts étroitement liés à la programmation orientée objet.

  1. Héritage
  2. Encapsulation
  3. this

Commençons par l'héritage.

Qu'est-ce que l'héritage?

L'héritage est un mot chargé. À mon avis, de nombreuses personnes dans l'industrie utilisent l'héritage de manière incorrecte. Le mot «héritage» est utilisé lorsque vous recevez des choses de quelque part. Par exemple:

  • Si vous recevez un héritage de vos parents, cela signifie que vous en retirez de l'argent et des biens.
  • Si vous héritez des gènes de vos parents, cela signifie que vous en tirez vos gènes.
  • Si vous héritez d'un processus de votre enseignant, cela signifie que vous obtenez ce processus de leur part.

Assez simple.

En JavaScript, l'héritage peut signifier la même chose: où vous obtenez les propriétés et les méthodes du plan parent.

Ça signifie tout les instances héritent en fait de leurs plans. Ils héritent des propriétés et des méthodes de deux manières:

  1. en créant une propriété ou une méthode directement lors de la création de l'instance
  2. via la chaîne Prototype

Nous avons expliqué comment utiliser les deux méthodes dans l'article précédent, alors reportez-vous-y si vous avez besoin d'aide pour voir ces processus dans le code.

Il y a un seconde Signification de l'héritage en JavaScript – où vous créez un plan dérivé à partir du plan parent. Ce processus est appelé plus précisément Sous-classement, mais les gens appelleront parfois aussi cet héritage.

Comprendre le sous-classement

Le sous-classement consiste à créer un plan dérivé à partir d'un plan commun. Vous pouvez utiliser n'importe quelle saveur de programmation orientée objet pour créer la sous-classe.

Nous en parlerons d'abord avec la syntaxe de la classe, car elle est plus facile à comprendre.

Sous-classement avec classe

Lorsque vous créez une sous-classe, vous utilisez le extends mot-clé.

class Child extends Parent {
  // ... Stuff goes here
}

Par exemple, disons que nous voulons créer un Developer classe d'un Human classe.

// Human Class
class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

le Developer la classe s'étendra Human comme ça:

class Developer extends Human {
  constructor(firstName, lastName) {
    super(firstName, lastName)
  }

    // Add other methods
}

Remarque: super appelle le Human (également appelée la classe «parent»). Il lance le constructor de Human. Si vous n'avez pas besoin de code d'initiation supplémentaire, vous pouvez omettre constructor entièrement.

class Developer extends Human {
  // Add other methods
}

Disons un Developer peut coder. Nous pouvons ajouter le code méthode directement à Developer.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Voici un exemple d'instance de Developer:

const chris = new Developer('Chris', 'Coyier')
console.log(chris)
Instance d'une classe Developer.

Sous-classement avec les fonctions Factory

Il y a quatre étapes pour créer des sous-classes avec des fonctions d'usine:

  1. Créer une nouvelle fonction Factory
  2. Créer une instance du Blueprint Parent
  3. Créer une nouvelle copie de cette instance
  4. Ajouter des propriétés et des méthodes à cette nouvelle copie

Le processus ressemble à ceci:

function Subclass (...args) {
  const instance = ParentClass(...args)
  return Object.assign({}, instance, {
    // Properties and methods go here
  })
}

Nous utiliserons le même exemple: créer un Developer Sous-classe – pour illustrer ce processus. Ici se trouve le Human fonction d'usine:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

Nous pouvons créer Developer comme ça:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    // Properties and methods go here
  })
}

Ensuite, nous ajoutons le code méthode comme celle-ci:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}

Voici un exemple de Developer exemple :

const chris = Developer('Chris', 'Coyier')
console.log(chris)
Exemple d'instance Developer avec des fonctions Factory. "Src =" https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998202996_frc977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998202996_frc977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998202996_frc_factory.png " // yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

Remarque: Vous ne pouvez pas utiliser Object.assign si vous utilisez Getters et Setters. Vous aurez besoin d'un autre outil, comme mix. J'explique pourquoi dans cet article.

Écraser la méthode du parent

Parfois, vous devez écraser la méthode du parent dans la sous-classe. Vous pouvez le faire en:

  1. Créer une méthode avec le même nom
  2. Appel de la méthode du parent (facultatif)
  3. Changer tout ce dont vous avez besoin dans la méthode de la sous-classe

Le processus ressemble à ceci avec les classes:

class Developer extends Human {
  sayHello () {
    // Calls the parent method
    super.sayHello() 

    // Additional stuff to run
    console.log(`I'm a developer.`)
  }
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()
Ecrasement la méthode d'un parent « src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_674_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1"= "srcset données:. Image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class = "jetpack-lazy-image

Le processus ressemble à ceci avec les fonctions Factory:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)

  return Object.assign({}, human, {
      sayHello () {
        // Calls the parent method
        human.sayHello() 

        // Additional stuff to run
        console.log(`I'm a developer.`)
      }
  })
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()
Ecrasement la méthode d'un parent « src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_722_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1"= "srcset données:. Image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class = "jetpack-lazy-image

Héritage vs composition

Aucune discussion sur l'héritage ne se termine sans la mention de la composition. Des experts comme Eric Elliot suggère souvent que nous devrions privilégier la composition à l'héritage.

«Privilégiez la composition d'objets par rapport à l'héritage de classe» le Gang of Four, «Design Patterns: Elements of Réutilisable Object Oriented Software»

«En informatique, un type de données composite ou un type de données composé est tout type de données qui peut être construit dans un programme à l’aide des types de données primitifs du langage de programmation et d’autres types composites. (…) Le fait de construire un type composite est appelé composition. » ~ Wikipédia

Alors, examinons de plus près Composition et comprenons de quoi il s'agit.

Comprendre la composition

La composition est l'acte de combiner deux choses en une seule. Il s'agit de fusionner les choses. Le moyen le plus courant (et le plus simple) de fusionner des objets est d'utiliser Object.assign.

const one = { one: 'one' }
const two = { two: 'two' }
const combined = Object.assign({}, one, two)

L'utilisation de la composition peut être mieux expliquée par un exemple. Disons que nous avons déjà deux sous-classes, une Designer et Developer. Les concepteurs peuvent concevoir, tandis que les développeurs peuvent coder. Les concepteurs et les développeurs héritent de la Human classe.

Voici le code pour le moment:

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class Designer extends Human {
  design (thing) {
    console.log(`${this.firstName} designed ${thing}`)
  }
}

class Developer extends Designer {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Supposons maintenant que vous souhaitiez créer une troisième sous-classe. Cette sous-classe est un mélange d'un concepteur et d'un développeur qu'ils peuvent concevoir et coder. Appelons ça DesignerDeveloper (ou DeveloperDesigner, selon vos envies).

Comment créeriez-vous la troisième sous-classe?

Nous ne pouvons pas prolonger Designer et Developer cours en même temps. Ceci est impossible car nous ne pouvons pas décider quelles propriétés viennent en premier. Ceci est souvent appelé le problème du diamant.

. Problème de diamant » src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_389_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1" srcset = "data: image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class = "jetpack-lazy-image

Le problème du diamant peut être facilement résolu si nous faisons quelque chose comme Object.assign – où nous priorisons un objet par rapport à l'autre. Si nous utilisons le Object.assign approche, nous pourrons peut-être étendre des classes comme celle-ci. Mais cela n'est pas pris en charge dans JavaScript.

// Doesn't work
class DesignerDeveloper extends Developer, Designer {
  // ...
}

Nous devons donc nous fier à la composition.

La composition dit: au lieu d'essayer de créer DesignerDeveloper via le sous-classement, créons un nouvel objet qui stocke les fonctionnalités communes. Nous pouvons ensuite inclure ces fonctionnalités chaque fois que nécessaire.

En pratique, cela peut ressembler à ceci:

const skills = {
  code (thing) { /* ... */ },
  design (thing) { /* ... */ },
  sayHello () { /* ... */ }
}

On peut alors sauter Human au total et créez trois classes différentes en fonction de leurs compétences.

Voici le code pour DesignerDeveloper:

class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      code: skills.code,
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}

const chris = new DesignerDeveloper('Chris', 'Coyier')
console.log(chris)
Composition de méthodes dans une classe "src =" https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998387266_composition-classrc.png?ispending-pending-setAaaAf = "dataAlchargement_AA_Af_AA_Alcharg =" data-AAAloadAf_AA_Alcharg = "data-AAAloadAf_AA_Alchargement yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

Vous pouvez faire la même chose avec Developer et Designer.

class Designer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName 

    Object.assign(this, {
      design: skills.design,
      sayHello: skills.sayHello
    }) 
  }
}

class Developer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName 

    Object.assign(this, {
      code: skills.code,
      sayHello: skills.sayHello
    }) 
  }
}

Avez-vous remarqué que nous créons des méthodes directement sur l'instance? Ceci est juste une option. Nous pouvons toujours mettre des méthodes dans le prototype, mais je pense que le code semble maladroit. (C’est comme si nous écrivions à nouveau des fonctions de constructeur.)

class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design,
  sayHello: skills.sayHello
})
Composition via Classes en mettant des méthodes dans le Prototype. "Src =" https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_15979984074rc_composition-class-2.png = "data-load=" ; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

N'hésitez pas à utiliser la structure de code qui vous attire. Les résultats sont un peu les mêmes de toute façon.

Composition avec fonctions d'usine

La composition avec les fonctions Factory ajoute essentiellement les méthodes partagées dans l'objet retourné.

function DesignerDeveloper (firstName, lastName) {
  return {
    firstName,
    lastName,    
    code: skills.code,
    design: skills.design,
    sayHello: skills.sayHello
  }
}
Méthodes dans une composition de fonction d'usine "src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_169_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1" srcset =" data: image / gif; base64, R0lGODlhAQABAIAAAAAAAP // / yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

Héritage et composition en même temps

Personne ne dit que nous ne pouvons pas utiliser l'héritage et la composition en même temps. Nous pouvons!

En utilisant l'exemple que nous avons mis au point jusqu'à présent, Designer, Developer, et DesignerDeveloper Humans sont toujours des humains. Ils peuvent étendre le Human objet.

Voici un exemple où nous utilisons à la fois l'héritage et la composition avec la syntaxe de classe.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class DesignerDeveloper extends Human {}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design
})
Sous-classement et composition en même temps. "Src =" https://papier-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998445525_rcOD-class.png_AA_Alload = image_BQAPlass.png = AaAchargement /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

Et voici la même chose avec les fonctions Factory:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () { 
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

function DesignerDeveloper (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code: skills.code,
    design: skills.design
  }
}
Sous-classement et composition dans les fonctions Factory "src =" https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998461471_brcoth-factory.png1- dataAload = "AAA_Base_Aloader" / yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

Sous-classement dans le monde réel

Un dernier point sur le sous-classement par rapport à la composition. Même si les experts ont souligné que la composition est plus flexible (et donc plus utile), le sous-classement a toujours ses mérites. Beaucoup de choses que nous utilisons aujourd'hui sont construites avec la stratégie de sous-classification.

Par exemple: le click événement que nous connaissons et aimons est un MouseEvent. MouseEvent est une sous-classe d'un UIEvent, qui à son tour est une sous-classe de Event.

MouseEvent est une sous-classe de UIEvent. "Src =" http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_238_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png? yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

Autre exemple: les éléments HTML sont des sous-classes de nœuds. C’est pourquoi ils peuvent utiliser toutes les propriétés et méthodes de Nodes.

HTMLElement est une sous-classe de Node. "Src =" https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998504rcending/image.png? GIS1Achargement04307_image.png? yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

Verdict préliminaire

Les fonctions Classes et Factory peuvent toutes deux utiliser l'héritage et la composition. La composition semble être plus propre dans les fonctions d'usine, mais ce n'est pas une grande victoire sur les classes.

Nous examinerons les classes et les fonctions d'usine plus en détail ensuite.


Classes vs fonctions d'usine – Encapsulation

Jusqu'à présent, nous avons examiné les quatre différents types de programmation orientée objet. Deux d'entre eux – les classes et les fonctions d'usine – sont plus faciles à utiliser que les autres.

Mais les questions demeurent: que devriez-vous utiliser? Et pourquoi?

Pour poursuivre la discussion sur les classes et les fonctions d'usine, nous devons comprendre trois concepts étroitement liés à la programmation orientée objet:

  1. Héritage
  2. Encapsulation
  3. this

Nous venons de parler d'héritage. Parlons maintenant de l'encapsulation.

Encapsulation

L'encapsulation est un gros mot, mais il a un sens simple. L'encapsulation consiste à enfermer une chose dans une autre chose afin que la chose à l'intérieur ne s'échappe pas. Pensez à stocker de l'eau dans une bouteille. La bouteille empêche l'eau de s'échapper.

En JavaScript, nous souhaitons inclure des variables (qui peuvent inclure des fonctions) afin que ces variables ne fuient pas dans la portée externe. Cela signifie que vous devez comprendre la portée pour comprendre l'encapsulation. Nous allons passer par une explication, mais vous pouvez également utiliser cet article pour renforcer vos connaissances sur les portées.

Encapsulation simple

La forme la plus simple d'encapsulation est une portée de bloc.

{
  // Variables declared here won't leak out
}

Lorsque vous êtes dans le bloc, vous pouvez accéder aux variables qui sont déclarées en dehors du bloc.

const food = 'Hamburger'

{
  console.log(food)
}
Enregistre la nourriture de l'intérieur du blog. Résultat: Hamburger « src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_279_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1" srcset = "data:. Image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" classe = "jetpack-lazy-image

Mais lorsque vous êtes en dehors du bloc, vous ne pouvez pas accéder aux variables déclarées à l’intérieur du bloc.

{
  const food = 'Hamburger'
}

console.log(food)
Enregistre la nourriture de l'extérieur du blog. Résultats: Erreur « src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_258_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1" srcset = "data:. Image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" classe = "jetpack-lazy-image

Remarque: Variables déclarées avec var ne respectez pas la portée du bloc. C'est pourquoi je vous recommande d'utiliser let ou const pour déclarer des variables.

Encapsulation avec des fonctions

Les fonctions se comportent comme des étendues de bloc. Lorsque vous déclarez une variable dans une fonction, elle ne peut pas s'échapper de cette fonction. Cela fonctionne pour toutes les variables, même celles déclarées avec var.

function sayFood () {
  const food = 'Hamburger'
}

sayFood()
console.log(food)
Enregistre la nourriture de l'extérieur de la fonction. Résultats: Erreur « src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_313_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1" srcset = "data:. Image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" classe = "jetpack-lazy-image

De même, lorsque vous êtes à l'intérieur de la fonction, vous pouvez accéder aux variables qui sont déclarées en dehors de cette fonction.

const food = 'Hamburger'

function sayFood () {
  console.log(food)
}


sayFood()
Enregistre la nourriture depuis l'intérieur de la fonction. Résultat: Hamburger « src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_88_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1" srcset = "data:. Image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" classe = "jetpack-lazy-image

Les fonctions peuvent renvoyer une valeur. Cette valeur renvoyée peut être utilisée ultérieurement, en dehors de la fonction.

function sayFood () {
  return 'Hamburger'
}

console.log(sayFood())
Les journaux renvoient la valeur de la fonction. Résultat: Hamburger « src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_324_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1" srcset = "data:. Image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" classe = "jetpack-lazy-image

Fermetures

Les fermetures sont une forme avancée d'encapsulation. Ce sont simplement des fonctions encapsulées dans des fonctions.

// Here's a closure
function outsideFunction () {
  function insideFunction () { /* ...*/ }
}

Variables déclarées dans outsideFunction peut être utilisé dans insideFunction.

function outsideFunction () {
  const food = 'Hamburger'
  console.log('Called outside')

  return function insideFunction () {
    console.log('Called inside')
    console.log(food)
  }
}

// Calls `outsideFunction`, which returns `insideFunction`
// Stores `insideFunction` as variable `fn`
const fn = outsideFunction() 

// Calls `insideFunction`
fn()
journaux de fermeture « src = "http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_812_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png?is-pending-load=1" srcset = "données:. image / gif, classe base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"= "jetpack-lazy-image

Encapsulation et programmation orientée objet

Lorsque vous créez des objets, vous souhaitez rendre certaines propriétés accessibles au public (afin que les utilisateurs puissent les utiliser). Mais vous souhaitez également garder certaines propriétés privées (afin que d'autres ne puissent pas interrompre votre mise en œuvre).

Voyons cela avec un exemple pour clarifier les choses. Disons que nous avons un Car plan. Lorsque nous produisons de nouvelles voitures, nous remplissons chaque voiture de 50 litres de carburant.

class Car {
  constructor () {
    this.fuel = 50
  }
}

Ici, nous avons exposé le fuel propriété. Les utilisateurs peuvent utiliser fuel pour obtenir la quantité de carburant restant dans leurs voitures.

const car = new Car()
console.log(car.fuel) // 50

Les utilisateurs peuvent également utiliser le fuel propriété pour définir n'importe quelle quantité de carburant.

const car = new Car()
car.fuel = 3000
console.log(car.fuel) // 3000

Ajoutons une condition et disons que chaque voiture a une capacité maximale de 100 litres. Avec cette condition, nous ne voulons pas laisser les utilisateurs définir le fuel propriété librement car ils peuvent casser la voiture.

Il existe deux façons d'empêcher les utilisateurs de définir fuel:

  1. Privé par convention
  2. Vrais membres privés

Privé par convention

En JavaScript, la pratique consiste à ajouter des traits de soulignement à un nom de variable. Cela indique que la variable est privée et ne doit pas être utilisée.

class Car {
  constructor () {
    // Denotes that `_fuel` is private. Don't use it!
    this._fuel = 50
  }
}

Nous créons souvent des méthodes pour obtenir et définir ce paramètre «privé» _fuel variable.

class Car {
  constructor () { 
    // Denotes that `_fuel` is private. Don't use it!
    this._fuel = 50
  }

  getFuel () {
    return this._fuel
  }

  setFuel (value) {
    this._fuel = value
    // Caps fuel at 100 liters
    if (value > 100) this._fuel = 100
  }
}

Les utilisateurs doivent utiliser le getFuel et setFuel méthodes pour obtenir et régler le carburant.

const car = new Car() 
console.log(car.getFuel()) // 50 

car.setFuel(3000)
console.log(car.getFuel()) // 100 

Mais _fuel n'est pas réellement privé. C'est toujours une variable publique. Vous pouvez toujours y accéder, vous pouvez toujours l'utiliser et vous pouvez toujours en abuser (même si la partie abusive est un accident).

const car = new Car() 
console.log(car.getFuel()) // 50 

car._fuel = 3000
console.log(car.getFuel()) // 3000

Nous devons utiliser de vraies variables privées si nous voulons empêcher complètement les utilisateurs d'y accéder.

Vrais membres privés

Les membres ici font référence à des variables, des fonctions et des méthodes. C’est un terme collectif.

Membres privés avec classes

Les cours vous permettent de créer des membres privés en ajoutant # à la variable.

class Car {
  constructor () {
    this.#fuel = 50
  }
}

Malheureusement, vous ne pouvez pas utiliser # directement à l'intérieur d'un constructor une fonction.

Erreur lors de la déclaration <code>#</code> directement en fonction de constructeur "src = "données https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998874977_class-private.png?is-pending-load=1" srcset =":. image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image »/><noscript><img src = "https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998874977_class-private.png" alt = "Erreur lors de la déclaration <code>#</code> directement dans la fonction constructeur. »/></noscript></figure>
<p>Vous devez d'abord déclarer la variable privée en dehors du constructeur.</p>
<pre rel=class Car { // Declares private variable #fuel constructor () { // Use private variable this.#fuel = 50 } }

Dans ce cas, nous pouvons utiliser un raccourci et déclarer#fuel dès le départ depuis que nous avons mis le carburant 50.

class Car {
  #fuel = 50
}

Vous ne pouvez pas accéder #fuel à l'extérieur Car. Vous obtiendrez une erreur.

const car = new Car()
console.log(car.#fuel)
Impossible d’accéder à #fuel. "Src =" http://www.maclasseweb.fr/wp-content/uploads/2020/09/1601330916_375_Les-saveurs-de-la-programmation-orientee-objet-en-JavaScript.png1Achargement_AAA / RAB4is-pending-set_AA = "dataAloadAcAf_AA" // dataAloadAcAf_AA = "dataAload_AA =" dataAload_AA = ". yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image

Vous avez besoin de méthodes (comme getFuel ou setFuel) pour utiliser le #fuel variable.

class Car {
  #fuel = 50

  getFuel () {
    return this.#fuel
  }

  setFuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.getFuel()) // 50

car.setFuel(3000)
console.log(car.getFuel()) // 100

Remarque: Je préfère Getters et Setters au lieu de getFuel et setFuel. La syntaxe est plus facile à lire.

class Car {
  #fuel = 50

  get fuel () {
    return this.#fuel
  }

  set fuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100

Membres privés avec des fonctions d'usine

Les fonctions d'usine créent automatiquement des membres privés. Il vous suffit de déclarer une variable comme normal. Les utilisateurs ne pourront pas obtenir cette variable ailleurs. Cela est dû au fait que les variables ont une portée fonctionnelle et sont donc encapsulées par défaut.

function Car () {
  const fuel = 50 
}

const car = new Car() 
console.log(car.fuel) // undefined 
console.log(fuel) // Error: `fuel` is not defined

Nous pouvons créer des fonctions getter et setter pour utiliser ce private fuel variable.

function Car () {
  const fuel = 50 

  return {
    get fuel () { 
      return fuel 
    },

    set fuel (value) {
      fuel = value 
      if (value > 100) fuel = 100
    }
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100

C'est tout! Simple et facile!

Verdict pour l'encapsulation

L'encapsulation avec les fonctions Factory est plus simple et plus facile à comprendre. Ils s'appuient sur les portées qui constituent une grande partie du langage JavaScript.

L'encapsulation avec les classes, en revanche, nécessite l'ajout # à la variable privée. Cela peut rendre les choses maladroites.

Nous allons examiner le concept final – this pour compléter la comparaison entre les classes et les fonctions d'usine – dans la section suivante.


Classes vs fonctions d'usine – Le this variable

this (ha!) est l'un des principaux arguments contre l'utilisation des classes pour la programmation orientée objet. Pourquoi? Parce que this la valeur change selon la manière dont elle est utilisée. Cela peut être déroutant pour de nombreux développeurs (à la fois nouveaux et expérimentés).

Mais le concept de this est relativement simple en réalité. Il n'y a que six contextes dans lesquels vous pouvez utiliser this. Si vous maîtrisez ces six contextes, vous n'aurez aucun problème à utiliser this.

Les six contextes sont:

  1. Dans un contexte global
  2. Construction d'objets Inan
  3. Dans une propriété / méthode d'objet
  4. Dans une fonction simple
  5. Dans une fonction fléchée
  6. Dans un auditeur d'événement

J'ai couvert ces six contextes en détail. Lisez-le si vous avez besoin d'aide pour comprendre this.

Remarque: N'hésitez pas à apprendre à utiliser this. C'est un concept important que vous devez comprendre si vous souhaitez maîtriser JavaScript.

Revenez à cet article après avoir consolidé vos connaissances sur this. Nous aurons une discussion plus approfondie sur l'utilisation this dans les fonctions Classes et Factory.

Déjà de retour? Bien. Allons-y!

En utilisant this en cours

this fait référence à l'instance lorsqu'elle est utilisée dans une classe. (Il utilise le contexte «Dans une propriété / méthode d'objet».) C'est pourquoi vous pouvez définir des propriétés et des méthodes sur l'instance à l'intérieur du constructor une fonction.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    console.log(this)
  }
}

const chris = new Human('Chris', 'Coyier')
<code>this</code>  pointe vers l'instance "src =" https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998979647_class.png?EA07rcPending-load=1 "baseAAAAAAA_Lass.png? = "jetpack-lazy-image »/><noscript><img src = "https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998979647_class.png" alt = "<code>this</code> pointe vers l'instance »/></noscript></figure>
<h4><svg aria-hidden=En utilisant this dans les fonctions du constructeur

Si tu utilises this à l'intérieur d'une fonction et new pour créer une instance, this fera référence à l'instance. C'est ainsi qu'une fonction Constructor est créée.

function Human (firstName, lastName) {
  this.firstName = firstName 
  this.lastName = lastName
  console.log(this)  
}

const chris = new Human('Chris', 'Coyier')
<code>this</code>  pointe vers l'instance « src = "https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998999533_class.png?is-pending-load=1"= "srcset données. image / gif; base64, R0lGODlhAQABAIAAAAAAAP /// yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class = "jetpack-lazy-image »/><noscript><img src = "https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597998999533_class.png" alt = "<code>this</code> pointe vers l'instance. »/></noscript></figure>
<p>J'ai mentionné les fonctions du constructeur car vous pouvez utiliser <code>this</code> à l'intérieur des fonctions d'usine. Mais <code>this</code> pointe vers Window (ou <code>undefined</code> si vous utilisez des modules ES6 ou un bundler comme webpack).</p>
<pre rel=// NOT a Constructor function because we did not create instances with the `new` keyword function Human (firstName, lastName) { this.firstName = firstName this.lastName = lastName console.log(this) } const chris = Human('Chris', 'Coyier')
<code>this</code>  pointe vers Window. "src =" https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999027058_factory-1.pngEA0977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999027058_factory-1.pngEA097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999027058_factory-1.pngEA097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999027058_factory-1.pngEA097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999027058_factory-1. "class =" jetpack-lazy-image »/><noscript><img src = "https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999027058_factory-1.png" alt = "<code>this</code> pointe vers Window. »/></noscript></figure>
<p>Essentiellement, lorsque vous créez une fonction Factory, vous ne devez pas utiliser <code>this</code> comme s’il s’agissait d’une fonction de constructeur. C'est un petit hoquet que les gens éprouvent avec <code>this</code>. Je voulais mettre en évidence le problème et le clarifier.</p>
<h4><svg aria-hidden=En utilisant this dans une fonction Factory

La bonne façon d'utiliser this dans une fonction Factory est de l'utiliser «dans un contexte de propriété / méthode d'objet».

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayThis () {
      console.log(this)
    }
  }
}

const chris = Human('Chris', 'Coyier')
chris.sayThis()
<code>this</code>  pointe vers l'instance. "src =" https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999047100_factory-2.png?rcODis-pending-load=1 base de donnéesAAA =. yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 "class =" jetpack-lazy-image »/><noscript><img src = "https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999047100_factory-2.png" alt = "<code>this</code> pointe vers l'instance. »/></noscript></figure>
<p>Même si vous pouvez utiliser <code>this</code> dans les fonctions d'usine, vous n'avez pas besoin de les utiliser. Vous pouvez créer une variable qui pointe vers l'instance. Once you do this, you can use the variable instead of <code>this</code>. Here’s an example at work.</p>
<pre rel=function Human (firstName, lastName) { const human = { firstName, lastName, sayHello() { console.log(`Hi, I'm ${human.firstName}`) } } return human } const chris = Human('Chris', 'Coyier') chris.sayHello()

human.firstName is clearer than this.firstName parce que human definitely points back to the instance. You know when you see the code.

If you’re used to JavaScript, you may also notice there’s no need to even write human.firstName in the first place! Juste firstName is enough because firstName is in the lexical scope. (Read this article if you need help with scopes.)

function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()
Runs <code>chris.sayHello</code>" src="https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999097985_hello.png?is-pending-load=1" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class=" jetpack-lazy-image »/><noscript><img src="https://paper-attachments.dropbox.com/s_AB4BC977DF2BBAB3F9B097592E16C7567C493B52A668B2A322B5360BC04D43E9_1597999097985_hello.png" alt="Runs <code>chris.sayHello</code>« /></noscript></figure>
<p>What we covered so far is simple. It’s not easy to decide whether <code>this</code> is actually needed until we create a sufficiently complicated example. So let’s do that.</p>
<h3><svg aria-hidden=Detailed example

Here’s the setup. Let’s say we have a Human blueprint. Cette Human ha firstName et lastName properties, and a sayHello method.

We have a Developer blueprint that’s derived from Human. Developers can code, so they’ll have a code method. Developers also want to proclaim they’re developers, so we need to overwrite sayHello and add I'm a Developer to the console.

We’ll create this example with Classes and Factory functions. (We’ll make an example with this and an example without this for Factory functions).

The example with Classes

First, we have a Human blueprint. Cette Human has a firstName et lastName properties, as well as a sayHello method.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName 
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

We have a Developer blueprint that’s derived from Human. Developers can code, so they’ll have a code method.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Developers also want to proclaim that they’re developers. We need to overwrite sayHello and add I'm a Developer to the console. We do this by calling Human‘s sayHello method. We can do this using super.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }

  sayHello () {
    super.sayHello()
    console.log(`I'm a developer`)
  }
}

The example with Factory functions (with this)

Again, first, we have a Human blueprint. Cette Human a firstName et lastName properties, as well as a sayHello method.

function Human () {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

Next, we have a Developer blueprint that’s derived from Human. Developers can code, so they’ll have a code method.

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}

Developers also want to proclaim they’re developers. We need to overwrite sayHello and add I'm a Developer to the console.
We do this by calling Human‘s sayHello method. We can do this using the human instance.

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    },

    sayHello () {
      human.sayHello()
      console.log('I'm a developer')
    }
  })
}

The example with Factory functions (sans pour autant this)

Here’s the full code using Factory functions (with this):

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    },

    sayHello () {
      human.sayHello()
      console.log('I'm a developer')
    }
  })
}

Did you notice firstName is available within the lexical scope in both Human et Developer? This means we can omit this and use firstName directly in both blueprints.

function Human (firstName, lastName) {
  return {
    // ...
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

function Developer (firstName, lastName) {
  // ...
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${firstName} coded ${thing}`)
    },

    sayHello () { /* ... */ }
  })
}

See that? This means you can safely omit this from your code when you use Factory functions.

Verdict for this

In simple terms, Classes require this while Factory functions don’t. I prefer Factory functions here because:

  1. The context of this can change (which can be confusing)
  2. The code written with factory functions is shorter and cleaner (since we can use encapsulated variables without writing this.#variable).

Next up is the last section where we build a simple component together with both Classes and Factory functions. You get to see how they differ and how to use event listeners with each flavolr.

Classes vs Factory functions — Event listeners

Most Object-Oriented Programming articles show you examples without event listeners. Those examples can be easier to understand, but they don’t reflect the work we do as frontend developers. The work we do requires event listeners — for a simple reason — because we need to build things that rely on user input.

Since event listeners change the context of this, they can make Classes troublesome to deal with. At the same time, they make Factory functions more appealing.

But that’s not really the case.

The change in this doesn’t matter if you know how to handle this in both Classes and Factory functions. Few articles cover this topic so I thought it would be good to complete this article with a simple component using Object-Oriented Programming flavors.

Building a counter

We’re going to build a simple counter in this article. We’ll use everything you learned in this article — including private variables.

Let’s say the counter contains two things:

  1. The count itself
  2. A button to increase the count

Here’s the simplest possible HTML for the counter:

Count: 0

Building the Counter with Classes

To make things simple, we’ll ask users to find and pass the counter’s HTML into a Counter class.

class Counter () {
  constructor (counter) {
    // Do stuff 
  } 
}

// Usage 
const counter = new Counter(document.querySelector('.counter'))

We need to get two elements in the Counter class:

  1. le that contains the count – we need to update this element when the count increases
  2. le
Counter () {
  constructor (counter) {
    this.countElement = counter.querySelector('span')
    this.buttonElement = counter.querySelector('button')
  }
}

We’ll initialize a count variable and set it to what the countElement spectacles. We’ll use a private #count variable since the count shouldn’t be exposed elsewhere.

class Counter () {
  #count
  constructor (counter) {
    // ...

    this.#count = parseInt(countElement.textContent)
  } 
}

When a user clicks the

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *