introduire
Dans cet article, nous considérons divers aspects de la programmation orientée objet dans ECMAScript (bien que ce sujet ait été discuté dans de nombreux articles auparavant). Nous examinerons ces problèmes davantage dans un point de vue théorique. En particulier, nous considérerons l'algorithme de création de l'objet, comment la relation entre les objets (y compris les relations de base - l'héritage) est également disponible dans la discussion (j'espère qu'une partie de l'ambiguïté conceptuelle de la POO en JavaScript sera supprimée).
Texte d'anglais original: http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/
Introduction, paradigme et pensées
Avant d'effectuer une analyse technique de la POO dans ECMAScript, il est nécessaire de maîtriser certaines caractéristiques de base de la POO et de clarifier les principaux concepts de l'introduction.
ECMascript prend en charge diverses méthodes de programmation, y compris structurées, orientées objet, fonctionnelle, impérative, etc., et dans certains cas, il prend également en charge la programmation orientée vers l'aspect; Mais cet article traite de la programmation orientée objet, nous allons donc donner la définition de la programmation orientée objet dans ECMascript:
ECMAScript est un langage de programmation orienté objet basé sur l'implémentation du prototype.
Il existe de nombreuses différences entre la POO basée sur le prototype et les méthodes basées sur la classe statique. Jetons un coup d'œil à leurs différences détaillées directes.
Basé sur la classe et basé sur les classes
Notez que le point important de la phrase précédente a déjà souligné - il est entièrement basé sur des classes statiques. Avec le mot «statique», nous comprenons des objets statiques et des classes statiques, fortement typées (bien que non requises).
En ce qui concerne cette situation, de nombreux documents du forum soulignent que c'est la principale raison pour laquelle ils s'opposent à la comparaison des «classes aux prototypes» en JavaScript. Bien que leurs différences de mise en œuvre (telles que Python et Ruby basées sur des classes dynamiques) ne soient pas trop opposées au point (certaines conditions sont écrites, bien qu'il existe des différences dans les idées, JavaScript n'est pas si alternatif), mais leur opposition est des classes statiques et des prototypes dynamiques (statistiques + classes par rapport aux prototypes dynamiques +). Pour être précis, une classe statique (comme C ++, Java) et ses subordonnés et les mécanismes de définition de la méthode peuvent nous permettre de voir la différence exacte entre l'informatique et la mise en œuvre du prototype.
Mais énumérons-les un par un. Considérons les principes généraux et les principaux concepts de ces paradigmes.
Basé sur des classes statiques
Dans un modèle basé sur les classes, il y a un concept sur les classes et les instances. Les instances de classes sont également souvent nommées objets ou paradigmes.
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, mais nous l'appelons type ou classification.
Par exemple (les exemples ici et ci-dessous sont pseudo-code):
La copie de code est la suivante:
C = classe {a, b, c} // classe C, y compris les fonctionnalités a, b, c
Les caractéristiques de l'instance sont: les attributs (description de l'objet) et les méthodes (activité de l'objet). Les caractéristiques elles-mêmes peuvent également être considérées comme des objets: c'est-à-dire si l'attribut est écrivable, configurable, réglable (Getter / Setter), etc. Par conséquent, les objets stockent l'état (c'est-à-dire les valeurs spécifiques de tous les attributs décrits dans une classe), et les classes définissent des structures strictement invariantes (propriétés) et des comportements strictement invariants (méthodes) pour leurs instances.
La copie de code est la suivante:
C = classe {a, b, c, méthode1, méthode2}
c1 = {a: 10, b: 20, c: 30} // classe C est une instance: objet с1
c2 = {a: 50, b: 60, c: 70} // classe C est une instance: objet с2, qui a son propre état (c'est-à-dire la 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).
La copie de code est la suivante:
D = class étend c = {d, e} // {a, b, c, d, e}
d1 = {a: 10, b: 20, c: 30, d: 40, e: 50}
Lorsque vous appelez la partie sur l'instance de la classe, la classe native recherchera généralement la méthode maintenant. S'il n'est pas trouvé, accédez à la classe parent pour rechercher directement. S'il n'est pas trouvé, accédez à la classe parent de la classe parent pour rechercher (par exemple, sur la chaîne d'héritage stricte). S'il est constaté que le haut de l'héritage n'a pas été trouvé, le résultat est: l'objet n'a pas de comportement similaire et ne peut pas obtenir le résultat.
La copie de code est la suivante:
D1.Method1 () // D.Method1 (non) -> C.Method1 (Oui)
d1.Method5 () // D.Method5 (NO) -> C.Method5 (NO) -> Aucun résultat
Par rapport aux méthodes d'héritage qui ne sont pas copiées dans une sous-classe, les propriétés sont toujours compliquées en 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, la méthode {Method1, Method2} ne copie pas le passé, mais hérite du passé. Par conséquent, c'est-à-dire que si une classe très profonde a des propriétés dont les objets n'ont pas besoin du tout, la sous-classe possède également ces propriétés.
Concepts clés basés sur les classes
Par conséquent, nous avons les concepts clés suivants:
1. Avant de créer un objet, la classe doit être déclarée. Premièrement, il est nécessaire de définir sa classe.
2. Par conséquent, l'objet sera créé à partir d'une classe abstraite dans son propre "pictographique et similitude" (structure et comportement)
3. La méthode est gérée par 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 des attributs ne sont pas requis par la sous-classe);
5. Créez une instance de classe. La classe ne peut pas (en raison d'un modèle statique) pour modifier les caractéristiques (attributs ou méthodes) de son instance;
6. Les instances (en raison de modèles statiques stricts) ne peuvent pas avoir des comportements ou des attributs supplémentaires, à l'exception des comportements et des attributs déclarés dans la classe correspondante à l'instance.
Voyons comment remplacer le modèle OOP dans JavaScript, qui est la POO basée sur le prototype que nous recommandons.
Basé sur le prototype
Le concept de base ici est les objets mutables dynamiques. La transformation (conversion complète, y compris non seulement des valeurs, mais aussi des fonctionnalités) est directement liée au langage dynamique. Des objets comme les suivants peuvent stocker indépendamment toutes leurs propriétés (propriétés, méthodes) sans classes.
La copie de code est la suivante:
objet = {a: 10, b: 20, c: 30, méthode: fn};
object.a; // 10
object.c; // 30
object.Method ();
De plus, en raison de la dynamique, ils peuvent facilement changer (ajouter, supprimer, modifier) leurs propres fonctionnalités:
La copie de code est la suivante:
object.Method5 = function () {...}; // Ajouter une nouvelle méthode
object.d = 40; // ajouter un nouvel attribut "d"
supprimer objet.c; // Supprimer l'attribut "с"
object.a = 100; // Modifiez l'attribut "а"
// Le résultat est: objet: {a: 100, b: 20, d: 40, méthode: fn, méthode5: fn};
Autrement dit, lors de l'attribution, si une fonctionnalité n'existe pas, elle est créée et l'affectation est initialisée avec elle, et s'il existe, il est juste mis à jour.
Dans ce cas, la réutilisation du code n'est pas implémentée en étendant la classe (notez que nous n'avons pas dit que la classe ne peut pas être modifiée car il n'y a pas de concept de classe ici), mais est implémenté par des prototypes.
Un prototype est un objet qui est utilisé comme copie primitive d'autres objets, ou si certains objets n'ont pas leurs propres caractéristiques nécessaires, le prototype peut être utilisé comme délégué à ces objets et agir comme un objet auxiliaire.
Basé sur la délégation
Tout objet peut être utilisé comme objet prototype pour un autre objet, car l'objet peut facilement modifier sa dynamique prototype à l'exécution.
Notez que nous envisageons actuellement une introduction plutôt qu'une implémentation concrète, et lorsque nous discutons de la mise en œuvre concrète dans ECMascript, nous verrons certaines de leurs propres caractéristiques.
Exemple (pseudocode):
La copie de code est la suivante:
x = {a: 10, b: 20};
y = {a: 40, c: 50};
y. [[prototype]] = x; // x est le prototype de y
Ya; // 40, ses propres caractéristiques
YC; // 50, c'est aussi sa propre caractéristique
YB; // 20 Get du prototype: yb (non) -> y. [[Prototype]]. B (oui): 20
supprimer ya; // supprimez votre propre "а"
Ya; // 10 Obtenez-le du prototype
z = {a: 100, e: 50}
y. [[prototype]] = z; // modifie le prototype de y en z
Ya; // 100 obtenez du prototype z
ye // 50, également obtenu à partir du prototype z
zq = 200 // ajouter un nouvel attribut au prototype
YQ // La modification s'applique également à Y
Cet exemple montre les fonctions et mécanismes importants du prototype en tant qu'attributs d'objets auxiliaires, tout comme vous avez besoin de vos propres attributs. Par rapport à vos propres attributs, ces attributs sont des attributs délégués. Ce mécanisme est appelé délégué, et le modèle de prototype basé sur lui est un prototype de délégué (ou un prototype basé sur le délégué). Le mécanisme de référence est appelé envoyer des informations à un objet. Si l'objet n'obtient pas de réponse, il sera délégué au prototype pour le trouver (l'obligeant à 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 un prototype. Étant donné que tout objet peut être considéré comme un prototype, c'est-à-dire le prototype peut également avoir son propre prototype. Ces prototypes sont connectés ensemble pour former une chaîne dite prototype. Les chaînes sont également hiérarchiques dans les classes statiques, mais elles peuvent être facilement réorganisées, modifiant les hiérarchies et les structures.
La copie de code est la suivante:
x = {a: 10}
y = {b: 20}
y. [[prototype]] = x
z = {c: 30}
z. [[prototype]] = y
za // 10
// za trouvé dans la chaîne prototype:
// za (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 messages, l'objet peut activer le signal système correspondant, qui peut être traité 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: #DoOSNOTERNERSTAND dans smalltalk, method_missing in ruby; __getatTr__ en python, __ calent en php; et __nosuchMethod__ Implémentation dans ECMAScript, etc.
Exemple (implémentation ECMascript de SpiderMonkey):
La copie de code est la suivante:
var objet = {
// attraper le signal système qui ne peut pas répondre aux messages
__NosuchMethod__: fonction (nom, args) {
alert ([nom, args]);
if (name == 'test') {
return '.test () La méthode est gérée';
}
return délégué [nom] .Apply (this, args);
}
};
var delegate = {
carré: fonction (a) {
retourner a * a;
}
};
alert (object.square (10)); // 100
alert (object.test ()); // .Test () est géré
C'est-à-dire, sur la base de la mise en œuvre des classes statiques, lorsque le message ne peut pas être répondu, 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 prototype, vous pouvez toujours obtenir le résultat, ou l'objet a la caractéristique après une série de modifications.
En ce qui concerne ECMAScript, l'implémentation spécifique est: l'utilisation d'un prototype basé sur les délégués. Cependant, comme nous le verrons sur les spécifications et les implémentations, ils ont également leurs propres caractéristiques.
Modèle concaténatif
Honnêtement, il est nécessaire de dire quelque chose sur une autre situation (non utilisé dans ECMAScript dès que possible): cette situation lorsque le prototype est complexe des autres objets au remplacement d'origine des objets natifs. Dans ce cas, la réutilisation du code est une vraie copie (clone) d'un objet plutôt qu'un délégué pendant l'étape de création d'objet. Ce prototype est appelé prototype concatenatif. La copie des propriétés de tous les prototypes d'un objet peut encore modifier complètement ses propriétés et ses méthodes, et en tant que prototype, vous pouvez également vous changer (dans un modèle basé sur le délégué, ce changement ne changera pas le comportement des objets existants, mais changera ses caractéristiques prototypes). L'avantage de cette méthode est qu'il peut réduire le temps de planification et de déléguer, tandis que l'inconvénient est qu'il utilise la mémoire.
Type de canard
Retour à l'objet de changement de type dynamiquement faible, par rapport au modèle basé sur la classe statique, il est nécessaire de vérifier s'il peut faire ces choses et quel type (classe) l'objet a, mais s'il peut être lié au message correspondant (c'est-à-dire s'il a la capacité de le faire après la vérification est nécessaire).
Par exemple:
La copie de code est la suivante:
// dans un modèle statique
if (instance d'objet Of SomeClass) {
// Certains comportements fonctionnent
}
// dans l'implémentation dynamique
// Peu importe le type d'objet pour le moment
// Parce que les mutations, les types et les caractéristiques peuvent être modifiés librement.
// si des objets importants peuvent répondre aux messages de test
if (isfunction (object.test)) // ecmascript
si object.respond_to? (: test) // rubis
Si Hasattr (objet, «test»): // python
C'est ce qu'on appelle le type de quai. Autrement dit, les objets peuvent être identifiés par leurs propres caractéristiques lors de la vérification, plutôt que par l'emplacement des objets dans la hiérarchie ou ils appartiennent à un type spécifique.
Concepts clés basés sur les prototypes
Jetons un coup d'œil aux principales caractéristiques de cette méthode:
1. Le concept de base est l'objet
2. Les objets sont complètement dynamiques et mutables (en théorie, il peut être complètement transformé d'un type à un autre)
3. Les objets n'ont pas de classes strictes qui décrivent leur propre structure et leur propre comportement, et les objets n'ont pas besoin de classes.
4. Les objets n'ont pas de classes de classe mais peuvent avoir des prototypes. S'ils ne peuvent pas répondre aux messages, ils peuvent déléguer aux prototypes.
5. Le prototype de l'objet peut être modifié à tout moment pendant l'exécution;
6. Dans un modèle basé sur le délégué, 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 la copie d'origine clonée à partir d'autres objets et devient en outre une copie complètement indépendante. La transformation des caractéristiques du prototype n'affectera pas l'objet cloné.
8. Si le message ne peut pas être répondu, son appelant peut prendre des mesures supplémentaires (par exemple, modifier la planification)
9. La défaillance de l'objet 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 différence "Classe VS prototype" montrée dans l'exemple ci-dessus n'est pas si importante dans ce modèle basé sur la classe dynamique (surtout si la chaîne prototype est inchangée, il est toujours nécessaire d'envisager une classe statique pour une distinction plus précise). Par exemple, il peut également être utilisé dans Python ou Ruby (ou d'autres langues similaires). Ces langues utilisent toutes des paradigmes dynamiques basés sur des classes. Cependant, dans certains aspects, nous pouvons voir certaines fonctions implémentées en fonction des prototypes.
Dans l'exemple suivant, nous pouvons voir que simplement sur la base du prototype du délégué, nous pouvons amplifier une classe (prototype), affectant ainsi tous les objets liés à cette classe, nous pouvons également modifier dynamiquement la classe de cet objet à l'exécution (en fournissant un nouvel objet pour le délégué), etc.
La copie de code est la suivante:
# Python
classe A (objet):
def __init __ (self, a):
self.a = a
Def Square (soi):
retourner self.a * self.a
a = a (10) # Créez une instance
Imprimer (aa) # 10
Ab = 20 # Fournir une nouvelle propriété pour la classe
Imprimer (AB) # 20 Vous pouvez y accéder dans l'instance "A"
A = 30 # Créez une propriété à part
Imprimer (AB) # 30
del ab # supprimer ses propres attributs
Imprimer (AB) # 20 - Obtenez-le à nouveau de la classe (prototype)
# Comme un modèle basé sur un prototype
# Peut modifier le prototype d'un objet lors de l'exécution
classe B (objet): # Classe vide B
passer
b = b () # b instance
b .__ classe__ = une classe de changement dynamiquement (prototype)
ba = 10 # Créer un nouvel attribut
imprimer (b.square ()) # 100 - la méthode de classe A est disponible pour le moment
# Peut afficher des références sur les classes supprimées
Del A
del B
# Mais l'objet a toujours des références implicites, et ces méthodes sont toujours disponibles
imprimer (b.square ()) # 100
# Mais vous ne pouvez pas changer la classe pour le moment
# C'est la fonctionnalité de l'implémentation
B .__ Class__ = Erreur de dict #
L'implémentation dans Ruby est similaire: elle utilise également des classes complètement dynamiques (en passant, dans la version actuelle de Python, par rapport à Ruby et Ecmascript, amplifier les classes (prototypes) ne peut pas être effectuée), nous pouvons complètement modifier les caractéristiques d'un objet (ou classe) (Ajouter des méthodes / attributs à la classe, et ces changements affecteront les objets existants), mais il ne peut pas modifier dynamiquement la classe d'un objet.
Cependant, cet article n'est pas spécifiquement pour Python et Ruby, donc nous ne dirons pas grand-chose, continuons à discuter d'Ecmascript lui-même.
Mais avant cela, nous devons regarder le "sucre synthétique" qui se trouve dans certains OOPS, car de nombreux articles précédents sur JavaScript couvrent souvent ces problèmes.
La seule mauvaise phrase qui doit être notée dans cette section est: "JavaScript n'est pas une classe, il a un prototype et peut remplacer une classe." Il est très nécessaire de savoir que toutes les implémentations basées sur les classes ne sont pas complètement différentes. Même si nous pouvons dire "JavaScript est différent", il est également nécessaire de considérer (en plus du concept de "classes") qu'il existe d'autres fonctionnalités connexes.
Autres caractéristiques de diverses implémentations OOP
Dans cette section, nous introduisons brièvement d'autres fonctionnalités et diverses implémentations OOP sur la réutilisation du code, y compris les implémentations OOP dans ECMAScript. La raison en est que l'apparence précédente de la mise en œuvre de la POO en JavaScript a des restrictions de réflexion habituelles. La seule exigence principale est qu'elle soit prouvée techniquement et idéologiquement. On ne peut pas dire que vous n'avez pas découvert la fonction de sucre de syntaxe dans d'autres implémentations OOP, et vous n'avez aucune idée que JavaScript n'est pas une langue pure OOP, ce qui est faux.
Polymorphe
Dans ECMAScript, il existe plusieurs polymorphismes dans lesquels les objets signifient.
Par exemple, une fonction peut être appliquée à différents objets, tout comme les propriétés d'un objet natif (car cette valeur est déterminée lors de la saisie du contexte d'exécution):
La copie de code est la suivante:
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;
var b = 2;
test(); // 1, 2
Cependant, il existe des exceptions: date.prototype.getTime (), selon la norme, il devrait toujours y avoir un objet de date, sinon une exception sera lancée.
La copie de code est la suivante:
alert (date.prototype.getTime.Call (new Date ())); // temps
alert (date.prototype.getTime.Call (new String (''))); // TypeError
Le polymorphisme dit des paramètres lors de la définition d'une fonction équivaut à tous les types de données, mais il n'accepte que des paramètres polymorphes (tels que la méthode de tri. Soit dit en passant, l'exemple ci-dessus peut également être considéré comme un polymorphisme de paramètre.
La méthode du prototype peut être définie comme vide, et tous les objets créés doivent être redéfinis (implémentés) la méthode (c'est-à-dire "une interface (signature), plusieurs implémentations").
Le polymorphisme est lié au type de canard que nous avons mentionné ci-dessus: c'est-à-dire que le type de l'objet et sa position dans la hiérarchie ne sont pas si importants, mais il peut être facilement accepté s'il a toutes les caractéristiques nécessaires (c'est-à-dire que l'interface générale est importante, et l'implémentation peut varier).
Emballer
Il y a souvent de mauvaises opinions sur l'encapsulation. Dans cette section, nous discuterons de certains sucres syntaxiques dans les implémentations de la POO - c'est-à-dire des modificateurs bien connus: dans ce cas, nous discuterons de certaines «sucres» pratiques - Modificateurs bien connus: privé, protégé et public (ou les modificateurs d'accès des objets).
Ici, je voudrais vous rappeler l'objectif principal de l'encapsulation: l'encapsulation est un ajout abstrait, plutôt que de sélectionner un "hacker malveillant" caché qui écrit quelque chose directement dans votre classe.
C'est une grosse erreur: utilisez Hidden pour vous cacher.
Les niveaux d'accès (privés, protégés et publics), pour la commodité de la programmation, ont été mis en œuvre dans de nombreux objets (vraiment très pratique de sucre de syntaxe), et décrivent et construisent le système plus abstrait.
Ceux-ci peuvent être vus dans certaines implémentations (comme Python et Ruby déjà mentionnés). D'une part (en python), ces propriétés _private _private (via la spécification du nom de soulignement) ne sont pas accessibles de l'extérieur. Python, en revanche, est accessible depuis l'extérieur par le biais de règles spéciales (_classname__field_name).
La copie de code est la suivante:
classe A (objet):
def __init __ (soi):
self.public = 10
Self .__ Private = 20
def get_private (self):
retour à soi .__ Private
# dehors:
a = a () # Exemple de a
imprimer (a.public) # ok, 30
print (a.get_private ()) # ok, 20
imprimer (un .__ privé) # a échoué car il ne peut être disponible que dans un
# Mais en python, les règles spéciales sont accessibles
print (a._a__private) # ok, 20
Dans Ruby: D'une part, il a la capacité de définir les caractéristiques du privé et protégé, et d'autre part, il existe également des méthodes spéciales (telles que instance_variable_get, instance_variable_set, envoy, etc.) pour obtenir des données encapsulées.
La copie de code est la suivante:
classe A
définir l'initialisation
@A = 10
fin
def public_method
private_method (20)
fin
Privé
def private_method (b)
Retour @A + B
fin
fin
a = a.new # nouvelle instance
a.public_method # ok, 30
AA # a échoué, @A - est une variable d'instance privée
# "private_method" est privé et ne peut être accessible que dans la classe A
A.private_method # Erreur
# Mais il existe un nom de méthode de métadonnées spéciaux qui peut obtenir les données
A.Send (: private_method, 20) # ok, 30
a.instance_variable_get (: @ a) # ok, 10
La raison principale est l'encapsulation (notez que je n'utilise pas les données "cachées") que le programmeur veut obtenir. Si ces données sont modifiées de manière incorrecte d'une manière ou d'une autre ou qu'il y a des erreurs, la responsabilité entière est le programmeur, mais pas simplement "SPELOW" ou "modifier certains champs avec désinvolture". Mais si cela se produit fréquemment, c'est une mauvaise habitude et un style de programmation, car c'est généralement une API courante pour "parler" avec des objets.
Répéter, l'objectif de base de l'encapsulation est de l'abstraire de l'utilisateur des données auxiliaires, plutôt que d'empêcher les pirates de cacher des données. Plus grave, l'encapsulation n'est pas d'utiliser privé pour modifier les données pour atteindre l'objectif de la sécurité des logiciels.
Encapsulation des objets d'assistance (locale), nous offrons la faisabilité des changements de comportement des interfaces publiques avec un coût minimal, une localisation et des changements prédictifs, ce qui est exactement le but de l'encapsulation.
De plus, l'objectif important de la méthode du secteur est de résumer les calculs complexes. Par exemple, élément.innerhtml setter - instruction abstraite - "Le HTML à l'intérieur de cet élément est maintenant le suivant", et la fonction de setter dans l'attribut innerHTML sera difficile à calculer et à vérifier. Dans ce cas, le problème implique principalement l'abstraction, mais l'encapsulation peut se produire.
Le concept d'encapsulation n'est pas seulement lié à la POO. Par exemple, il pourrait s'agir d'une fonction simple qui résume toutes sortes de calculs afin qu'il soit abstrait (pas besoin de faire savoir à l'utilisateur, par exemple comment la fonction Math.Round (......) est implémentée, l'utilisateur l'appelle simplement). C'est une encapsulation, notez que je n'ai pas dit que c'était "privé, protégé et public".
La version actuelle de la spécification ECMAScript ne définit pas les modificateurs privés, protégés et publics.
Cependant, dans la pratique, il est possible de voir quelque chose nommé "imiter l'encapsulation JS". Généralement, le but de ce contexte est à utiliser (en règle générale, le constructeur lui-même). Malheureusement, la mise en œuvre souvent de cette «imitation» peut être effectuée, et les programmeurs peuvent produire des entités pseudo-absouillées non abstraites pour définir la «méthode Getter / Setter» (je le répète, c'est faux):
La copie de code est la suivante:
fonction a () {
var _a; // "privé" un
this.geta = fonction _geta () {
retour _a;
};
this.seta = fonction _seta (a) {
_a = a;
};
}
var a = new a ();
a.seta (10);
alert (a._a); // indéfini, "privé"
alerte (a.geta ()); // 10
Par conséquent, tout le monde comprend que pour chaque objet créé, la méthode GETA / SETA est également créée, ce qui est également la raison de l'augmentation de la mémoire (par rapport à la définition du prototype). Bien que, en théorie, l'objet puisse être optimisé dans le premier cas.
De plus, certains articles JavaScript mentionnent souvent le concept de «méthode privée». Remarque: la norme ECMA-262-3 ne définit aucun concept de "méthode privée".
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 (dans certaines conditions du constructeur, certains objets peuvent obtenir des méthodes supplémentaires, tandis que d'autres ne le peuvent pas).
De plus, en JavaScript, si l'encapsulation est toujours mal interprétée comme un moyen d'empêcher automatiquement les pirates malveillants d'écrire certaines valeurs à la place de la méthode du secteur, les soi-disant "cachés" et "privés" ne sont pas "cachés". Certaines implémentations peuvent obtenir des valeurs sur la chaîne de portée pertinente (et tous les objets variables correspondants) en appelant le contexte sur la fonction EVAL (qui peut être testé sur SpiderMonkey 1.7).
La copie de code est la suivante:
eval ('_ a = 100', a.geta); // ou a.seta, parce que "_a" méthodes sur [[SPOPE]]
a.geta (); // 100
Alternativement, dans l'implémentation, permettez un accès direct à l'objet actif (comme Rhino), en accédant aux propriétés correspondantes de l'objet, la valeur de la variable interne peut être modifiée:
La copie de code est la suivante:
// rhinocéros
var foo = (function () {
var x = 10; // "privé"
return function () {
imprimer (x);
};
}) ();
foo (); // 10
foo .__ parent __. x = 20;
foo (); // 20
Parfois, en JavaScript, le but des données «privées» et «protégées» est obtenue en soulignant les variables (mais par rapport à Python, ce n'est qu'une spécification de dénomination):
var _myprivatedata = 'testString';
Il est souvent utilisé pour entourer le contexte d'exécution avec des supports, mais pour les données auxiliaires réelles, elle n'a aucune association directe avec les objets, et il est tout simplement pratique de résumer des API externes:
La copie de code est la suivante:
(fonction () {
// initialiser le contexte
}) ();
Héritage multiple
La multi-inhérence est un sucre syntaxique très pratique pour les améliorations de réutilisation du code (si nous pouvons hériter d'une classe à la fois, pourquoi ne pouvons-nous pas hériter 10 à la fois?). Cependant, en raison de certaines lacunes en héritage multiple, il n'est pas devenu populaire dans la mise en œuvre.
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 les langages d'auto-programmation de ses ancêtres aient de telles capacités. Mais dans certaines implémentations (telles que SpiderMonkey), l'utilisation de __NosuchMethod__ peut être utilisée pour gérer la planification et le délégué pour remplacer les chaînes prototypes.
Mixins
Mixins est un moyen pratique de réutiliser le code. Les mélanges ont été recommandés en remplacement de l'héritage multiple. Ces éléments indépendants peuvent tous être mélangés avec n'importe quel objet pour étendre leur fonctionnalité (de sorte que les objets peuvent également mélanger plusieurs mélanges). La spécification ECMA-262-3 ne définit pas le concept de "mixins", mais selon la définition de mixins et ECMascript a des objets mutables dynamiques, il n'y a donc aucun obstacle à l'élargissement des fonctionnalités à l'aide de mixins.
Exemples typiques:
La copie de code est la suivante:
// assistant pour l'augmentation
Object.extend = fonction (destination, source) {
pour (propriété dans source) if (source.hasownproperty (propriété)) {
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 en x
alert ([xa, xb, xc, xd]); 10, 20, 30, 40
Veuillez noter que j'ai pris ces définitions ("mixin", "mix") dans les citations mentionnées dans ECMA-262-3. Il n'y a pas un tel concept dans la spécification, et ce n'est pas un mélange mais un moyen couramment utilisé pour étendre les objets via de nouvelles fonctionnalités. (Le concept de mélanges dans Ruby est officiellement défini. Le mixin crée une référence au module au lieu de simplement copier tous les attributs du module à un autre module - en fait: créer un objet supplémentaire (prototype) pour le délégué).
Caractéristiques
Les traits et les mixins ont des concepts similaires, mais il a de nombreuses fonctions (par définition, car les mélanges peuvent être appliqués afin qu'il ne puisse pas contenir d'états car il a le potentiel de provoquer des conflits de dénomination). Selon ECMascript, les traits et les mixins suivent les mêmes principes, de sorte que la spécification ne définit pas le concept de "traits".
interface
Les interfaces implémentées dans certains OOP sont similaires aux mélanges et aux traits. Cependant, par rapport aux mixins et aux traits, une interface applique la classe d'implémentation pour implémenter son comportement de signature de méthode.
Les interfaces peuvent être considérées comme des classes abstraites. Cependant, par rapport aux classes abstraites (les méthodes dans les classes abstraites ne peuvent mettre en œuvre une partie d'entre elles, et l'autre partie est toujours définie comme des signatures), l'héritage ne peut être qu'une seule classe de base d'héritage, mais il peut hériter de plusieurs interfaces. C'est pourquoi les interfaces (multiples mixtes) peuvent être considérées comme une alternative à l'héritage multiple.
La norme ECMA-262-3 ne définit pas le concept d '"interface" ni le concept de "classe abstraite". Cependant, en tant qu'imitation, il peut être implémenté par un objet qui "vide" (ou une exception jetée dans une méthode vide, indiquant au développeur que cette méthode doit être implémentée).
Combinaison d'objets
La combinaison d'objets est également l'une des techniques de réutilisation du code dynamique. Les combinaisons d'objets diffèrent d'un héritage à haute flexibilité, ce qui implémente un délégué dynamique et variable. Et cela est également basé sur le prototype principal. En plus des prototypes mutables dynamiques, l'objet peut être un objet agrégé du délégué (créer une combinaison en conséquence - l'agrégation), et envoyer davantage des messages à l'objet et au délégué au délégué. Cela peut être plus de deux délégués, car sa nature dynamique détermine qu'elle peut être modifiée au moment de l'exécution.
L'exemple __nosuchMethod__ mentionné est comme celui-ci, mais nous permet également de montrer comment utiliser explicitement les délégués:
Par exemple:
La copie de code est la suivante:
var _delegate = {
foo: function () {
alert ('_ delegate.foo');
}
};
var agrégat = {
délégué: _delegate,
foo: function () {
Renvoie ce.delegate.foo.call (this);
}
};
agrégat.foo (); // Delegate.foo
agrégate.delegate = {
foo: function () {
alert ('foo du nouveau délégué');
}
};
agrégat.foo (); // foo du nouveau délégué
Cette relation d'objet est appelée "has-a", tandis que l'intégration est la relation de "IS-A".
En raison du manque de combinaisons d'affichage (flexibilité par rapport à l'héritage), il est également acceptable d'ajouter du code intermédiaire.
Caractéristiques AOP
En tant que fonction orientée vers l'aspect, les décorateurs de fonction peuvent être utilisés. La spécification ECMA-262-3 n'a pas de concept clairement défini de "décorateurs de fonction" (par opposition à Python, le mot est officiellement défini dans Python). Cependant, les fonctions avec des paramètres fonctionnels sont décoratives et activées à certains égards (en appliquant des suggestions dites):
L'exemple le plus simple d'un décorateur:
La copie de code est la suivante:
Fonction CheckDecorator (originalFunction) {
return function () {
if (foobar! = 'test') {
alert («mauvais paramètre»);
retourne false;
}
return originalFunction ();
};
}
fonction test () {
alert («fonction de test»);
}
var testwithcheck = checkDecorator (test);
var foobar = false;
test(); // "Fonction de test"
TestWithCheck (); // 'Mauvais paramètre'
foobar = 'test';
test(); // "Fonction de test"
TestWithCheck (); // "Fonction de test"
en conclusion
Dans cet article, nous avons trié l'introduction à la POO (j'espère que ces informations vous ont été utiles), et dans le chapitre suivant, nous continuerons à implémenter ECMAScript dans la programmation orientée objet.