Maison >interface Web >js tutoriel >Compréhension approfondie de la série JavaScript (19) : Explication détaillée de la stratégie d'évaluation_Connaissances de base

Compréhension approfondie de la série JavaScript (19) : Explication détaillée de la stratégie d'évaluation_Connaissances de base

WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWB
WBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBOYWBoriginal
2016-05-16 16:11:091029parcourir

Présentation

Dans ce chapitre, nous expliquerons la stratégie de transmission de paramètres aux fonctions dans ECMAScript.

En informatique, cette stratégie est généralement appelée « stratégie d'évaluation » (NDLR : Certains disent que cela se traduit par stratégie d'évaluation, et d'autres le traduisent par stratégie d'affectation. En regardant le contenu suivant, je pense que cela s'appelle La stratégie d'affectation est plus appropriée (de toute façon, le titre doit être rédigé comme une stratégie d'évaluation facile à comprendre pour tout le monde), comme la définition de règles pour évaluer ou calculer des expressions dans un langage de programmation. La stratégie consistant à passer des arguments aux fonctions est un cas particulier.

http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/
La raison pour laquelle nous avons écrit cet article est que quelqu'un sur le forum a demandé une explication précise de certaines stratégies de passage de paramètres. Nous avons donné ici les définitions correspondantes, dans l'espoir d'être utiles à tout le monde.

De nombreux programmeurs sont convaincus qu'en JavaScript (et même dans certains autres langages), les objets sont passés par référence, tandis que les types de valeurs primitifs sont passés par valeur. De plus, de nombreux articles mentionnent ce "fait", mais beaucoup de gens le font vraiment. comprenez cette terminologie, et dans quelle mesure est-elle correcte ? Nous l'expliquerons un par un dans cet article.

Théorie générale

Il convient de noter qu'il existe généralement deux stratégies d'affectation dans la théorie de l'affectation : stricte, ce qui signifie que les paramètres sont calculés avant d'entrer dans le programme ; non stricte, ce qui signifie que les paramètres sont calculés en fonction des exigences de calcul (c'est-à-dire le calcul. est équivalent à un calcul différé).

Ensuite, nous considérons ici la stratégie de base de passage des paramètres de fonction, qui est très importante du point de départ d'ECMAScript. La première chose à noter est qu'ECMAScript (et même d'autres langages tels que C, JAVA, Python et Ruby) utilise une stratégie stricte de passage de paramètres.

De plus, l'ordre de calcul des paramètres passés est également très important - dans ECMAScript, il est de gauche à droite, et l'ordre de réflexion (de droite) implémenté dans d'autres langages​​peut également être utilisé.

La stratégie de passage strict de paramètres est également divisée en plusieurs sous-stratégies, dont les plus importantes sont abordées en détail dans ce chapitre.

Toutes les stratégies décrites ci-dessous ne sont pas utilisées dans ECMAScript, donc lorsque nous discutons du comportement spécifique de ces stratégies, nous utilisons du pseudocode pour les démontrer.

Passer par valeur

Passez par valeur, comme de nombreux développeurs le savent très bien. La valeur d'un paramètre est une copie de la valeur de l'objet transmis par l'appelant. Changer la valeur du paramètre à l'intérieur de la fonction n'affectera pas l'objet externe (le. paramètre est dans Valeur externe), d'une manière générale, la nouvelle mémoire est réaffectée (on ne fait pas attention à la façon dont la mémoire allouée est implémentée - c'est aussi une pile ou une allocation de mémoire dynamique), la valeur du nouveau bloc mémoire est une copie de l'objet externe et sa valeur est utilisée à l'intérieur de la fonction.

Copier le code Le code est le suivant :

barre = 10

procédure foo(barArg):
barArg = 20;
fin

foo(bar)

// Changer la valeur à l'intérieur de foo n'affectera pas la valeur de la barre interne
imprimer(barre) // 10

Cependant, si le paramètre de la fonction n'est pas une valeur primitive mais un objet de structure complexe, cela entraînera un gros problème de performances lorsque la structure est transmise à la fonction en tant que valeur - c'est une copie complète.

Donnons un exemple général et utilisons la stratégie d'affectation suivante pour le tester. Pensez à une fonction qui accepte 2 paramètres. Le premier paramètre est la valeur de l'objet et le deuxième paramètre est une marque booléenne. l'objet est complètement modifié (réaffecté une valeur à l'objet), ou seules certaines propriétés de l'objet sont modifiées.

Copier le code Le code est le suivant :

// Remarque : les éléments suivants sont du pseudocode, pas une implémentation JS
barre = {
x : 10,
y : 20
>

procédure foo(barArg, isFullChange):

si isFullChange :
barArg = {z : 1, q : 2}
sortir
fin

barArg.x = 100
barArg.y = 200

fin

foo(bar)

// Passage par valeur, les objets externes ne sont pas modifiés
print(barre) // {x : 10, y : 20}

// Changer complètement l'objet (attribuer une nouvelle valeur)
foo(bar, vrai)

//Pas de changement non plus
print(bar) // {x : 10, y : 20}, au lieu de {z : 1, q : 2}

Passez par référence

Une autre méthode de passage par référence bien connue ne reçoit pas une copie de la valeur, mais une référence implicite à l'objet, telle que l'adresse de référence externe directe de l'objet. Toute modification des paramètres à l'intérieur de la fonction affectera la valeur de l'objet en dehors de la fonction, car les deux font référence au même objet, c'est-à-dire : à ce moment, le paramètre est équivalent à un alias de l'objet externe.

Pseudo-code :

Copier le code Le code est le suivant :

procédure foo(barArg, isFullChange):

si isFullChange :
barArg = {z : 1, q : 2}
sortir
fin

barArg.x = 100
barArg.y = 200

fin

//Utilisez le même objet que ci-dessus
barre = {
x : 10,
y : 20
>

// Le résultat de l'appel par référence est le suivant :
foo(bar)

// La valeur de l'attribut de l'objet a été modifiée
print(barre) // {x : 100, y : 200}

// La réaffectation de nouvelles valeurs affecte également l'objet
foo(bar, vrai)

// L'objet est désormais un nouvel objet
print(barre) // {z : 1, q : 2}

Cette stratégie permet une livraison plus efficace d'objets complexes, tels que de grands objets structurels avec de grands lots de propriétés.

Appelez en partageant
Tout le monde connaît les deux stratégies ci-dessus, mais la stratégie dont je veux parler ici n'est peut-être pas bien comprise par tout le monde (en fait, c'est une stratégie académique). Cependant, nous verrons bientôt que c'est exactement la stratégie dans laquelle il joue un rôle clé dans les stratégies de passage de paramètres dans ECMAScript.

Il existe également quelques synonymes pour cette stratégie : "passer par objet" ou "passer par partage d'objet".

Cette stratégie a été proposée par Barbara Liskov en 1974 pour le langage de programmation CLU.

Le point clé de cette stratégie est le suivant : la fonction reçoit une copie (copie) de l'objet, et la copie de référence est associée aux paramètres formels et à leurs valeurs.

Nous ne pouvons pas appeler la référence apparaissant ici « passer par référence » car le paramètre reçu par la fonction n'est pas un alias d'objet direct, mais une copie de l'adresse de référence.

La différence la plus importante est : la réaffectation d'une nouvelle valeur au paramètre à l'intérieur de la fonction n'affectera pas l'objet externe (similaire au cas du passage par référence dans l'exemple ci-dessus), mais comme le paramètre est une copie d'adresse, il n'est pas accessible à l'extérieur et à l'intérieur. Le même objet est accessible (par exemple, l'objet externe n'est pas une copie complète comme le passage par valeur), et la modification de la valeur de l'attribut de l'objet paramètre affectera l'objet externe.

Copier le code Le code est le suivant :

procédure foo(barArg, isFullChange):

si isFullChange :
barArg = {z : 1, q : 2}
sortir
fin

barArg.x = 100
barArg.y = 200

fin

//Toujours utiliser cette structure d'objet
barre = {
x : 10,
y : 20
>

// Le passage par contribution affectera l'objet
foo(bar)

// Les propriétés de l'objet ont été modifiées
print(barre) // {x : 100, y : 200}

// La réaffectation n'a aucun effet
foo(bar, vrai)

// Toujours la valeur ci-dessus
print(barre) // {x : 100, y : 200}


Ce traitement suppose que les objets sont utilisés dans la plupart des langages, plutôt que des valeurs primitives.

Le passage par partage est un cas particulier de passage par valeur

La stratégie pass-by-share est utilisée dans de nombreux langages : Java, ECMAScript, Python, Ruby, Visual Basic, etc. De plus, la communauté Python a adopté cette terminologie, et d'autres langages peuvent également utiliser cette terminologie, car d'autres noms ont tendance à prêter à confusion. Dans la plupart des cas, comme dans Java, ECMAScript ou Visual Basic, cette stratégie est également appelée passage par valeur, ce qui signifie : copie spéciale de référence de valeur.

D'une part, c'est comme ça - le paramètre passé à la fonction n'est qu'un nom de la valeur liée (adresse de référence) et n'affectera pas l'objet externe.

D'un autre côté, ces termes sont vraiment considérés comme faux sans creuser plus profondément, car de nombreux forums parlent de la façon de passer des objets aux fonctions JavaScript).

La théorie générale dit qu'elle est transmise par valeur : mais à l'heure actuelle, la valeur est ce que nous appelons une copie d'adresse (copie), elle n'enfreint donc pas les règles.

En Ruby, cette stratégie est appelée passage par référence. Encore une fois : il n'est pas transmis comme une copie de la grande structure (c'est-à-dire pas transmis par valeur), et d'un autre côté, nous n'avons pas affaire à une référence à l'objet original, et ne pouvons donc pas le modifier ; -le concept de terme peut causer plus de problèmes.

En théorie, il n'existe pas de cas particulier de passage par référence comme le cas particulier de passage par valeur.

Mais il faut encore comprendre que dans les technologies mentionnées ci-dessus (Java, ECMAScript, Python, Ruby, autres), en fait - la stratégie qu'elles utilisent est le passage par partage.

Appuyez sur Partager et pointeur

Pour С/С, cette stratégie est idéologiquement la même que le passage par pointeur par valeur, mais avec une différence importante : cette stratégie peut déréférencer le pointeur ainsi que muter complètement l'objet. Mais en général, un pointeur de valeur (adresse) est alloué à un nouveau bloc mémoire (c'est-à-dire que le bloc mémoire précédemment référencé reste inchangé) ; la modification des attributs de l'objet via le pointeur affectera l'objet externe Adon ;

Donc, et la catégorie pointeur, on peut évidemment voir que celle-ci est transmise par valeur d'adresse. Dans ce cas, le pass-by-share n'est qu'un "sucre syntaxique" qui se comporte comme une affectation de pointeur (mais ne peut pas déréférencer), ou modifie une propriété comme une référence (aucune opération de déréférencement requise). Parfois, il peut être nommé "Pointeurs sécurisés". ".

Cependant, С/С a également un sucre de syntaxe spécial lors du référencement des propriétés d'un objet sans déréférencement explicite du pointeur :

Copier le code Le code est le suivant :

obj->x au lieu de (*obj).x

Cette idéologie est la plus étroitement associée au C et peut être vue dans l'implémentation de "pointeurs intelligents", par exemple dans boost::shared_ptr, qui surcharge l'opérateur d'affectation et le constructeur de copie et utilise également le compteur de références de l'objet, supprime les objets via. CG. Ce type de données a même un nom similaire : shared_ptr.

Implémentation ECMAScript

Nous connaissons maintenant la stratégie de passage d'objets en tant que paramètres dans ECMAScript - passage par partage : la modification des propriétés du paramètre affectera l'extérieur, mais la réaffectation n'affectera pas l'objet externe. Cependant, comme nous l'avons mentionné ci-dessus, les développeurs ECMAScript parmi eux l'appellent généralement : passer par valeur, sauf que la valeur est une copie de l'adresse de référence.

L'inventeur de JavaScript, Brendan Ash, a également écrit : Ce qui est transmis est une copie de la référence (copie de l'adresse). Donc, ce que tout le monde sur le forum a dit à propos du passage par valeur est également correct selon cette explication.

Plus précisément, ce comportement peut être compris comme une simple affectation. Nous pouvons voir qu'à l'intérieur se trouve un objet complètement différent, mais il fait référence à la même valeur, c'est-à-dire une copie de l'adresse.

Code ECMAScript :

Copier le code Le code est le suivant :

var foo = {x : 10, y : 20};
var bar = foo;

alert(bar === foo); // vrai

bar.x = 100;
bar.y = 200;

alert([foo.x, foo.y]); // [100, 200]

Autrement dit, deux identifiants (liaison de noms) sont liés au même objet en mémoire et partagent cet objet :

valeur foo : addr(0xFF) => {x : 100, y : 200} (adresse 0xFF) <= valeur de la barre : addr(0xFF)
Avec la réaffectation, la liaison se fait vers un nouvel identifiant d'objet (nouvelle adresse) sans affecter les objets précédemment liés :

Copier le code Le code est le suivant :

barre = {z : 1, q : 2};

alert([foo.x, foo.y]); // [100, 200] – inchangé
alert([bar.z, bar.q]); // [1, 2] – mais faisant maintenant référence à un nouvel objet

Autrement dit, maintenant foo et bar ont des valeurs et des adresses différentes :
Copier le code Le code est le suivant :

valeur foo : addr(0xFF) => {x : 100, y : 200} (adresse 0xFF)
valeur de la barre : addr(0xFA) => {z : 1, q : 2} (adresse 0xFA)

Permettez-moi de souligner encore une fois que la valeur de l'objet mentionné ici est l'adresse (adresse), et non la structure de l'objet elle-même. L'attribution d'une variable à une autre variable est une référence à la valeur attribuée. Les deux variables font donc référence à la même adresse mémoire. L'affectation suivante est une nouvelle adresse, qui résout la liaison d'adresse à l'ancien objet, puis la lie à l'adresse du nouvel objet. C'est la différence la plus importante par rapport au passage par référence.

De plus, si l'on considère uniquement le niveau d'abstraction fourni par la norme ECMA-262, tout ce que l'on voit dans l'algorithme c'est la notion de « valeur », et la « valeur » transmise par l'implémentation (peut être une valeur primitive ou un objet), mais selon notre définition ci-dessus, on peut aussi l'appeler "passer par valeur" car l'adresse de référence est aussi une valeur.

Cependant, afin d'éviter tout malentendu (pourquoi les propriétés des objets externes peuvent être modifiées à l'intérieur de la fonction), il reste encore des détails au niveau de l'implémentation qui doivent être pris en compte ici - ce que nous considérons comme un passage par partage, ou dans d'autres mots - en passant par un pointeur sûr, et il est impossible pour un pointeur sûr de déréférencer et de modifier l'objet, mais il peut modifier la valeur d'attribut de l'objet.

Version à terme

Définissons le terme version de cette stratégie dans ECMAScript.

Cela peut être appelé "passage par valeur" - la valeur mentionnée ici est un cas particulier, c'est-à-dire que la valeur est une copie d'adresse. A partir de ce niveau, nous pouvons dire : Tous les objets dans ECMAScript, à l'exception des exceptions, sont transmis par valeur. Il s'agit en fait du niveau abstrait d'ECMAScript.

Ou dans ce cas, cela est spécifiquement appelé "passage par partage". Grâce à cela, vous pouvez voir la différence entre le passage par valeur traditionnel et le passage par référence. Cette situation peut être divisée en 2 situations : 1 : Valeurs originales ​​sont transmis par valeur ; 2 : les objets sont transmis par partage.

La phrase "Convertir un objet en fonction par type référence" n'a rien à voir avec ECMAScript, et elle est fausse.

Conclusion

J'espère que cet article aidera à comprendre plus de détails sur la situation générale et sa mise en œuvre dans ECMAScript. Comme toujours, si vous avez des questions, n'hésitez pas à en discuter.

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