JavaScript 18 Encapsulation Module Pattern IIFE Closure
#JavaScript, #LearnToCode, #CodingTips, #WebDevelopment, #Frontend, #Fullstack, #Programming, #CodeNewbie
Généré par IA·Peut contenir des erreurs
Résumé vidéo
Généré par IAThis video series explores data protection and encapsulation in JavaScript, tracing the evolution from manual patterns to native language features. It covers the mechanics of Lexical Environments and Closures, the risks of global scope pollution in script mode, and the implementation of the Module Pattern and IIFEs to create private data and public interfaces.
À ce stade de notre apprentissage du langage JavaScript,
la seule chose que nous n'avons pas encore abordée jusqu'ici
est la protection de la série aux données
et leur invisibilité depuis l'extérieur.
Dans les langages de programmation orienté-objet basés sur les classes,
la protection des données et l'invisibilité depuis l'extérieur
sont gérées par des règles de visibilité.
Par exemple, en Java, il suffit de déclarer une propriété
ou une méthode comme privée ou protégée,
c'est-à-dire private ou protected,
pour qu'elle ne soit pas accessible ou visible à l'extérieur
en dehors de l'unité dans laquelle elle a été définie.
Par défaut, en Java, les propriétés
et les méthodes non déclarées comme privées sont publiques.
On peut également indiquer explicitement
qu'une propriété est publique en utilisant la visibilité publique.
Le JavaScript n'étant pas nativement un langage de programmation orienté-objet
basé sur les classes, la notion de visibilité
n'existait pas jusqu'à son ajout récemment
dans la version IS2019 du langage.
Il est important de noter que l'utilisation
de cette nouvelle fonctionnalité de visibilité
introduite dans la version IS2019
n'est possible que lorsqu'on utilise une classe
comme unité d'encapsulation.
Cela signifie qu'il n'est pas possible
à la date où je réalise cette vidéo, c'est-à-dire en juillet 2023,
de bénéficier nativement de la visibilité privée
avec IS2019
en utilisant une fonction constructeur.
Avant l'ajout des classes dans la version IS2015
et l'ajout des règles de visibilité
dans la version IS2019,
la protection des données était réalisée
grâce à l'utilisation des closures.
Dans cette vidéo, nous verrons d'abord qu'il n'est pas possible
de protéger nativement les propriétés et les méthodes d'un objet.
Nous ferons ensuite un rappel sur les closures
puis nous verrons comment elles peuvent être utilisées
pour protéger les données.
L'un des fondements de la programmation orientée objet
est l'encapsulation
qui consiste en deux points.
Le premier point consiste à regrouper des données liées
et des fonctions permettant leur manipulation
sans d'une même unité.
Cette unité peut être un objet, une classe ou un module.
Le deuxième point est de rendre les données sensibles
ou considérées comme privées,
invisibles et inaccessibles depuis l'extérieur
et cela en protégeant leur accès
via une interface publique telle une boîte noire.
En ce qui concerne le regroupement des données
et des fonctions dans une unité,
nous savons déjà le faire en utilisant des objets.
Et nous verrons également plus tard comment réaliser ce regroupement
avec des classes ou en utilisant un module ECMAScript.
Si nous prenons le code suivant,
l'intention est de déclarer un objet user
qui possède une propriété privée name
dans la valeur initiale nu.
Étant donné que la propriété name est privée,
nous mettons à disposition des getters
et des setters pour pouvoir
contrôler l'accès à cette propriété.
Une chose à noter ici si vous ne le savez pas déjà,
c'est que vu que jusqu'à sa version
IS 2019, le Javascript ne fournissait
pas nativement la fonctionnalité de visibilité
pour déclarer des propriétés privées
contrairement aux autres langages de programmation
orientée objets tels que le Java et le PHP.
Par convention, on utilise le signe
underscore, c'est-à-dire le soulignement de bas
comme préfice pour marquer les propriétés
comme étant considérées comme privées.
Donc dans notre cas, vu que nous avons une propriété
privée qui est nommée name,
pour indiquer que cette propriété
est considérée comme privée,
on préfise cette propriété, le nom de cette propriété
du underscore comme nous l'avons ici.
Donc lorsque vous vous retrouvez avec un code source
à l'intérieur duquel vous retrouvez
underscore ou un nom qui est
préfisé de underscore, ça veut dire que
l'intention c'est de considérer cette
propriété comme étant privée.
Préfiser le nom d'une propriété ainsi permet
d'indiquer aux autres développeurs que même si
la propriété est visible de l'extérieur, elle doit
être considérée comme privée et ne doit pas
être modifiée directement. Cependant, l'utilisation
du trait de soulignement ne rend pas réellement
la propriété privée au sens strict,
comme c'est le cas dans les langages
comme le PHP ou le Java par exemple.
Comme le montre la ligne de code numéro 18 ici,
rien n'empêche le développeur de modifier
une propriété déclarée comme privée
en utilisant cette convention, c'est-à-dire
une propriété déclarée comme privée
en utilisant le trait de soulignement suivi
du nom de la propriété en question.
De plus, même si nous pouvons utiliser un getter
ou un setter pour contrôler l'asset ou la modification
d'une propriété depuis l'extérieur,
la propriété reste quand même visible de l'extérieur
comme nous pouvons le constater ici.
En résumé, le développeur doit être de bonne foi
pour ne pas modifier cette propriété
depuis l'extérieur, car rien ne l'empêche de le faire.
Donc si nous nous référons au code
de test que nous avons ici, nous affichons
dans un premier temps les détails de notre
objet ici et on voit bien que la propriété
même que nous avons déclarée
comme privée par convention est quand même
visible ici. Donc on peut accéder
directement à cette propriété malgré que nous avons
défini un getter ici
pour pouvoir contrôler
la récupération de sa valeur.
Donc rien n'empêche le développeur
aussi d'utiliser directement
la propriété et de la modifier
ou d'accéder directement à cette propriété
de cette façon ou de cette façon ici
ou accéder juste à la valeur
ici pour modifier sa valeur.
Donc ici on voit également que
après la modification de la valeur
si on raffiche
les détails de l'objet que nous avons
déclaré ici, on voit bien que l'objet est impacté
par le changement, c'est-à-dire que
la propriété même ici qui avait la valeur nulle
emmène la nouvelle valeur
que nous avons définie ici.
On s'y explique le name ici,
le underscore name ici à la valeur
WeCanEditName. Nous avons l'exemple
de tout à l'heure qui est repris ici en utilisant cette fois-ci
les properties descriptors. Puisque nous
parlons de la protection d'accès à des données,
nous pourrions envisager l'utilisation des properties
descriptors que nous avons étudiées en détail
dans une précédente vidéo. Ces derniers
permettent de définir des règles d'accès aux propriétés
d'un objet, notamment la suppression et la
modification d'une propriété. Cependant,
comme vous pouvez le constater avec le code que nous avons
ici, même avec l'utilisation des
properties descriptors pour définir les
getters et les setters, il est quand même
nécessaire de stocker la propriété
d'origine name ici, quelque part
avant de contrôler son accès en utilisant
des getters et des setters. Malheureusement, les
properties descriptors ne vous permettent pas de protéger
et de rendre cette propriété originale name
que nous souhaitons protéger invisible
depuis l'extérieur. Autrement dit, il est possible
de contourner le setter que nous avons mis en place
en modifiant directement la valeur de la propriété
d'origine name. Et nous avons ici
un code qui montre cette
modification de valeur directement
sans passer par le setter.
Ainsi, l'utilisation des properties descriptors ne nous
permettent pas également de résoudre le problème
de visibilité que nous avons.
Avant de continuer, je souhaite parler de la notion de
Lessical Environments et faire un petit rappel
sur les closures, car leur compréhension nous sera
utile pour la suite. Lorsqu'on exécute une fonction
ou un bloc de code en JavaScript, il y a
un objet interne appelé Lessical Environments
qui lui est rattaché. Lessical Environments
qui se traduit en français par environnement lessical
ou champ lessical. Le Lessical Environments
est constitué de deux parties. Nous avons
le Environment Recode qui est
un objet qui stocke toutes les variables et fonctions
déclarées explicitement à l'intérieur
de la fonction ou du bloc en cours d'exécution.
Nous avons également une référence
vers le Lessical Environments
externe, appelé Author
Lessical Environments. Si nous nous référons
donc au code que nous avons ici et que nous
prenons la fonction interne inner que
nous avons ici, le Lessical Environments
de cette fonction interne inner est
constitué de son Environment Recode
qui contient la variable Y
ici, vu que c'est la variable qui a
été déclarée à l'intérieur de cette fonction.
Donc son Environment Recode
est constitué d'une seule variable qui est
le Y ici. Cette fonction
garde également une référence
vers son Author Lessical
Environments qui est dans ce cas
le Lessical Environments de
cette fonction Author, la fonction externe ici.
De son côté, lorsque nous prenons
la fonction Author ici,
son Lessical Environments est
constitué d'un Environment Recode
d'abord, qui est constitué
de la variable X que
nous avons déclarée ici et
de la fonction inner. Donc dans le
Environment Recode de Author, on
retrouve la déclaration de la
variable X que nous avons ici et de
la fonction inner que nous avons là.
Concernant la référence vers son
Author Lessical Environments,
vu que cette fonction n'est pas imbriquée
dans une autre fonction ou qu'elle
n'est pas déclarée dans une autre fonction,
mais directement dans l'environnement
global, donc cela veut dire que
la référence vers son Author
Lessical Environments va pointer
sur le Global Lessical Environments.
Ce qu'il faut savoir ici, c'est qu'un nouvel
Lessical Environments est créé à chaque fois que
l'exécution d'une fonction commence. Et ceci
permet de sauvegarder les variables locales
et les paramètres de la fonction. Pendant l'exécution
d'une fonction, elle dispose de deux environnements lessicaux.
Celui qui contient les variables locales
et les paramètres de la fonction, qu'on appelle
Inner Lessical Environments
ou Environnement Lessical Interne.
Et nous avons le Author Lessical Environments
ou l'Environnement Lessical Externe
qui est celui qui entoure la fonction. Lors de l'exécution
d'un bloc de code ou d'une fonction,
lorsqu'on essaie d'accéder à une variable,
celle-ci est d'abord recherchée au niveau du
Inner Lessical Environments, c'est-à-dire
l'Environnement Lessical Local. Si la variable n'est pas
trouvée au niveau de l'Environnement Lessical Local,
elle est ensuite recherchée au niveau du
Author Lessical Environments, c'est-à-dire
au niveau de l'Environnement Lessical Externe qui l'entoure.
Si la variable n'est toujours pas retrouvée
au niveau du Lessical Environments Externe
de la fonction ou du bloc en cours
d'exécution, elle est alors recherchée au niveau
du Lessical Environments Externe
ou Lessical Environments Externe courant
et ainsi de suite jusqu'à ce que la variable soit retrouvée.
Et si la variable n'est retrouvée à aucun
niveau, jusqu'à atteindre le global
Lessical Environments sans que
la variable ne soit toujours retrouvée,
une erreur d'accès et une variable inexistante
sera donc générée. Donc si nous prenons l'exemple
de notre fonction interne inner que nous avons
ici, si nous essayons d'accéder à
une variable qui est par exemple supposant
que nous avons, nous essayons d'accéder
ici à une variable z par exemple.
Donc cette variable sera d'abord recherchée ici
dans le Lessical Environments Local
vu qu'il n'y a aucune variable
qui est nommée ou déclarée
comme z dans ce
dans cet environnement Lessical Local.
La variable sera ensuite
recherchée dans le Author
Lessical Environments
Outer de inner.
Donc cela va représenter, dans ce
cas, le Lessical
Environments Local de Outer
dans ce cas. Donc la variable z sera
recherchée ici.
Et si cette variable n'est toujours pas
retrouvée ici, elle sera recherchée
finalement au niveau du
Lessical Environments
externe de la fonction Outer ici.
Et dans ce cas on tombe dans le global
Lessical Environments. Et vu qu'il n'y a aucune
variable z qui est toujours déclarée
ici, donc on aura dans ce cas une erreur
de variable z inexistante.
Ce qu'il faut retenir ici, c'est qu'en javascript,
chaque fonction conserve une référence
vers son Lessical Environments dans lequel
il a été créé, c'est-à-dire son Lessical
Environments Externe. Et ceci nous
amène à la notion de closure.
En javascript, on appelle closure, qui se traduit
en français par fermeture, une fonction
interne à une autre fonction qui conserve
l'asset ou la référence à toutes les variables
et fonctions qui existent dans l'environnement
où elle a été créée. Une closure est un mécanisme
qui permet à une fonction d'accéder et de
maintenir l'asset au scope qui l'entoure, ou dans lequel
elle a été créée, même si cette fonction
est appelée plus tard en dehors de ce scope.
Dans la pratique, nous obtenons un closure à
chaque fois que nous définissons une fonction
à l'intérieur d'une autre fonction. Donc si nous prenons
comme exemple le code que nous avons ici,
nous déclarons une fonction innant
à l'intérieur d'une fonction outer.
Donc en possédant ainsi,
on crée une closure
ici. Donc le
innant ici devient
une closure ou une
fermeture de la
fonction outer que nous avons ici.
C'est-à-dire qu'elle conserve une
référence vers toutes les variables
ou toutes les fonctions qui seront
déclarées à l'intérieur de la fonction outer
ici. Donc ainsi, innant
devient closure de outer.
C'est-à-dire qu'elle garde
une référence vers les variables
et les fonctions qui seront déclarées
dans l'estical environment
de la
fonction outer que nous avons là.
Donc cela veut dire que plus tard, lorsque
nous essayons
d'accéder à la variable x ici,
même après l'exécution de cette fonction,
vu que
l'estical environment
du closure garde
une référence vers cet environnement,
donc elle pourra toujours accéder à cette variable
même après
l'exécution ou la fin d'exécution
de la fonction qui a permis sa
création. Donc si nous prenons le code
de test que nous avons ici,
en exécutant la fonction outer, cela va
nous retourner une nouvelle fonction,
une nouvelle instance de la fonction
innant qui est ici.
Donc plus tard, lorsque nous allons faire appel
à cette nouvelle instance de innant,
ce code va s'exécuter.
Et vu que la variable
x ici a été
déclarée dans le
estical environment de la fonction externe,
la fonction innant
continue par accéder
à cette variable même après
que la fonction qui a permis
sa création ait terminé son exécution.
Donc cette fonctionnalité
c'est le closure qui nous permet d'avoir
cela. Donc on voit ici
que la variable qui est affichée
lors de l'exécution
de cette partie,
c'est la valeur 2.
Donc même si nous déclarons, supposons maintenant que
même si ici,
nous déclarons une variable ici,
une nouvelle variable
x et que je lui donne
la valeur 10,
on voit que c'est pas
la valeur 10 qui est affichée ici lors de
l'exécution du closure, mais c'est
plutôt la variable ici qui est
affichée. Parce que
le x qui est ici garde une
référence vers le
la fonction ici garde une référence
vers l'estical environment
externe. Ce qui fait que
cette fonction continue par accéder
à cette variable x qui est ici. Donc on voit
que c'est la variable qui est définie
lors de la création de cette
fonction qui est affichée là.
Donc si
j'efface ce code, nous avons ici
un second exemple qui utilise
également des closures.
Donc ici c'est pareil,
on définit une fonction innant
à l'intérieur d'une fonction out.
Donc cette fonction ici devient
un closure qui capture
ou qui réalise une fermeture
de l'estical environment
de out à ici
qui représente l'out à l'estical
environment de innant dans ce cas. Donc pareil
ici, lorsqu'on fait appel à la
fonction, ici le but c'est de montrer
que depuis le
closure que nous avons ici, nous
pouvons accéder également aux paramètres
qui sont passés à la fonction
externe lors de son exécution.
Donc dans un premier temps ici on
fait appel à la fonction out en lui
passant la valeur 12. Donc en faisant
appel à cette fonction,
la valeur paramètre
ici ou la variable paramètre
sera présente
dans l'estical environment
de la fonction
externe out ici. Donc plus tard,
lorsqu'on procède à l'exécution de ce code,
on a toujours
assez à cette valeur
malgré qu'on est en dehors
de ce
scope ou
que l'exécution de cette fonction
s'est déjà terminée. Donc si
je modifie cette valeur et que je mets par exemple
13, on voit que la valeur
qui est affichée ici c'est 13 parce que c'est
la valeur qui est passée comme paramètre
à la fonction
externe lors de son appel.
Donc ça, ce
mécanisme, cette fonctionnalité, c'est le
closure ou la fonctionnalité
de closure qui nous permet d'avoir
ça. Avec toute la notion que nous avons
vu, nous pouvons maintenant voir comment utiliser
les closures pour rendre une donnée invisible
et accessible depuis l'extérieur. Avant l'introduction
des modules en Javascript, le code Javascript
ne pouvait s'exécuter qu'en mode script,
ce qui signifie que pour un fichier Javascript donné,
après son chargement par le navigateur,
son contenu était interprété du début à la fin,
c'est-à-dire de la première ligne à la dernière ligne.
Pour le développement d'une application ou d'un site,
il était courant pour une page Web
de charger plusieurs fichiers Javascript en fonction
des besoins. Par exemple, pour un site Web donné,
nous pouvions charger le fichier représentant la bibliothèque
d'iconnerie pour manipuler le dos,
ainsi que les fichiers Javascript de l'applicauté
Bootstrap pour obtenir certains effets
visuels. Étant donné que l'exécution se faisait
en mode script, si nous avions par exemple
un script 1 représenté
par un fichier script1.js
et que ce script utilisait des dépendances
telles que des fonctions provenant d'une
bibliothèque représentée par un fichier
lib.js,
il fallait s'assurer de charger le fichier
Javascript représentant la bibliothèque,
c'est-à-dire le lib.js,
avant de charger le fichier
script1.js, vu que ce script
utilise des dépendances provenant
de lib.js. L'un des impacts
du mode exécution en mode script, c'est que
si j'ai deux scripts, par exemple
script1 et script2, comme nous l'avons
ici, et que dans le script1
représenté par le fichier
script1.js, je déclare
une fonction f1 dans le scope global
de ce script,
lors du traitement de notre page par le navigateur,
si nous chargeons le fichier script1.js
avant le script
script2.js,
cela signifie que la fonction f1
sera visible et accessible
également depuis le script
script2.js. La raison, c'est que
lors de l'exécution du script1,
la fonction f1 est ajoutée au scope global,
ce qui le rend accessible à tous les codes
qu'ils exécutent après, et qui ont également
accès au scope global. Et comme le script2
est chargé et exécuté
après le script1, il voit et accède
à toutes les variables ou fonctions
présentes dans le scope global avant son chargement.
Et comme la fonction f1 est déjà
présente dans le scope global, elle est accessible
par le code du script2 également.
En résumé, lorsque deux scripts sont chargés l'un après l'autre,
et que ces deux scripts s'exécutent tous
en mode script, si le premier ajoute
des variables ou des fonctions à l'environnement global,
le second pourra s'aider également à ces
variables et fonctions. Pour démontrer ce que je
viens de dire à propos de l'exécution du code
en mode script, j'ai créé ici un
petit projet, ou un petit
pop, juste pour démontrer cela.
Donc ici, nous avons une page HTML
auquel nous lions deux fichiers
JavaScript. Donc il s'agit de
deux scripts. Donc nous avons
un premier script qui est script1
ici, qui contient la
déclaration d'une fonction dans
le scope global et la déclaration
d'une variable également dans le scope
global. Donc si nous revenons au
fichier représentant
notre page HTML,
nous avons également un second script ici
qui possède
l'affichage de variables
et l'utilisation
de fonctions
provenant du script1
ici. Donc cela veut dire
que le script2
utilise le script1 comme dépendance.
Donc on voit qu'ici nous chargeons
le script1 avant
le script2. Donc nous avons vu tout à
l'heure que lorsque nous possédons
ici les variables qui seront
déclarées dans le script1, vu que ces deux
fichiers s'exécutent en mode
script, cela veut dire que si je déclare
à partir de ce fichier
des variables ou des fonctions
dans l'environnement global,
cela veut dire que ces fichiers
ou ces variables ou fonctions
seront également visibles pour tous
les scripts qui viendront se charger
après
ce script.
Donc toutes les variables déclarées dans ce
script ou toutes les fonctions déclarées dans ce script
seront visibles par tous les codes qui vont
s'exécuter après l'exécution de ce
script. Donc pour prouver cela,
j'ai déclaré ici
une variable d'abord ici
et puis une fonction dans le scope global.
Donc ensuite dans le second script
qui est chargé après le
premier, je
procède à l'affichage de
cette variable qui est
déclarée dans le premier script et l'utilisation
de cette fonction qui est également
déclarée dans le premier script.
Donc si tout se passe bien, c'est-à-dire si
le mode, ce que j'ai dit tout à l'heure
à propos du mode d'exécution en script
est vrai, cela veut dire que le contenu de
ce script2
va s'exécuter sans
qu'on ait une erreur et cela va nous afficher
les valeurs
d'abord
1.0.0 suivie de
test dans notre console.
Donc si on regarde un peu la structure
ici, donc j'ai juste un fichier
html qui représente ma page auquel
j'alliais deux scripts, le script1 qui est
chargé en premier qui contient
la déclaration des variables et le script2
qui est à utiliser
après pour afficher ces variables et
les fonctions qui ont été déclarées dans le premier
script ici.
Donc ici j'ai mon navigateur qui contient
cette page, donc si je procède à l'actualisation
de la page, donc je vois que
j'ai bien le
1.0.0
qui est affiché suivi de test. Donc cela veut dire
que le contenu de ce
fichier s'exécute
et on voit que les variables
qui ont été déclarées dans le script1
vu que ces variables sont déclarées
dans le scope global
vu que le script
qui est chargé après ce
premier script a
également accès à cet environnement global
c'est que nous accédons clairement
aux variables qui ont été
ajoutées dans ce
scope par le premier script.
Donc si c'est que ces variables n'étaient pas
ajoutées par
ce script, cela veut dire qu'on aurait une erreur
ici. Donc pour démontrer cela
il suffit que je change par exemple
le nom, que je fasse
ceci. Par exemple
que j'enregistre et que j'actualise
de nouveau la page. Donc si je viens ici que
j'actualise la page, on voit maintenant que j'ai une erreur
qui me dit que cette variable n'existe
pas parce que cela n'a pas été déclaré nulle part.
Donc cela prouve bien que
les données qui seront,
les variables qui seront ajoutées par le premier script
dans le scope global seront vues
par tous les scripts qui seront chargés
sur la même page après
si ces deux scripts sont
exécutés par le navigateur en mode
script, c'est-à-dire leur contenu est lu
de haut en bas. Ce fonctionnement
présente différents problèmes dont notamment la collision
de noms. Cela signifie que si j'ai deux scripts
et que le premier script déclare des variables
ou des fonctions qui sont également redéclarées
dans un second script en utilisant les mêmes noms,
cela entraînerait des conflits ou des collisions
de noms, ce qui peut poser des bugs tels
que le remplacement des fonctions ou des variables
déjà déclarées dans un script par
d'autres. C'est l'une des raisons pour lesquelles il est
considéré comme une mauvaise pratique de polluer
le scope global avec des variables ou
des fonctions qui ne doivent pas être accessibles.
Pour démontrer cela,
il me suffit par exemple de me rendre
dans le fichier ici,
supposant que je refasse ceci.
Je copie cela,
que je passe dans le second fichier
et que je remplace la valeur ici,
que je clé une nouvelle variable
et que je lui donne
la valeur 2.0.0.
À cause du
fait que j'ai redéclaré cette
variable ici, à l'intérieur du
script 2, ça veut dire que les scripts qui vont se
charger ici, après le script 2,
vont voir seulement
la variable que j'ai déclarée ici.
Donc la valeur, vu que c'est la
dernière variable qui a été déclarée,
que cette variable à la valeur
2.0.0, elle va
écraser la variable qui avait
été déclarée ici.
Cette variable sera écrasée,
c'est-à-dire qu'on a une nouvelle déclaration
de variable et c'est la dernière qui sera prise en compte
dans ce cas. L'impact de cela, c'est que
lorsque nous allons exécuter maintenant ce code,
nous verrons
2.0.0 qui sera affiché,
vu que c'est la dernière
valeur de la variable
déclarée dans le script 2. L'impact de cela,
c'est que si nous avons par exemple une application
dans laquelle nous avons deux librairies qui utilisent
les mêmes noms de variables ou les mêmes noms de
fonctions, c'est la dernière librairie à être
chargée qui prendra le dessus. Face à ce
problème, il était nécessaire de trouver une solution
pour éviter la pollution du scope global
ou de réduire cette pollution en y ajoutant
uniquement ce qui mérite d'être public,
c'est-à-dire accessible de partout. Ainsi, la communauté
a dû trouver une solution de contrôlement,
vu que le JavaScript ne fournissait pas nativement les modules.
C'est ainsi que le module Pattern, encore
appelé Revealing Module Pattern, a vu
le jour en JavaScript. Nous avons ici un exemple
de code dont l'objectif est de montrer comment
mettre en place le module Pattern. Le module
Pattern en JavaScript se résume à exécuter
une fonction anonyme comme nous l'avons ici
et à retourner une valeur correspondant
à un littéral objet.
Donc, l'exécution de cette
fonction anonyme que nous avons ici
retourne un objet
ou un littéral objet. Cet objet littéral
sera visible dans le scope global
et exposera les fonctionnalités qui sont
considérées comme publiques à travers
ces propriétés. Donc, l'imprémentation
du module Pattern se présente
comme ceci. Donc, si nous
revenons au code que nous avons ici,
dans cet exemple, nous avons
une fonction anonyme qui est exécutée
directement. Donc, ici,
j'ai réduit cette partie.
Donc, en réalité, ici,
nous avons une fonction anonyme qui est déclarée là.
Donc, cette fonction
anonyme est exécutée directement
en utilisant
cette façon de faire. Et le fait d'exécuter
une fonction anonyme de cette façon,
c'est-à-dire faire directement,
appel à une fonction anonyme,
juste après sa déclaration,
cela est appelé le if,
qui se traduit en anglais
par immediately invoke function
expression. Donc, ça s'écrit comme ça.
Donc, il s'agit
également d'un pattern en javascript.
Donc, if for immediately
invoke function expression.
L'utilisation du immediately invoke
function expression permet de préserver
l'espace global. Les variables et les
fonctions déclarées à l'intérieur de l'ife
ne polluent pas l'espace global, car le contexte
d'exécution de la fonction est isolé de l'environnement
extérieur qui l'entoure, ce qui
permet de maintenir un environnement extérieur
propre et de réduire les conflits de nom.
Donc, si nous prenons la fonction anonyme que nous avons
ici, nous savons que le
contexte d'exécution
d'une fonction n'est pas
visible de l'extérieur. Donc, cela veut dire que
lorsque cette fonction va s'exécuter,
toutes les variables qui ont été déclarées
à l'intérieur de cette
fonction par défaut ne seront pas
visibles à l'extérieur après l'exécution
de cette fonction. Donc, cela évite que
ces différentes variables ne polluent
le scope global, et ainsi
de réduire les conflits de nom que nous avons
vu précédemment. De plus, l'utilisation de l'ife
peut permettre d'éviter des modifications
depuis l'extérieur des variables ou des fonctions
se trouvant dans le scope local de la fonction
anonyme, car ces fonctions ou ces
variables ne sont pas accessibles depuis l'extérieur.
Cela protège donc le code contre
les modifications indésirables provenant de
l'extérieur et des erreurs causées par
d'autres parties du code. En résumé,
par défaut, toutes les variables ou fonctions déclarées
à l'intérieur de l'ife ici ne sont
pas visibles de l'extérieur. Il faut donc exposer
explicitement les fonctions ou les variables
que nous souhaitons rendre publiques en retournant
un objet, comme nous l'avons ici,
qui regroupe tout ce que nous voulons rendre
public. Si nous analysons un peu le code
que nous avons ici en se basant sur les explications
précédentes, les variables quant à
private function, increment,
decrement et getValue
seront par défaut inaccessibles ou invisibles
depuis l'extérieur. Et pour démontrer cela,
il me suffit de mettre en commentaire
le code d'exposition vers l'extérieur
que nous avons ici.
Ainsi, je mets en commentaire ce code
et que je mets en commentaire également
le code de test.
Lorsque j'essaie d'accéder à cette
variable ici, après,
on voit que cette variable
n'existe pas dans le scope global. Pareil,
lorsque je
essaie d'appeler la fonction decrement,
par exemple, depuis l'extérieur,
on me dit que
cette fonction également n'est pas définie.
La raison, c'est simple, c'est parce que
le scope de cette fonction
est isolé de l'extérieur.
C'est le fonctionnement normal
que nous avons vu et c'est le fonctionnement
normal dans javascript lorsqu'on exécute une fonction.
Donc, le contexte n'est pas exposé
à l'extérieur. Mais si nous n'avons pas
mis en place ce ife, cela implique
que ces différentes fonctions sont variables
et pollueront naturellement l'environnement
global. Donc, pour démontrer cela,
il me suffit de mettre en commentaire
la mise en place du ife ici.
Donc, si je reprends
ici, je mets en commentaire cette partie.
Je mets en commentaire
cette partie
et je mets en commentaire
également cette partie. Parce que c'est ce qui
me permet de définir mon ife.
Donc, en procédant
ainsi, si j'essaie maintenant
d'accéder à la variable
quanta ici maintenant,
on voit que j'ai la valeur 0.
Pareil, lorsque j'essaie d'accéder
à la
fonction increment également,
j'ai l'exécution qui
se passe bien. Et pour démontrer cela, il me suffit
de faire un getValue après pour voir
que la valeur a été
incrementée. Donc là,
je dois faire un console dessus si je veux
vraiment voir
le résultat.
Je dois faire un console dessus.
Donc, je vois que j'ai la valeur 1.
Donc, si je fais appel
2 fois increment, je m'attends
à avoir 2 ici comme valeur.
Donc, en exécutant le code
de cette façon, sans le protéger
par un ife,
cela implique que nous aurons une pollution
de l'environnement global.
Et c'est pour éviter cette pollution de l'environnement
global qu'on met en place le ife.
Donc, le code qu'il y a ici,
lorsque je mets en place
mon ife, c'est-à-dire
l'appel de la fonction anonyme,
on s'y fait ceci.
Donc, on voit que
après la mise en place du ife,
ces variables sont automatiquement invisibles
depuis l'extérieur.
Donc, je peux supprimer ce code de test.
Si nous reprenons maintenant
le code avec la mise en place
du ife qui permet de cacher
les variables et les fonctions
que nous avons déclarées dans la fonction anonyme
ici, pour rendre certains de ces variables
ou de ces fonctions accessibles
ou visibles à l'extérieur, il nous faut
au moins de les exposer explicitement.
Et c'est là qu'entre en jeu
l'objet littéral que nous
retournons ici.
Donc, il nous permet d'exposer vers l'extérieur
les variables
ou les fonctions que nous considérons comme
publiques. Donc, il me suffit de décommenter
ce code qui
me permet d'exposer
les fonctions ou les variables vers l'extérieur.
Donc ainsi, si nous
procédons maintenant à l'exécution de ce code,
l'ife fait
que, après l'exécution de ce code,
la valeur qui sera retournée
sera l'objet littéral que nous avons
ici. Et cet objet littéral
va porter maintenant les propriétés qui vont
mapper
sur les fonctions
ou les variables que nous
souhaitons rendre publiques.
Donc, l'implication de cela, c'est que
après l'exécution de cette
ligne de code, c'est-à-dire la mise en place
de l'ife, l'objet littéral
qui sera retourné par l'ife
sera affecté
dans la variable
moduleName. Donc, pour utiliser
plus tard ces fonctions ou ces
variables qui sont exposées
de façon explicite comme étant
publiques, il faut passer par
cette variable pour pouvoir les utiliser.
Donc, nous utiliserons le nom de cette
variable dans laquelle le résultat de
l'ife est affecté, comme une sorte d'espace
de nom, de namespace par exemple.
Donc ici, c'est ce qui
explique le fait que
après la mise en place de l'ife, on passe
par ce nom de variable qui représente
maintenant le littéral d'objet
qui expose les propriétés
ou les fonctions publiques à travers
ces propriétés. Donc, c'est ce qui explique le fait
qu'on utilise maintenant pour
appeler la fonction getName
que nous avons ici.
La fonction getName
que nous avons, getValue
plutôt, que nous avons ici.
On passe par le nom
de notre objet littéral
qui fait office ici de namespace
ou d'espace de nom.
Donc, la remarque aussi à faire ici, c'est que
nous pouvons changer
la façon dont nous exposons
ces méthodes. Donc, on peut changer
l'interface publique, c'est ici que nous
définissons, en quelque sorte, l'interface publique
de notre module que nous sommes en train de créer.
Donc ici, on voit que
malgré que les fonctions
incrémentes et décrémentes ont été
déclarées, en utilisant
des noms plus courts ici,
on les expose publiquement
en utilisant incrémentes et décrémentes.
Raison pour laquelle, pour pouvoir
appeler ces fonctions, on utilise les
noms qui ont été exposés
publiquement, c'est-à-dire les noms de propriétés que nous avons ici.
Donc, si nous regardons
et nous nous intéressons maintenant aux
codes de test, dans un premier temps,
après la
mise en place de notre
module, la première chose
que nous faisons, nous récupérons la valeur
de value.
Donc ici, il s'agit de la valeur
de la propriété ou de la varia
privée ici. Donc, sa valeur
initialement est égale à 0, c'est ce qui explique
le 0 que nous avons ici.
Ensuite, nous faisons appel à la fonction incrementes
dans le but d'incrémenter
la valeur de quanta ici
de 1. Donc ici,
après l'exécution de cette
code, la valeur de quanta
qui est ici, sera incrémentée de 1.
Donc, on aura la valeur 1 et après,
lorsqu'on fait de nouveau appel à base value,
donc on retourne la valeur 1.
Ensuite, on fait appel au déclément
dont l'objectif est de déclémenter
cette valeur actuelle de 1.
Ce qui ramène après la valeur
à 0 lorsqu'on l'affiche ici.
Donc, c'est de cette
façon qu'on peut utiliser le module
pattern pour protéger des données
et exposer seulement les données
laissées publiques ou rendre invisible depuis l'extérieur.
Donc, pour résumer, une dernière fois
le pattern, il s'agit de créer
une fonction anonyme en utilisant
le pattern
de if, le immediately
invoke function expression.
Donc, on fait appel à une fonction
on crée une fonction anonyme
qu'on exécute directement
et on retourne une valeur
qui sera vue de l'extérieur.
Donc ici, on aurait pu
retourner, par exemple
une primitive
si nous le voulons, mais
l'idée ici, c'est de pouvoir regrouper
beaucoup de
fonctions ou plusieurs fonctions
comme si on faisait une encapsulation dans un module
dans raison pour laquelle
on utilise un littéral d'objet ici
et c'est ce littéral d'objet qui sera
affecté comme valeur de notre
module ou de notre variable ici.
Donc après, pour pouvoir utiliser
les méthodes,
on utilise les méthodes
en utilisant le nom
de cette variable comme si on faisait appel
à un namespace.
Donc, c'est cette variable maintenant qui sera
présente dans l'espace
global ou qui sera vue de l'extérieur
avec les méthodes qu'elle expose.
Nous avons ici
un mini-poc dont l'objectif est de montrer
comment le module pattern est à utiliser
pour mettre en place des bibliothèques, pour éviter
de polluer l'environnement global
avant l'introduction des modules
de façon native dans le javascript dans la
version 6 du langage.
Donc le poc ici, il s'agit d'une page
HTML représentée par le fichier
indes.html ici, auquel nous
lions deux fichiers javascript.
Donc le premier fichier ici
est considéré comme une
librairie qui expose certaines fonctionnalités
qui pourront être utilisées
dans le fichier indes.js ici.
Donc nous chargeons
dans un premier temps le fichier
libre représentant notre
pseudo-librairie.
Et nous avons ici le fichier
où le script utilise cette dépendance.
Donc nous allons voir comment le
module pattern permettait de mettre en place
les librairies avant l'introduction
des modules de façon native dans le
langage. Donc si on se
réfère au contenu de ce fichier,
ici nous avons
une mini-librairie
qui utilise
le module pattern
que nous avons vu actuellement
pour exposer une fonction
qui peut être utilisée
pour extraire
des voyelles d'un test. Donc ici
on ne va pas trop se focaliser sur le
contenu, mais l'idée c'est que cette fonction
prend en paramètre un test
et extrait les voyelles qui sont
contenues dans ce test. Donc si je lui donne
par exemple le test
comme ça, si je lui donne le test
le comme ça, donc du coup
le résultat final sera
elle comme ça. Vu que ce code va
remplacer tout ce qui est
par de l'espace vide.
Ou du test vide plutôt.
Une scène de caractère vide.
Du coup ici nous utilisons
le module pattern, c'est-à-dire
qu'on procède ici à la création
d'une fonction anonyme qui est
exécutée immédiatement, c'est-à-dire
un immediately evoked
function expression.
Donc on procède à l'exposition
seulement de
certaines fonctionnalités que nous souhaitons
exposer ou rendre publiques.
Donc cela veut dire
que depuis l'extérieur, cette fonction
ne va pas polluer, ou tout ce qui
va se retrouver à l'intérieur
du corps de cette fonction
anonyme ici, ne va pas polluer
l'environnement global
même si on exécute
ce fichier ou ce script
en mode script. Donc
l'IFE nous permet
d'éviter cela.
Donc on expose ensuite cette fonction
OVL only en utilisant
le module pattern ici que nous avons ici.
Si on analyse un peu les choses,
c'est que le script représentant la librairie
sera chargé premièrement par le navigateur.
Donc lors de l'exécution de ce code,
cela va exposer
dans le scope global
une variable qui sera myLead
ici. Donc cela veut dire que c'est cette
variable qu'on va utiliser comme espace de nom
plus tard pour pouvoir
avoir assez au module ou aux
fonctionnalités du module. Ce qui explique le fait
qu'on utilise le myLead ici.
Vu que cette librairie
expose une méthode qui s'appelle
VOIL only ici.
Dans notre fichier nds.js
on utilise
le VOIL only
qui est disponible
sur l'interface publique
de cette librairie.
On va réaliser un petit test dans notre
navigateur pour prouver
déjà qu'en utilisant cette façon
de faire, c'est à dire le module paterne
l'environnement global n'est plus
polié avec le
VOIL que nous avons ici ou toutes
les variables. Supposons que nous déclarons d'autres variables
ici. Supposons que je
fasse ceci par exemple.
Que je déclare une variable ici
et que je la nomme
un toto par exemple.
Et que je lui donne la valeur.
Cette valeur.
Lorsque je vais exécuter
mon code dans le navigateur
donc je m'attends à avoir
d'abord le résultat escompté.
Le résultat escompté ici
c'est à dire le test qu'il y a mis en commentaire ici.
Et également
que l'environnement global
ne soit pas polué avec par exemple
la fonction qui a été déclarée ici
ou la variable qui a été déclarée ici.
Donc nous allons retourner à notre
navigateur ici et tester
ça. Donc j'actualise
mon page. Donc je vois
déjà pour la fonctionnalité
que j'ai le test
escompté
qui est affiché là.
Pour le hello world qui est passé en paramètre
à notre fonction
voyelle en lit.
On voit que les voyelles sont extraites
de ce test. Donc sont remplacées
par intervites.
Donc ça explique le hll
suivi de wrld
que nous avons là. Et concernant
la vérification de la pollution
il suffit de vérifier
si nous avons assez
au toto ici ou pas.
Donc si je fais ceci
que j'enregistre et que j'actualise
la page, je m'attends à avoir une erreur parce que
toto n'existe pas dans le
scope global.
Pareil, lorsque je
prends par exemple le voyelle qui est
ici, le voyelle
n'existe pas dans ce scope.
Donc du coup, lorsque je vais
essayer de faire ceci
lorsque j'actualise ma page
j'ai également une erreur parce que le voyelle
n'existe pas dans le scope global.
Donc cette technique est à utiliser
pour pouvoir développer des
librairies et exposer seulement
les fonctionnalités qui sont
censées être publiques. Et éviter également
de polluer l'environnement global
avec des variables qui
ne sont pas censées être vues
de l'extérieur. Nous avons ici un avant
dernier exemple dont l'objectif est de montrer
comment la même technique peut être utilisée pour
développer une propriété privée au niveau d'un objet
littéral. Donc ici nous utilisons
un if-e qui retourne
un nouvel objet littéral. Et ce
objet littéral dispose
de méthodes gateName et setName
c'est à dire des setters et des
getters qui permettent de contrôler
cette variable qui est
cachée ici. Donc ici, cette variable
n'est pas exposée à l'extérieur
et n'est pas visible également à l'extérieur
mais peut être manipulée
à travers l'objet littéral
qui est retourné ici.
Donc si nous essayons d'accéder
par exemple à cette
valeur directement, on verra
que ce n'est pas possible. Donc du coup
ici il suffit de faire
user.ceci
pour afficher
simplement
la valeur
de user. Donc on voit que cette propriété
n'apparaît nulle part dans
cette propriété.
Ou cette valeur plutôt n'apparaît
nulle part dans cette propriété. Parce que c'est
une variable qui est cachée
et qui est manipulée en passant par
ces
getters et ces setters.
Donc ceci nous permet d'avoir
un objet littéral avec une
propriété qui est privée.
Et dans ce cas il s'agit du
name ici qui est caché de l'extérieur.
Donc à côté on voit qu'on peut quand même
manipuler cette variable
en utilisant les getters
qui sont mis en place. Donc ici
dans un premier temps on récupère la valeur
du getter.
Et on voit que
cette valeur dans un premier temps
elle a la valeur nulle.
Ce qui est retourné ici.
Dans un second temps on utilise le setter
ici pour modifier cette
valeur en lui donnant cette variable
en lui donnant la nouvelle
valeur qui est passée en paramètre ici.
Donc il s'agit de toto. Et on voit
que à la récupération
de la valeur la nouvelle fois
on a la nouvelle valeur
de name qui est ici.
Donc ici pour montrer
que ceci permet de protéger également
l'asset. Si l'utilisateur
essaie de
mettre par exemple une valeur
qui n'est pas autorisée. Si je fais par exemple
false ici.
Donc j'aurai
une exception qui sera
levée ici. Donc si je regarde dans la console
ici, on voit que j'ai
une exception qui est levée
avec le message que nous avons ici.
Que le nom doit être
une selle de caractère.
Donc c'est de cette façon qu'on peut utiliser
également le module pattern
pour créer un objet
littéral avec une propriété qui est
protégée. Donc ici nous pouvons même
aller plus loin en faisant ceci.
Vu que ces méthodes ne sont pas
des vrais
getter, on pourrait faire ceci par exemple.
Donc j'aurais pu faire ceci
pour créer de vrais getter.
Donc ça veut dire que de l'extérieur
ça donne l'impression qu'il y a
une propriété
name qui existe alors
que ce n'est pas le cas. Donc ici
pour faire la même chose, je vais
juste faire ceci.
Donc on voit que j'ai la valeur nulle.
Ici lorsque je mets
par exemple ceci.
Je peux faire name
comme ça égale à total.
Donc si après j'essaie
d'accéder à la valeur de name
donc j'aurai la dernière valeur
que j'ai renseigné qui est ttp.
Donc là j'ai utilisé de vrais getter
pour le faire alors qu'avant c'était juste
de simple méthode qui était disponible
sur le littéral d'objet. Donc ici
lorsque nous affichons directement
l'objet littéral en question
c'est à dire le user ici.
On voit que nous avons juste une propriété
name qui fait référence
à des getter et à des setter.
Donc la vraie propriété name que nous
manipulons derrière et qui
est la donnée protégée et cachée ici.
L'objectif de cet exemple était
de montrer que le module pattern est plus
adapté à la création d'objets isolés
c'est à dire des objets qui sont créés
sans passer par une fonction constructeur.
Mais cela peut être adapté pour pouvoir avoir
une fonction constructeur et cela nous amène
au dernier exemple qui est un peu particulier.
Si nous référons au code que nous avons
ici, l'objectif est de créer une fonction
constructeur qui servira plus tard à la
création de nouveaux objets
qui seront des utilisateurs comme
nous l'avons ici. Nous avons vu dans
la précédente vidéo sur les prototypes qu'en
JavaScript, les propriétés publiques devraient
être ajoutées au niveau du prototype de la fonction
constructeur afin qu'elles soient partagées
par toutes les instances d'objets qui seront
créées ultérieurement en utilisant la fonction
constructeur. Donc cela veut dire que si nous
voulons déclarer une propriété
plutôt une méthode qui soit partagée entre
les instances d'objets qui seront
créées après ou ultérieurement
en utilisant cette fonction constructeur
il faut les rattacher au prototype
de cette fonction constructeur.
Donc avec le code que nous avons
ici, si nous souhaitons par exemple créer une nouvelle méthode
test qui sera partagée
par toutes les instances
d'objets créés à partir de cette
fonction constructeur
user, il nous suffit d'ajouter
cette méthode au niveau
du
prototype de cette fonction constructeur.
Donc nous avons ici un code
qui montre cela.
Donc ici, cela veut dire que nous ajoutons
au niveau du prototype
de la fonction constructeur
une méthode test qui sera partagée
par toutes les nouvelles instances
d'objets créés à partir
de cette fonction constructeur.
Et un autre dernier exemple que nous avons
ici, nous ne pouvons pas
rattacher les méthodes getName
et setName au prototype
de la fonction constructeur user.
Sinon elles seront partagées par toutes les instances
d'utilisateurs créés ultérieurement
en utilisant
cette fonction constructeur
user. Parce que comme
vous pouvez le constater ici,
ces méthodes nous permettent de manipuler
une propriété
qui est censée être privée
et non visible
de l'extérieur. Donc si nous
déplaçons cette propriété,
si nous déplaçons
ces méthodes et que nous les rattachons
au prototype, cela veut dire que ces méthodes seront
partagées par toutes les instances
des objets
qui seront créés à partir de cette fonction
constructeur alors que ce n'est pas ce que nous souhaitons
réaliser. Donc c'est pour cette
raison que ces méthodes
ont été déclarées directement au niveau
de la fonction constructeur au lieu d'être
rattachées au prototype
pour qu'elles soient partagées
par toutes les instances de nouveaux
objets créés à partir de cette fonction constructeur.
Il faut noter que
malgré que ces méthodes sont
propres à chaque instance
d'objet
créé à partir de la fonction
constructeur user,
ces méthodes restent quand même des méthodes
publiques, c'est-à-dire que ce sont des méthodes qui sont
accessibles comme on peut le voir ici
depuis l'extérieur.
Et ces méthodes sont appelées des méthodes
un peu particulières
parce que ce sont des méthodes publiques
mais qui permettent de
manipuler une propriété
qui est cachée ou non
visible ici, donc une propriété privée.
Donc c'est pourquoi on les appelle des méthodes
privilégiées, à la différence
des méthodes considérées comme publiques
et partagées qui sont définies
au niveau du prototype ici.
Donc c'est ce qui fait la différence entre les méthodes
publiques et les méthodes
privilégiées. Donc les méthodes
privilégiées ce sont quand même des méthodes publiques
c'est-à-dire accessibles de l'extérieur
jusqu'à elles permettent de manipuler
une propriété ici
qui est vraiment privée et non visible
de l'extérieur. Donc c'est de cette façon
qu'on peut utiliser, on peut
créer une fonction constructeur
qui permet de créer des objets
qui sont dotés de
propriétés
qui ne sont pas visibles à l'extérieur.
Ici, lors de l'appel
de la fonction constructeur
en faisant le new ici, on réalise en réalité
l'exécution
de ce code. Et c'est
en exécutant ce code qu'on déclare
cette variable qui est cachée de l'extérieur
vu que ce code
le scope d'exécution
est isolé de l'environnement
global. Et lorsqu'on regarde
l'objet ici, on voit que c'est seulement
les méthodes privilégiées
qui sont visibles à l'extérieur
mais pas la
variable name
que nous avons déclarée ici qui est cachée
de l'extérieur. Une dernière chose
importante à noter, c'est que le module pattern
souffre également du problème de pollution
de l'espace global.
Mais il réduit juste
la pollution en ajoutant seulement
la valeur retournée au scope global.
Il souffre également du problème de
collision de noms. Il est donc nécessaire de
choisir le bon nom de variable en espérant
qu'il ne soit pas utilisé
dans d'autres scripts. Voilà pour cette vidéo
dont l'objectif était de montrer comment on peut réaliser
l'encapsulation sans passer par
des classes et des modules ECMAScript.
Cela nous a quand même permis de faire un rappel
sur les closures, de découvrir les IFER
c'est-à-dire le Immediately Invoke
Function Expression, ainsi que
le module pattern ou les Revealing Module
Pattern qui se basent également sur
les IFER pour créer des variables et
des fonctions privées. Avec l'introduction
des modules ECMAScript depuis la version
IS2015 du langage et
des propriétés privées natives au
niveau des classes introduites dans la version
IS2022 du langage,
le module pattern n'est plus autant utilisé.
Cependant, de nombreux code-sources existants utilisent
ce pattern qui mérite d'être connu
pour être un bon développeur JavaScript.
Nous verrons donc dans les prochaines vidéos
l'utilisation des modules et des classes
et comment ils permettent de réaliser nativement
l'encapsulation.

