introduire
Dans ce chapitre, nous expliquerons la stratégie de passage des paramètres aux fonctions de fonction dans ECMAScript.
En informatique, cette stratégie est généralement appelée «stratégie d'évaluation» (note de l'oncle: certaines personnes disent qu'elle est traduite par une stratégie d'évaluation, tandis que d'autres la traduisent en stratégie d'affectation. En examinant le contenu suivant, je pense qu'il est plus approprié de l'appeler une stratégie d'affectation. Quoi qu'il en soit, le titre devrait être rédigé comme une stratégie d'évaluation qui est facile à comprendre pour tout le monde). Par exemple, dans les langages de programmation, définissez des règles pour l'évaluation ou les expressions de calcul. Une stratégie pour transmettre des paramètres à une fonction est un cas spécial.
http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/
La raison de la rédaction de cet article est qu'une personne sur le forum a demandé d'expliquer avec précision certaines stratégies pour passer des paramètres. Nous avons donné la définition correspondante ici, en espérant que cela sera utile à tout le monde.
De nombreux programmeurs sont convaincus que dans JavaScript (même dans certains autres langues), les objets sont passés par référence, tandis que le type de valeur d'origine est passé par des valeurs. De plus, de nombreux articles parlent de ce "fait", mais beaucoup de gens comprennent vraiment ce terme, et combien sont corrects? Nous l'expliquerons un par un dans cet article.
Théorie générale
Il convient de noter que dans la théorie des affectations, il existe généralement deux stratégies d'affectation: stricte - signifie que les paramètres sont calculés avant d'entrer dans le programme; Non-stricts - signifie que le calcul des paramètres est calculé en fonction des exigences de calcul (c'est-à-dire qu'elle équivaut à un calcul de retard).
Ensuite, nous considérons ici la stratégie de transfert de paramètres de fonction de base, qui est très importante du point de départ de ECMAScript. La première chose à noter est que des stratégies de passage de paramètres strictes sont utilisées dans ECMAScript (même dans d'autres langues telles que C, Java, Python et Ruby).
De plus, l'ordre de calcul des paramètres adoptés est également très important - dans ECMAScript, il est de gauche à droite, et l'ordre d'introduction (fait de droite à droite) implémenté dans d'autres langues peut également être utilisé.
Des stratégies de transmission strictes sont également divisées en plusieurs stratégies de semences, et nous discuterons en détail des stratégies les plus importantes de ce chapitre.
Toutes les stratégies discutées ci-dessous ne sont pas utilisées dans ECMAScript, donc lorsque vous discutez du comportement spécifique de ces stratégies, nous avons utilisé le pseudo-code pour le montrer.
Passer par valeur
En passant par la valeur, de nombreux développeurs sont bien conscients que la valeur du paramètre est une copie de la valeur de l'objet transmise par l'appelant. La modification de la valeur du paramètre à l'intérieur de la fonction n'affectera pas l'objet extérieur (la valeur du paramètre est à l'extérieur). D'une manière générale, une nouvelle mémoire est réaffectée (nous ne faisons pas attention à la façon dont la mémoire allouée est implémentée - c'est aussi la pile ou l'allocation de mémoire dynamique). La valeur du nouveau bloc de mémoire est une copie de l'objet externe, et sa valeur est utilisée à l'intérieur de la fonction.
La copie de code est la suivante:
bar = 10
Procédure foo (Bararg):
Bararg = 20;
fin
foo (bar)
// modifier la valeur à l'intérieur de Foo n'affectera pas la valeur de la barre interne
Imprimer (bar) // 10
Cependant, si les paramètres de la fonction ne sont pas la valeur d'origine mais un objet structurel complexe, il apportera d'excellents problèmes de performance. C ++ a ce problème. Lorsque vous passez la structure en valeur dans la fonction, il s'agit d'une copie complète.
Donnons un exemple général, utilisons la stratégie d'affectation suivante pour la tester. Pensez à une fonction qui accepte 2 paramètres. Le premier paramètre est la valeur de l'objet, et le second est une marque booléenne pour marquer si l'objet entrant est complètement modifié (réaffectant l'objet à l'objet), ou que certaines propriétés de l'objet sont modifiées.
La copie de code est la suivante:
// Remarque: Voici tous des pseudo-code, pas de l'implémentation JS
bar = {
X: 10,
Y: 20
}
Procédure foo (Bararg, Isfullchange):
Si Isfullchange:
bararg = {z: 1, q: 2}
sortie
fin
bararg.x = 100
bararg.y = 200
fin
foo (bar)
// passe par valeur, les objets externes ne seront pas modifiés
imprimer (bar) // {x: 10, y: 20}
// modifie complètement l'objet (attribuez une nouvelle valeur)
foo (bar, vrai)
// pas de changement non plus
print (bar) // {x: 10, y: 20}, au lieu de {z: 1, q: 2}
Passer par référence
Un autre pass-by-by-référence bien connu n'est pas une copie de valeur, mais une référence implicite à l'objet, comme l'adresse de référence directe de l'objet à l'extérieur. Toute modification des paramètres à l'intérieur de la fonction affecte la valeur de l'objet en dehors de la fonction, car les deux font référence au même objet, c'est-à-dire que le paramètre est équivalent à un alias de l'objet externe.
Pseudocode:
La copie de code est la suivante:
Procédure foo (Bararg, Isfullchange):
Si Isfullchange:
bararg = {z: 1, q: 2}
sortie
fin
bararg.x = 100
bararg.y = 200
fin
// utilise le même objet que ci-dessus
bar = {
X: 10,
Y: 20
}
// Le résultat de l'appel par référence est le suivant:
foo (bar)
// La valeur de la propriété de l'objet a été modifiée
imprimer (bar) // {x: 100, y: 200}
// Réaffectation de nouvelles valeurs affecte également l'objet
foo (bar, vrai)
// Cet objet est déjà un nouvel objet
imprimer (bar) // {z: 1, q: 2}
Cette stratégie peut passer plus efficacement des objets complexes, tels que les objets de grande structure avec de grands lots d'attributs.
Appelez en partageant
Tout le monde connaît les deux stratégies ci-dessus, mais vous ne connaissez peut-être pas la stratégie dont vous souhaitez parler ici (en fait, c'est une stratégie académique). Cependant, nous verrons bientôt que c'est exactement la stratégie qui joue un rôle clé dans la stratégie de livraison des paramètres dans ECMAScript.
Cette stratégie a également quelques synonymes: "passer par objet" ou "passer par la part d'objet".
Cette stratégie a été proposée en 1974 par Barbara Liskov pour le langage de programmation CLU.
Le point clé de cette stratégie est que 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 à ses valeurs.
Nous ne pouvons pas appeler les références qui apparaissent ici "passer par référence", car les paramètres reçus par la fonction ne sont pas un alias d'objet direct, mais une copie de l'adresse de référence.
La différence la plus importante est que la réaffectation de nouvelles valeurs au paramètre à l'intérieur de la fonction n'affectera pas l'objet externe (comme dans le cas transmis par référence dans l'exemple ci-dessus), mais parce que le paramètre est une copie d'adresse, le même objet accessible à l'extérieur et à l'intérieur est le même objet (par exemple, l'objet externe n'est pas une copie complète car vous souhaitez passer par la valeur). La modification de la valeur d'attribut de l'objet Paramètre affectera l'objet externe.
La copie de code est la suivante:
Procédure foo (Bararg, Isfullchange):
Si Isfullchange:
bararg = {z: 1, q: 2}
sortie
fin
bararg.x = 100
bararg.y = 200
fin
// utilise toujours cette structure d'objet
bar = {
X: 10,
Y: 20
}
// passer par contribution affectera l'objet
foo (bar)
// Les propriétés de l'objet ont été modifiées
imprimer (bar) // {x: 100, y: 200}
// La réaffectation ne fonctionne pas
foo (bar, vrai)
// toujours la valeur ci-dessus
imprimer (bar) // {x: 100, y: 200}
L'hypothèse de ce traitement est que les objets utilisés dans la plupart des langues ne sont pas les valeurs d'origine.
Passer par part est un cas particulier de réussite par valeur
La stratégie de livraison par le partage est utilisée dans de nombreuses langues: Java, Ecmascript, Python, Ruby, Visual Basic, etc. Dans la plupart des cas, comme dans Java, Ecmascript ou Visual Basic, cette stratégie est également appelée pass-by-valeur - Signification: Valeur spéciale - Copie de référence (copie).
D'une part, c'est comme ça - les paramètres transmis à la fonction ne sont qu'un nom de la valeur liée (adresse de référence) et n'affecteront pas l'objet externe.
D'un autre côté, ces termes sont vraiment considérés pour manger mal sans y plonger, car de nombreux forums parlent de la façon de passer des objets aux fonctions JavaScript).
Il y a en effet un dicton dans la théorie générale qui passe par valeur: mais pour le moment, cette valeur est ce que nous appelons la copie d'adresse (copie), donc elle ne enfreint pas les règles.
Dans Ruby, cette stratégie est appelée passe par référence. Permettez-moi de le répéter: il n'est pas adopté en termes de copie d'une grande structure (par exemple, pas par valeur), et d'autre part, nous ne traitons pas des références à l'objet d'origine et ne pouvons pas la modifier; Par conséquent, ce concept de termes à terme peut être plus déroutant.
Il n'y a pas de cas spécial adopté par référence en théorie comme un cas spécial adopté par valeur.
Mais il est toujours nécessaire de comprendre ces stratégies dans les technologies mentionnées ci-dessus (Java, Ecmascript, Python, Ruby, autre). En fait, la stratégie qu'ils utilisent est de passer par le partage.
Appuyez sur Share et Pointer
Pour с / с ++, cette stratégie est la même que de passer par la valeur du pointeur, mais il y a une différence importante - cette stratégie peut dégérer les pointeurs et changer complètement d'objets. Cependant, en général, l'allocation d'un pointeur de valeur (adresse) vers un nouveau bloc de mémoire (c'est-à-dire que le bloc de mémoire référencé précédemment reste inchangé); La modification des propriétés de l'objet via un pointeur affectera l'objet externe d'Adong.
Ainsi, et les catégories de pointeurs, nous pouvons clairement voir que cela est passé par valeur d'adresse. Dans ce cas, passer par le partage n'est que du "sucre synthétique", comme le comportement d'attribution du pointeur (mais pas la déréférence), ou la modification des propriétés comme les références (aucune opération de déréférence requise), il peut parfois être nommé "Pointer sûr".
Cependant, с / с + + a également un sucre syntaxique spécial lorsqu'il se réfère aux propriétés des objets sans déréférences de pointeurs évidents:
La copie de code est la suivante:
obj-> x au lieu de (* obj) .x
Cette idéologie la plus étroitement liée au C ++ peut être vue à partir de l'implémentation de "pointeurs intelligents". Par exemple, dans boost :: shared_ptr, l'opérateur d'affectation et le constructeur de copie sont surchargés et le compteur de référence de l'objet est également utilisé pour supprimer l'objet via GC. Ce type de données a même un nom similaire - Share_ptr.
Implémentation ECMAScript
Maintenant, nous savons que la politique de passage des objets sous forme de paramètres dans ECMAScript - passe par part: modifier les propriétés du paramètre affectera l'extérieur, tandis que la réaffectation de la valeur n'affectera pas l'objet externe. Cependant, comme nous l'avons mentionné ci-dessus, les développeurs ECMAScript 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 Ashe a également écrit: Ce qui est passé est une copie de la référence (une copie de l'adresse). Donc, ce que tout le monde a mentionné dans le forum a dit est également correct sous cette explication.
Plus précisément, ce comportement peut être compris comme une simple affectation. Nous pouvons voir que les internes sont des objets complètement différents, mais la même valeur est référencée - c'est-à-dire la copie d'adresse.
Code ecmascript:
La copie de code est la suivante:
var foo = {x: 10, y: 20};
var bar = foo;
alerte (bar === foo); // vrai
bar.x = 100;
bar.y = 200;
alert ([foo.x, foo.y]); // [100, 200]
C'est-à-dire que 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 bar: addr (0xff)
Pour la réaffectation, la liaison est un nouvel identifiant d'objet (nouvelle adresse) sans affecter l'objet qui a été auparavant lié:
La copie de code est la suivante:
bar = {z: 1, q: 2};
alert ([foo.x, foo.y]); // [100, 200] Pas de changement
alert ([bar.z, bar.q]); // [1, 2] mais maintenant la référence est un nouvel objet
Autrement dit, FOO et BAR ont désormais des valeurs différentes et différentes adresses:
La copie de code est la suivante:
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 à nouveau que la valeur de l'objet mentionné ici est l'adresse, pas la structure de l'objet elle-même, et attribuer la variable à une autre variable - une référence à la valeur attribuée. Par conséquent, les deux variables se réfèrent à la même adresse mémoire. L'attribution suivante est la nouvelle adresse, qui consiste à résoudre la liaison à l'adresse de l'ancien objet, puis à se lier à l'adresse du nouvel objet. Il s'agit de la différence la plus importante entre le passage par référence.
De plus, si nous ne considérons que le niveau d'abstraction fourni par la norme ECMA-262, nous ne voyons que le concept de "valeur" dans l'algorithme, qui implémente la "valeur" passée (peut être la valeur d'origine ou l'objet), mais selon notre définition ci-dessus, elle peut également être complètement appelée "adoptée par valeur" parce que l'adresse de référence est également une valeur.
Cependant, afin d'éviter les malentendus (pourquoi les propriétés des objets externes peuvent être modifiées à l'intérieur de la fonction), les détails du niveau d'implémentation doivent être pris en compte ici - ce que nous voyons est passé par le partage, ou en d'autres termes - passé par un pointeur sûr, qui ne peut pas être dérécisé et modifié l'objet, mais la valeur de propriété de l'objet peut être modifiée.
Version à terme
Définissons le terme version de cette politique dans ECMascript.
Il peut être appelé "Pass by Value" - la valeur mentionnée ici est un cas spécial, c'est-à-dire que la valeur est une copie d'adresse. À partir de ce niveau, nous pouvons dire: les objets dans ECMAScript, sauf que les exceptions, sont passées par valeur, qui est en fait le niveau de l'abstraction ECMAScript.
Ou dans ce cas, il est spécifiquement appelé "passé par le partage". Grâce à cela, vous pouvez voir la différence entre le passage traditionnel par valeur et le passage par référence. Cette situation peut être divisée en deux situations: 1: La valeur d'origine est transmise par valeur; 2: L'objet est passé par le partage.
L'expression «fonctionner en référençant les types» n'a rien à voir avec ECMAScript, et c'est mal.
en conclusion
J'espère que ce message permet d'apprendre plus de détails au niveau de la macro et de la mise en œuvre dans ECMAScript. Comme toujours, s'il y a des questions, n'hésitez pas à en discuter.