Maison  >  Article  >  interface Web  >  Compréhension approfondie de la série JavaScript (17) : Introduction détaillée à la programmation orientée objet_Connaissances de base

Compréhension approfondie de la série JavaScript (17) : Introduction détaillée à la programmation orientée objet_Connaissances de base

WBOY
WBOYoriginal
2016-05-16 16:11:181113parcourir

Présentation

Dans cet article, nous examinons divers aspects de la programmation orientée objet dans ECMAScript (bien que ce sujet ait déjà été abordé dans de nombreux articles). Nous examinerons ces questions davantage d’un point de vue théorique. En particulier, nous examinerons les algorithmes de création d'objets, la façon dont les objets sont liés (y compris les relations de base - héritage), qui peuvent également être utilisés dans les discussions (ce qui, je l'espère, dissipera certaines ambiguïtés conceptuelles précédentes sur la POO en JavaScript).

Texte original en anglais :http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/

Introduction, paradigme et idées

Avant de procéder à l'analyse technique de la POO dans ECMAScript, il nous est nécessaire de maîtriser certaines caractéristiques de base de la POO et de clarifier les principaux concepts dans l'introduction.

ECMAScript prend en charge une variété de méthodes de programmation, notamment structurée, orientée objet, fonctionnelle, impérative, etc. Dans certains cas, il prend également en charge la programmation orientée aspect, mais cet article traite de la programmation orientée objet, voici donc la programmation orientée objet ; programmation orientée en ECMAScript Définition :

ECMAScript est un langage de programmation orienté objet basé sur l'implémentation d'un prototype.
Il existe de nombreuses différences entre les approches POO basées sur des prototypes et basées sur des classes statiques. Jetons un coup d’œil à leurs différences en détail immédiat.

Basé sur les attributs de classe et basé sur un prototype

Notez qu'un point important a été souligné dans la phrase précédente - entièrement basé sur des classes statiques. Avec le mot « statique », nous entendons les objets statiques et les classes statiques, fortement typés (bien que non obligatoires).

Concernant cette situation, de nombreux documents sur le forum ont souligné que c'est la principale raison pour laquelle ils s'opposent à la comparaison des "classes avec des prototypes" en JavaScript, bien que leurs implémentations soient différentes (comme celles basées sur des classes dynamiques Python et Ruby) ne sont pas trop opposés au focus (certaines conditions sont écrites, bien qu'il existe certaines différences de pensée, JavaScript n'est pas devenu si alternatif), mais le focus de leur opposition est les classes statiques contre les prototypes dynamiques ), pour être précis, le mécanisme d'une classe statique (par exemple : C, JAVA) et de ses subordonnés et définitions de méthodes nous permet de voir la différence exacte entre celle-ci et une implémentation basée sur un prototype.

Mais listons-les un par un. Considérons les principes généraux et les principaux concepts de ces paradigmes.

Basé sur une classe statique

Dans le modèle basé sur les classes, il existe un concept de classes et d'instances. Les instances de classes sont également souvent nommées objets ou instances.

Classes et objets

Une classe représente une abstraction d'une instance (c'est-à-dire un objet). C'est un peu comme les mathématiques à cet égard, mais nous appelons cela un type ou une classification.

Par exemple (les exemples ici et ci-dessous sont des pseudocodes) :

Copier le code Le code est le suivant :

C = Classe {a, b, c} // Classe C, incluant les caractéristiques a, b, c

Les caractéristiques d'une instance sont : les propriétés (description de l'objet) et les méthodes (activités de l'objet). Les propriétés elles-mêmes peuvent également être considérées comme des objets : c'est-à-dire si les propriétés sont accessibles en écriture, configurables, définissables (getter/setter), etc. Ainsi, les objets stockent l'état (c'est-à-dire les valeurs spécifiques de toutes les propriétés décrites dans une classe) et les classes définissent des structures (propriétés) strictement immuables et un comportement (méthodes) strictement immuable pour leurs instances.
Copier le code Le code est le suivant :

C = Classe {a, b, c, méthode1, méthode2}

c1 = {a : 10, b : 20, c : 30} // La classe C est une instance : objet с1
c2 = {a : 50, b : 60, c : 70} // La classe C est une instance : l'objet с2, qui a son propre état (c'est-à-dire une valeur d'attribut)

Héritage hiérarchique

Pour améliorer la réutilisation du code, les classes peuvent être étendues de l'une à l'autre, en ajoutant des informations supplémentaires. Ce mécanisme est appelé héritage (hiérarchique).

Copier le code Le code est le suivant :

D = La classe s'étend C = {d, e} // {a, b, c, d, e>
d1 = {a : 10, b : 20, c : 30, d : 40, e : 50}

Lorsque vous appelez une méthode sur une instance d'une classe, vous rechercherez généralement la méthode dans la classe native. Si elle n'est pas trouvée, accédez à la classe parent directe pour rechercher. Si elle n'est pas encore trouvée, accédez à la classe parent de. la classe parent à rechercher (par exemple, dans une chaîne d'héritage stricte), si le haut de l'héritage est trouvé mais pas encore trouvé, le résultat est : l'objet n'a pas de comportement similaire et il n'y a aucun moyen d'obtenir le résultat.

Copier le code Le code est le suivant :

d1.method1() // D.method1 (non) -> C.method1 (oui)
d1.method5() // D.method5 (non) -> C.method5 (non) ->
Contrairement à l'héritage, où les méthodes ne sont pas copiées dans une sous-classe, les propriétés sont toujours copiées dans les sous-classes. Nous pouvons voir que la sous-classe D hérite de la classe parent C : les attributs a, b, c sont copiés et la structure de D est {a, b, c, d, e}}. Cependant, les méthodes {method1, method2} ne sont pas copiées du passé, mais héritées du passé. Par conséquent, si une classe profonde possède des attributs dont l'objet n'a pas du tout besoin, alors la sous-classe possède également ces attributs.

Concepts clés basés sur les cours

Nous avons ainsi les concepts clés suivants :

1. Avant de créer un objet, la classe doit être déclarée. Il faut d'abord définir sa classe

2. Par conséquent, l'objet sera créé à partir de la classe abstraite dans son propre « iconogramme et similarité » (structure et comportement)
3. Les méthodes sont traitées via une chaîne d'héritage stricte, directe et immuable
4. La sous-classe contient tous les attributs de la chaîne d'héritage (même si certains attributs ne sont pas nécessaires à la sous-classe
) ; 5. Créez une instance de classe. Une classe ne peut pas (en raison du modèle statique) modifier les caractéristiques (propriétés ou méthodes) de son instance
; 6. Les instances (en raison du modèle statique strict) ne peuvent pas avoir de comportements ou d'attributs supplémentaires autres que ceux déclarés dans la classe correspondant à l'instance.

Voyons comment remplacer le modèle POO en JavaScript, c'est ce que nous proposons sur la base du prototype POO.

Basé sur un prototype

Le concept de base ici est celui des objets mutables dynamiques. Les transformations (transformations complètes, incluant non seulement des valeurs mais aussi des attributs) sont directement liées aux langages dynamiques. Les objets comme ceux-ci peuvent stocker toutes leurs propriétés (propriétés, méthodes) indépendamment sans avoir besoin d'une classe.

Copier le code Le code est le suivant :
objet = {a : 10, b : 20, c : 30, méthode : fn};
objet.a; // 10
objet.c; // 30
objet.method();

De plus, étant dynamiques, ils peuvent facilement changer (ajouter, supprimer, modifier) ​​leurs caractéristiques :


Copier le code Le code est le suivant :
object.method5 = function () {...}; // Ajouter une nouvelle méthode
object.d = 40; // Ajouter un nouvel attribut "d"
delete object.c; // Supprimer l'attribut "с"
object.a = 100; // Modifier l'attribut "а"

// Le résultat est : objet : {a : 100, b : 20, d : 40, méthode : fn, méthode5 : fn};

C'est-à-dire qu'au moment de l'affectation, si un attribut n'existe pas, créez-le et initialisez l'affectation avec lui, et s'il existe, mettez-le simplement à jour.

Dans ce cas, la réutilisation du code ne se fait pas par extension de classe, (veuillez noter que nous n'avons pas dit que la classe ne peut pas être modifiée, car il n'y a pas de notion de classe ici), mais par prototype.

Un prototype est un objet qui est utilisé comme copie primitive d'autres objets, ou si certains objets n'ont pas les propriétés nécessaires qui leur sont propres, le prototype peut être utilisé comme délégué pour ces objets et servir d'objet auxiliaire .

Basé par délégation

N'importe quel objet peut être utilisé comme objet prototype pour un autre objet, car un objet peut facilement changer son prototype de manière dynamique au moment de l'exécution.

Notez que nous envisageons actuellement une vue d'ensemble plutôt qu'une implémentation spécifique. Lorsque nous discuterons d'implémentations spécifiques dans ECMAScript, nous verrons certaines de leurs propres caractéristiques.

Exemple (pseudocode) :


Copier le code Le code est le suivant :
x = {a : 10, b : 20};
y = {a : 40, c : 50};
y.[[Prototype]] = x; // x est le prototype de y

oui; // 40, propres caractéristiques
y.c; // 50, aussi ses propres caractéristiques
y.b; // 20 – obtenu à partir du prototype : y.b (non) -> y.[[Prototype]].b (oui) : 20

delete y.a; // Supprime son propre "а"
oui; // 10 – Obtenez
du prototype
z = {a : 100, e : 50}
y.[[Prototype]] = z; // Modifier le prototype de y en z
y.a; // 100 – Obtenez
du prototype z oui // 50, également obtenu à partir du prototype z

z.q = 200 // Ajouter de nouvelles propriétés au prototype
y.q // La modification s'applique également à y

Cet exemple montre la fonction et le mécanisme importants du prototype en tant qu'attribut d'objet auxiliaire, tout comme demander ses propres attributs, par rapport à ses propres attributs, ces attributs sont des attributs délégués. Ce mécanisme est appelé délégué, et un modèle prototype basé sur celui-ci est un prototype délégué (ou prototype basé sur un délégué). Le mécanisme de référence ici est appelé envoi d'un message à un objet. Si l'objet n'obtient pas de réponse, il sera délégué au prototype pour le trouver (l'obligeant à essayer de répondre au message).

La réutilisation du code dans ce cas est appelée héritage basé sur les délégués ou héritage basé sur les prototypes. Puisque n’importe quel objet peut être utilisé comme prototype, cela signifie qu’un prototype peut également avoir son propre prototype. Ces prototypes sont reliés entre eux pour former ce qu'on appelle une chaîne de prototypes. Les chaînes sont également hiérarchiques comme les classes statiques, mais elles peuvent être facilement réorganisées pour modifier la hiérarchie et la structure.

Copier le code Le code est le suivant :

x = {a : 10}

y = {b : 20}
y.[[Prototype]] = x

z = {c : 30}
z.[[Prototype]] = y

z.a // 10

// z.a se retrouve dans la chaîne prototype :
// z.a (non) ->
// z.[[Prototype]].a (non) ->
// z.[[Prototype]].[[Prototype]].a (oui) : 10

Si un objet et sa chaîne prototype ne peuvent pas répondre à l'envoi de message, l'objet peut activer le signal système correspondant, éventuellement géré par d'autres délégués sur la chaîne prototype.

Ce signal système est disponible dans de nombreuses implémentations, y compris des systèmes basés sur des classes dynamiques entre crochets : #doesNotUnderstand dans Smalltalk, method_missing dans Ruby ; __getattr__ en Python, __call en PHP et __noSuchMethod__ implémentation dans ECMAScript, etc.

Exemple (implémentation ECMAScript de SpiderMonkey) :

Copier le code Le code est le suivant :

var objet = {

//Capte les signaux du système qui ne peuvent pas répondre aux messages
__noSuchMethod__ : fonction (nom, arguments) {
alerte([nom, arguments]);
Si (nom == 'test') {
         return 'la méthode .test() est gérée';
>
Renvoyez le délégué[nom].apply(this, args);
>

};

var délégué = {
carré : fonction (a) {
Renvoie un * a;
>
};

alert(object.square(10)); // 100
alert(object.test()); // La méthode .test() est gérée

En d'autres termes, si l'implémentation basée sur la classe statique ne peut pas répondre au message, la conclusion est que l'objet actuel n'a pas les caractéristiques requises, mais si vous essayez de l'obtenir à partir de la chaîne de prototypes, vous pouvez toujours obtenir le résultat, ou l'objet possède cette caractéristique après une série de changements.

Concernant ECMAScript, l'implémentation spécifique est : l'utilisation de prototypes basés sur des délégués. Cependant, comme nous le verrons dans la spécification et la mise en œuvre, ils ont également leurs propres caractéristiques.

Modèle concaténatif

Honnêtement, il faut dire quelque chose sur une autre situation (dès qu'elle n'est pas utilisée dans ECMASCript) : la situation où le prototype remplace l'objet natif d'autres objets. Dans ce cas, la réutilisation du code est une copie fidèle (clone) d'un objet pendant la phase de création de l'objet plutôt qu'une délégation. Ce type de prototype est appelé prototype concaténatif. La copie de toutes les propriétés du prototype d'un objet peut en outre modifier complètement ses propriétés et ses méthodes, et le prototype peut également se modifier lui-même (dans un modèle basé sur des délégués, cette modification ne modifiera pas le comportement de l'objet existant, mais modifiera ses propriétés du prototype). L’avantage de cette méthode est qu’elle peut réduire le temps de planification et de délégation, mais l’inconvénient est que l’utilisation de la mémoire est élevée.

Type Canard

Renvoyer des objets qui modifient dynamiquement les types faibles. Par rapport aux modèles basés sur des classes statiques, tester s'il peut faire ces choses n'a rien à voir avec le type (classe) de l'objet, mais s'il peut répondre au message (qui c'est-à-dire, après avoir vérifié si la capacité de le faire est indispensable).

Par exemple :

Copier le code Le code est le suivant :

// Dans un modèle statique
if (instance d'objet de SomeClass) {
// Certaines actions sont exécutées
>

// En implémentation dynamique
// Le type de l'objet n'a pas d'importance à ce stade
// Parce que les mutations, les types et les propriétés peuvent être transformés librement et de manière répétée.
// Si les objets importants peuvent répondre aux messages de test
if (isFunction(object.test)) // ECMAScript

if object.respond_to?(:test) // Ruby

if hasattr(object, 'test'): // Python

C'est ce qu'on appelle le type Dock. Autrement dit, les objets peuvent être identifiés par leurs propres caractéristiques lors de la vérification, plutôt que par leur position dans la hiérarchie ou leur appartenance à un type spécifique.

Concepts clés basés sur des prototypes

Regardons les principales caractéristiques de cette approche :

1. Le concept de base est objet
2. L'objet est complètement dynamique et variable (théoriquement il peut être converti d'un type à un autre)
3. Les objets n'ont pas de classes strictes qui décrivent leur propre structure et leur propre comportement. Les objets n'ont pas besoin de classes
. 4. Les objets n'ont pas de classes mais peuvent avoir des prototypes. S'ils ne peuvent pas répondre aux messages, ils peuvent être délégués au prototype
. 5. Le prototype de l'objet peut être modifié à tout moment pendant l'exécution ;
6. Dans le modèle basé sur les délégués, la modification des caractéristiques du prototype affectera tous les objets liés au prototype ;
7. Dans le modèle de prototype concaténatif, le prototype est une copie originale clonée à partir d'autres objets, et devient en outre une copie originale complètement indépendante. La transformation des caractéristiques du prototype n'affectera pas les objets clonés à partir de celui-ci
. 8. S'il est impossible de répondre au message, son appelant peut prendre des mesures supplémentaires (par exemple, modifier la planification)
9. La défaillance des objets ne peut pas être déterminée par leur niveau et à quelle classe ils appartiennent, mais par les caractéristiques actuelles

Cependant, il existe un autre modèle que nous devrions également considérer.

Basé sur des classes dynamiques

Nous pensons que la distinction "classe VS prototype" montrée dans l'exemple ci-dessus n'est pas si importante dans ce modèle basé sur des classes dynamiques, (surtout si la chaîne de prototypes est immuable, pour une distinction plus précise, il faut quand même considérons une classe statique). A titre d'exemple, il pourrait également utiliser Python ou Ruby (ou d'autres langages similaires). Ces langages utilisent tous un paradigme dynamique basé sur les classes. Cependant, sous certains aspects, nous pouvons voir certaines fonctionnalités implémentées sur la base du prototype.

Dans l'exemple suivant, nous pouvons voir que sur la base uniquement de la délégation, nous pouvons agrandir une classe (prototype), affectant ainsi tous les objets liés à cette classe. Nous pouvons également modifier dynamiquement cet objet au moment de l'exécution (en fournissant une nouvelle classe. objet pour le délégué) et ainsi de suite.

Copier le code Le code est le suivant :

#Python

classe A (objet):

Def __init__(soi, a):
         self.a = a

Carré def (soi) :
          retourner self.a * self.a

a = A(10) # Créer une instance
imprimer(a.a) #10

A.b = 20 # Fournir un nouvel attribut pour la classe
print(a.b) # 20 –
est accessible dans l'instance "a"
a.b = 30 # Créer ses propres attributs
imprimer(a.b) #30

del a.b # Supprimer ses propres attributs
print(a.b) # 20 - Récupérez à nouveau le (prototype) de la classe

# Tout comme un modèle basé sur un prototype
# Vous pouvez changer le prototype de l'objet au moment de l'exécution

classe B(objet) : # Classe B vide
Passer

b = B() # Instance de B

b.__class__ = A # Changer dynamiquement la classe (prototype)

b.a = 10 # Créer un nouvel attribut
print(b.square()) # 100 - Les méthodes de classe A sont disponibles en ce moment

# Vous pouvez afficher les références aux classes supprimées
del A
del B

# Mais l'objet a toujours des références implicites, et ces méthodes sont toujours disponibles
print(b.square()) # 100

# Mais la classe ne peut pas être modifiée pour le moment
# Il s'agit d'une fonctionnalité implémentée
b.__class__ = dict # erreur

L'implémentation dans Ruby est similaire : des classes entièrement dynamiques sont également utilisées (d'ailleurs dans la version actuelle de Python, contrairement à Ruby et ECMAScript, l'agrandissement des classes (prototypes) ne fonctionne pas), on peut complètement changer l'objet (ou de classe) (ajout de méthodes/propriétés à la classe, et ces changements affecteront les objets existants), cependant, il ne peut pas changer dynamiquement la classe d'un objet.

Cependant, cet article ne concerne pas spécifiquement Python et Ruby, nous n'en dirons donc pas plus et continuons à discuter d'ECMAScript lui-même.

Mais avant cela, nous devons jeter un autre regard sur le « sucre syntaxique » que l'on trouve dans certaines POO, car de nombreux articles précédents sur JavaScript abordent souvent ces questions.

La seule phrase incorrecte à noter dans cette section est : "JavaScript n'est pas une classe, il possède des prototypes, qui peuvent remplacer les classes." Il est important de savoir que toutes les implémentations basées sur les classes ne sont pas complètement différentes. Même si l'on peut dire que "JavaScript est différent", il faut également considérer que (en plus du concept de "classes") il existe d'autres caractéristiques connexes. .

Autres fonctionnalités de diverses implémentations de POO

Dans cette section, nous présentons brièvement d'autres fonctionnalités et méthodes de réutilisation de code dans diverses implémentations de POO, y compris les implémentations de POO dans ECMAScript. La raison en est qu'il existe certaines restrictions de pensée habituelles sur l'implémentation de la POO en JavaScript. La seule exigence principale est que cela soit prouvé techniquement et idéologiquement. On ne peut pas dire que nous n'avons pas découvert la fonction sucre syntaxique dans d'autres implémentations de POO, et nous avons hâtivement supposé que JavaScript n'était pas un langage POO pur. C'est faux.

Polymorphe

Les objets ont plusieurs significations de polymorphisme dans ECMAScript.

Par exemple, une fonction peut être appliquée à différents objets, tout comme les propriétés de l'objet natif (car la valeur est déterminée lors de la saisie du contexte d'exécution) :

Copier le code Le code est le suivant :

fonction test() {
alert([this.a, this.b]);
>

test.call({a : 10, b : 20}); // 10, 20
test.call({a : 100, b : 200}); // 100, 200

var a = 1;
varb = 2;

test(); // 1, 2

Il existe cependant des exceptions : la méthode Date.prototype.getTime() qui, selon la norme, doit toujours avoir un objet date, sinon une exception sera levée.
Copier le code Le code est le suivant :

alert(Date.prototype.getTime.call(new Date())); // heure
alert(Date.prototype.getTime.call(new String(''))); // TypeError

Le soi-disant polymorphisme des paramètres lors de la définition d'une fonction est équivalent à tous les types de données, sauf qu'il accepte les paramètres polymorphes (comme la méthode de tri .sort du tableau et ses paramètres - fonction de tri polymorphe). À propos, l’exemple ci-dessus peut également être considéré comme une sorte de polymorphisme paramétrique.

Les méthodes du prototype peuvent être définies comme vides, et tous les objets créés doivent redéfinir (implémenter) cette méthode (c'est-à-dire "une interface (signature), plusieurs implémentations").

Le polymorphisme est lié au type Canard que nous avons mentionné ci-dessus : c'est-à-dire que le type et la position de l'objet dans la hiérarchie ne sont pas si importants, mais s'il possède toutes les caractéristiques nécessaires, il peut être facilement accepté (c'est-à-dire que les interfaces communes sont importantes , les implémentations peuvent être diverses).

Encapsulation

Il existe souvent des idées fausses sur l'encapsulation. Dans cette section, nous discutons de certains sucres syntaxiques dans les implémentations de la POO - également connus sous le nom de modificateurs : Dans ce cas, nous discuterons de certains "sucres" pratiques dans les implémentations de la POO - des modificateurs bien connus : privé, protégé et public (également connu sous le nom d'accès à un objet niveau ou modificateur d’accès).

Ici, je voudrais vous rappeler l'objectif principal de l'encapsulation : l'encapsulation est un ajout abstrait, pas un "hacker malveillant" caché qui écrit quelque chose directement dans votre classe.

C'est une grosse erreur : utilisez hide pour vous cacher.

Des niveaux d'accès (privé, protégé et public) ont été implémentés dans de nombreux programmes orientés objet pour faciliter la programmation (sucre de syntaxe vraiment très pratique), décrivant et construisant des systèmes de manière plus abstraite.

Cela peut être vu dans certaines implémentations (telles que Python et Ruby déjà mentionnées). D'une part (en Python), ces attributs __private_protected (nommés via la convention underscore) ne sont pas accessibles de l'extérieur. Python, en revanche, est accessible de l'extérieur avec des règles spéciales (_ClassName__field_name).

Copier le code Le code est le suivant :

classe A (objet):

Déf __init__(soi) :
        self.public = 10
        self.__private = 20

Déf get_private(self):
          retourner self.__private

#extérieur :

a = A() # Instance de A

print(a.public) # OK, 30
print(a.get_private()) # OK, 20
print(a.__private) # Échec car il ne peut être utilisé qu'en A

# Mais en Python, on y accède via des règles spéciales

print(a._A__private) # OK, 20

Dans Ruby : D'une part, il a la capacité de définir des caractéristiques privées et protégées. D'autre part, il existe également des méthodes spéciales (telles que instance_variable_get, instance_variable_set, send, etc.) pour obtenir des données encapsulées.

Copier le code Le code est le suivant :

classe A

def initialiser
@a = 10
fin

def public_method
méthode_privée(20)
fin

privé

def private_method(b)
Retour @a b
fin

fin

a = A.new # Nouvelle instance

a.public_method # OK, 30

a.a # Échec, @a - est une variable d'instance privée

# "private_method" est privé et n'est accessible qu'en classe A

a.private_method # Erreur

# Mais il existe un nom de méthode de métadonnées spécial qui peut obtenir des données

a.send(:private_method, 20) # OK, 30
a.instance_variable_get(:@a) # OK, 10

La raison principale est que le programmeur lui-même souhaite obtenir les données encapsulées (notez que je n'utilise spécifiquement pas de données "cachées"). Si ces données changent de manière incorrecte d'une manière ou d'une autre ou comportent des erreurs, l'entière responsabilité incombe au programmeur, mais pas simplement aux « erreurs de frappe » ou à la « simple modification de certains champs ». Mais si cela se produit fréquemment, il s'agit d'une très mauvaise habitude et d'un très mauvais style de programmation, car il vaut généralement la peine d'utiliser l'API publique pour « parler » à l'objet.

Je le répète, l'objectif fondamental de l'encapsulation est de faire abstraction de l'utilisateur des données auxiliaires, et non d'empêcher les pirates de cacher les données. Plus sérieusement, l'encapsulation n'utilise pas le privé pour modifier les données afin d'assurer la sécurité du logiciel.

Encapsuler les objets auxiliaires (partiels). Nous utilisons un coût minimal, une localisation et des changements prédictifs pour assurer la faisabilité des changements de comportement dans les interfaces publiques.

De plus, l'objectif important de la méthode setter est d'abstraire des calculs complexes. Par exemple, le setter element.innerHTML - l'instruction abstraite - "Le HTML à l'intérieur de cet élément est maintenant le contenu suivant", et la fonction setter dans la propriété innerHTML sera difficile à calculer et à vérifier. Dans ce cas, le problème concerne principalement l’abstraction, mais l’encapsulation se produit également.

Le concept d'encapsulation n'est pas seulement lié à la POO. Par exemple, il peut s'agir d'une fonction simple qui n'encapsule que divers calculs, la rendant abstraite (l'utilisateur n'a pas besoin de savoir, par exemple, comment la fonction Math.round(...) est implémentée, l'utilisateur appelle simplement il). C'est une sorte d'encapsulation. Notez que je n'ai pas dit que c'est "privé, protégé et public".

La version actuelle de la spécification ECMAScript ne définit pas les modificateurs private, protected et public.

Cependant, dans la pratique, il est possible de voir quelque chose nommé "Mock JS Encapsulation". Généralement ce contexte est destiné à être utilisé (en règle générale, le constructeur lui-même). Malheureusement, ce "mimétisme" est souvent mis en œuvre et les programmeurs peuvent produire des "méthodes getter/setter" de paramétrage d'entité pseudo-absolument non abstraites (je le répète, c'est faux) :

Copier le code Le code est le suivant :

fonction A() {

var _a; // "privé" a

this.getA = fonction _getA() {
Retourner _a;
};

this.setA = fonction _setA(a) {
_a = a;
};

>

var a = nouveau A();

a.setA(10);
alert(a._a); // non défini, "privé"
alert(a.getA()); // 10

Donc, tout le monde comprend que pour chaque objet créé, les méthodes getA/setA sont également créées, ce qui est aussi la raison de l'augmentation de la mémoire (par rapport à la définition du prototype). Cependant, en théorie, l'objet peut être optimisé dans le premier cas.

De plus, certains articles JavaScript mentionnent souvent la notion de « méthodes privées ». Remarque : La norme ECMA-262-3 ne définit aucune notion de « méthodes privées ».

Cependant, dans certains cas, il peut être créé dans le constructeur, car JS est un langage idéologique - les objets sont complètement mutables et ont des caractéristiques uniques (sous certaines conditions dans le constructeur, certains objets peuvent obtenir des méthodes supplémentaires tandis que d'autres non ).

De plus, en JavaScript, si l'encapsulation est encore interprétée à tort comme une compréhension qui empêche les pirates malveillants d'écrire automatiquement certaines valeurs​​au lieu d'utiliser la méthode setter, alors ce qu'on appelle « caché » et « « privé » n'est en fait pas très "caché", certaines implémentations peuvent obtenir la valeur sur la chaîne de portée appropriée (et tous les objets variables correspondants) en appelant le contexte à la fonction eval (peut être testé sur SpiderMonkey1.7).

Copier le code Le code est le suivant :

eval('_a = 100', a.getA); // ou a.setA, car les deux méthodes "_a" sont sur [[Scope]]
a.getA(); // 100

Alternativement, l'implémentation permet un accès direct à l'objet actif (comme Rhino), et la valeur de la variable interne peut être modifiée en accédant à la propriété correspondante de l'objet :

Copier le code Le code est le suivant :

// Rhinocéros
var foo = (fonction () {
var x = 10; // "privé"
fonction de retour () {
Imprimer(x);
};
})();
foo(); // 10
foo.__parent__.x = 20;
foo(); // 20

Parfois, les données « privées » et « protégées » sont obtenues en JavaScript en préfixant les variables avec un trait de soulignement (mais par rapport à Python, il ne s'agit que d'une convention de dénomination) :

var _myPrivateData = 'testString';
Il est souvent utilisé pour mettre le contexte d'exécution entre parenthèses, mais pour les données auxiliaires réelles, il n'est pas directement lié à l'objet, mais est simplement pratique pour faire abstraction d'une API externe :

Copier le code Le code est le suivant :

(fonction () {

// Initialiser le contexte

})();

Héritage multiple

L'héritage multiple est un sucre syntaxique très pratique pour améliorer la réutilisation du code (si nous pouvons hériter d'une classe à la fois, pourquoi ne pouvons-nous pas en hériter 10 à la fois ?). Cependant, en raison de certaines lacunes de l’héritage multiple, sa mise en œuvre n’est pas devenue populaire.

ECMAScript ne prend pas en charge l'héritage multiple (c'est-à-dire qu'un seul objet peut être utilisé comme prototype direct), bien que son langage de programmation ancêtre ait une telle capacité. Mais dans certaines implémentations (telles que SpiderMonkey), l'utilisation de __noSuchMethod__ peut être utilisée pour gérer la planification et la délégation au lieu de la chaîne de prototypes.

Mixins

Les mixins sont un moyen pratique de réutiliser du code. Les mixins ont été suggérés comme alternatives à l'héritage multiple. Chacun de ces éléments individuels peut être mélangé avec n'importe quel objet pour étendre ses fonctionnalités (les objets peuvent donc également être mélangés avec plusieurs Mixins). La spécification ECMA-262-3 ne définit pas le concept de « Mixins », mais selon la définition des Mixins et ECMAScript a des objets dynamiquement mutables, il n'y a aucun obstacle à simplement étendre les fonctionnalités à l'aide de Mixins.

Exemple typique :

Copier le code Le code est le suivant :

// assistant pour l'augmentation
Object.extend = fonction (destination, source) {
for (propriété dans la source) if (source.hasOwnProperty(property)) {
destination[propriété] = source[propriété];
>
Destination de retour ;
};

var X = {a : 10, b : 20};
var Y = {c : 30, d : 40};

Object.extend(X, Y); // mélanger Y dans X
alerte([X.a, X.b, X.c, X.d]); 10, 20, 30, 40

Veuillez noter que j'utilise ces définitions ("mixin", "mix") entre guillemets mentionnées dans ECMA-262-3. Il n'y a pas de tel concept dans la spécification, et il ne s'agit pas de mix mais couramment utilisé pour étendre des objets. avec de nouvelles fonctionnalités. (Le concept de mixins dans Ruby est officiellement défini. Les mixins créent une référence à un module conteneur au lieu de simplement copier toutes les propriétés du module dans un autre module - en fait : créer un objet supplémentaire (prototype) pour le délégué. ).

Caractéristiques

Les traits sont similaires dans leur concept aux mixins, mais ils ont beaucoup de fonctionnalités (par définition, puisque les mixins peuvent être appliqués, ils ne peuvent pas contenir d'état, car cela peut provoquer des conflits de noms). Selon ECMAScript, les Traits et les mixins suivent les mêmes principes, donc la spécification ne définit pas le concept de « Traits ».

Interface

Les interfaces implémentées dans certaines POO sont similaires aux mixins et aux traits. Cependant, contrairement aux mixins et aux traits, les interfaces obligent les classes d'implémentation à implémenter le comportement de leurs signatures de méthode.

Les interfaces peuvent être entièrement considérées comme des classes abstraites. Cependant, par rapport aux classes abstraites (les méthodes des classes abstraites ne peuvent implémenter qu'une partie de la méthode, et l'autre partie est toujours définie comme une signature), l'héritage ne peut hériter que d'une seule classe de base, mais peut hériter de plusieurs interfaces. les interfaces (multiples mixtes) peuvent être considérées comme une alternative à l’héritage multiple.

La norme ECMA-262-3 ne définit ni la notion d'« interface » ni la notion de « classe abstraite ». Cependant, à titre d'imitation, il est possible d'implémenter un objet avec une méthode "vide" (ou une exception levée dans une méthode vide pour indiquer au développeur que cette méthode doit être implémentée).

Combinaison d'objets

La composition d'objets est également l'une des technologies de réutilisation dynamique du code. La composition d'objets diffère de l'héritage très flexible dans la mesure où elle implémente un délégué dynamiquement modifiable. Et cela repose également sur des prototypes commandés. En plus des prototypes dynamiquement modifiables, l'objet peut regrouper des objets pour les délégués (créer ainsi une combinaison - une agrégation) et envoyer en outre des messages aux objets qui délèguent au délégué. Cela peut être fait avec plus de deux délégués, car sa nature dynamique signifie qu'elle peut changer au moment de l'exécution.

L'exemple __noSuchMethod__ déjà mentionné fait cela, mais montrons également comment utiliser explicitement les délégués :

Par exemple :

Copier le code Le code est le suivant :

var _delegate = {
foo : fonction () {
alert('_delegate.foo');
>
};

var agrégat = {

délégué : _delegate,

foo : fonction () {
Renvoyez this.delegate.foo.call(this);
>

};

global.foo(); // délégué.foo

agrégat.delegate = {
foo : fonction () {
alert('foo du nouveau délégué');
>
};

Aggregate.foo(); // foo du nouveau délégué

Cette relation d'objet est appelée "has-a", et l'intégration est une relation "is-a".

En raison du manque de composition explicite (flexibilité par rapport à l'héritage), l'ajout de code intermédiaire est également acceptable.

Fonctionnalités AOP

En tant que fonction orientée aspect, vous pouvez utiliser des décorateurs de fonctions. La spécification ECMA-262-3 ne définit pas clairement la notion de « décorateurs de fonctions » (contrairement à Python, où ce terme est officiellement défini). Cependant, les fonctions avec des paramètres fonctionnels peuvent être décorées et activées sous certains aspects (en appliquant ce que l'on appelle des suggestions) :

L'exemple de décorateur le plus simple :

Copier le code Le code est le suivant :

function checkDecorator(originalFunction) {
fonction de retour () {
Si (fooBar != 'test') {
alert('mauvais paramètre');
Renvoie faux ;
>
Renvoie originalFunction();
};
>

fonction test() {
alert('fonction de test');
>

var testWithCheck = checkDecorator(test);
var fooBar = faux;

test(); // 'fonction de test'
testWithCheck(); // 'mauvais paramètre'

fooBar = 'test';
test(); // 'fonction de test'
testWithCheck(); // 'fonction de test'

Conclusion

Dans cet article, nous avons clarifié l'introduction de la POO (j'espère que ces informations vous ont été utiles), et dans le prochain chapitre nous continuerons avec l'implémentation d'ECMAScript pour la programmation orientée objet.

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn