Avant-propos
Cette phrase dans le titre de l'article a été initialement vue dans une spécification JavaScript étrangère, à cette époque, elle n'a pas suffisamment attiré l'attention jusqu'à ce qu'un bug ait récemment découvert les caractéristiques (pièges) de l'opération d'affectation continue dans JS.
Après une recherche en ligne, j'ai trouvé un très bon exemple de mission continue (source 1, source 2) :
var a = {n:1}; a.x = a = {n:2}; console.log(a.x); // 输出?
La réponse est :
console.log(a.x); // undefined
Je ne sais pas si vous avez la bonne réponse, du moins je me suis trompée.
J'en ai donc profité pour regarder de plus près le fonctionnement de l'affectation continue JS
Ordre de mission ?
Supposons qu'il y ait un code : A=B=C; , l'ordre d'exécution des instructions d'affectation est de droite à gauche, donc le problème est :
La conjecture 1 est-elle : B = C ;
Ou devinez 2 : B = C ; A = B ?
Nous savons tous que si deux objets pointent vers un objet en même temps, alors la modification de cet objet est synchronisée, comme par exemple :
var a={n:1}; var b=a; a.n=2; console.log(b);//Object {n: 2}
Vous pouvez ainsi tester l'ordre des devoirs consécutifs en fonction de cette fonctionnalité.
Selon la conjecture 1, remplacez C par un objet spécifique. Vous pouvez voir que la modification de a ne sera pas synchronisée avec b, car deux {n:1} sont créés lors de l'exécution de la première et de la deuxième lignes de l'objet. Tel que :
var b={n:1}; var a={n:1}; a.n=0; console.log(b);//Object {n: 1}
Selon la conjecture 2, remplacez C par un objet spécifique. Vous pouvez voir que la modification de a est synchronisée avec b, car a et b font référence à un objet en même temps, comme :
var b={n:1}; var a=b; a.n=0; console.log(b);//Object {n: 0}
Test d'une véritable affectation continue :
var a,b; a=b={n:1}; a.n=0; console.log(b);//Object {n: 0}
Vous pouvez voir qu'il est cohérent avec la conjecture 2. Si quelqu'un estime que ce test est inexact, vous pouvez le tester à nouveau et utiliser les fonctionnalités setter et getter d'ECMA5 pour tester.
Tout d'abord, les setters et les getters sont appliqués aux noms de variables, et non aux objets réellement stockés dans les variables, comme suit :
Object.defineProperty(window,"obj",{ get:function(){ console.log("getter!!!"); } }); var x=obj; obj;//getter!!! undefined x;//undefined
Vous pouvez voir que seul obj génère "getter !!!", mais pas x. Utilisez cette fonctionnalité pour tester.
Test d'affectation continue 2 :
Object.defineProperty(window,"obj",{ get:function(){ console.log("getter!!!"); } }); a=b=obj;//getter!!! undefined
Confirmé à nouveau via getter, dans A=B=C, C n'est lu qu'une seule fois.
Ainsi, la véritable règle de fonctionnement de l'affectation continue est B = C = A = B ; c'est-à-dire que l'affectation continue prend toujours uniquement le résultat de l'expression du côté droit du signe égal de droite à gauche ; côté du signe égal.
Le devoir continu peut-il être rédigé séparément ?
Vous pouvez voir les vraies règles de l'affectation continue à partir de ce qui précède. Revenez ensuite au cas au début de l'article. Si vous divisez l'affectation continue selon les règles ci-dessus, vous constaterez que le résultat est différent, tel. comme :
var a={n:1}; a={n:2}; a.x=a; console.log(a.x);//Object {n: 2, x: Object}
Ainsi, bien que l'instruction d'affectation continue suive les règles d'affectation de droite à gauche, elle ne peut toujours pas être écrite dans des instructions séparées. Quant au pourquoi
.Je suppose : afin de garantir l'exactitude de l'instruction d'affectation, js supprimera d'abord une copie de toutes les adresses de référence à attribuer avant d'exécuter une instruction d'affectation, puis attribuera les valeurs une par une.
Je pense donc que la logique de ce code a.x=a={n:2} est :
;1. Avant l'exécution, les adresses de référence de a dans a et a.x seront d'abord supprimées. Cette valeur pointe vers {n:1}
.2. Créez un nouvel objet {n:2} en mémoire
3. Exécutez a={n:2} et changez la référence de a de pointant vers {n:1} vers pointant vers le nouveau {n:2}
4. Exécutez a.x=a. À ce moment, a pointe déjà vers le nouvel objet, et comme a.x conserve la référence d'origine avant l'exécution, a.x's a pointe toujours vers l'objet {n:1} d'origine, donc le un nouvel objet est donné à l'objet d'origine. Ajoutez un attribut x avec le contenu {n:2}, qui est maintenant un
.5. L'exécution de l'instruction se termine, l'objet d'origine passe de {n:1} à {n:1,x:{n:2}}, et l'objet d'origine est recyclé par GC car plus personne ne le référence. Actuellement, pointer vers un nouvel objet {n:2}
6. Nous avons donc le résultat courant au début de l'article, puis exécutons a.x, il sera naturellement indéfini
Le processus ci-dessus est illustré par le numéro de série :
En suivant le processus ci-dessus, on peut voir que l'ancien a.x et le nouveau a pointent tous deux vers l'objet nouvellement créé {n:2}, ils devraient donc être congruents.
Test :
var a = {n:1}; var b = a; a.x = a = {n:2}; console.log(a===b.x); //true
Parce que nous avons ajouté var b=a, ce qui signifie ajouter une référence à l'objet d'origine, il ne sera pas publié à l'étape 5 ci-dessus, ce qui confirme la conclusion ci-dessus.
Post-scriptum
C'est à cette époque que j'ai découvert les caractéristiques de l'affectation continue. En repensant au titre de l'article, il semble qu'il devrait s'appeler :
.Essayez de ne pas utiliser l'opération d'affectation continue de JS à moins de vraiment comprendre son mécanisme interne et ses conséquences possibles.