JavaScript est-il un langage passe-par-référence ou passe-par-valeur?


1405

Les types primitifs (nombre, chaîne, etc.) sont transmis par valeur, mais les objets sont inconnus, car ils peuvent tous deux être transmis par valeur (dans le cas où nous considérons qu'une variable contenant un objet est en fait une référence à l'objet ) et passé par référence (quand on considère que la variable de l'objet contient l'objet lui-même).

Bien que cela n'ait pas vraiment d'importance à la fin, je veux savoir quelle est la bonne façon de présenter les arguments en passant les conventions. Existe-t-il un extrait de la spécification JavaScript, qui définit quelle devrait être la sémantique à ce sujet?


2
Je pense que vous avez accidentellement inversé vos définitions de passé par valeur et passé par référence ... "passé par valeur (au cas où nous considérons qu'une variable contenant un objet est en fait une référence à l'objet) et passé -par référence (quand on considère que la variable de l'objet contient l'objet lui-même) "
Niko Bellic

5
Oui. Quelle que soit la syntaxe, dans tout appel de fonction dans n'importe quel langage de programmation, le passage par référence signifie que les données associées à la variable transmise ne sont pas copiées lorsqu'elles sont transmises à la fonction, et donc toutes les modifications apportées par la fonction à la variable transmise seront conservées. dans le programme après la fin de l'appel de fonction. Pass-by-value signifie que les données associées à la variable sont réellement copiées lorsqu'elles sont transmises à la fonction et toutes les modifications apportées par cette fonction à cette variable seront perdues lorsque la variable sort du champ d'application du corps de la fonction lorsque la fonction revient.
John Sonderson

5
Cette vieille question est quelque peu toxique parce que sa réponse fortement votée est incorrecte. JavaScript est strictement pass-by-value .
Pointy

6
@DanailNachev La terminologie est malheureusement déroutante. Le fait est que «passer par valeur» et «passer par référence» sont des termes antérieurs à de nombreuses fonctionnalités de langage de programmation plus modernes. Les mots "valeur" et "référence" se réfèrent spécifiquement au paramètre tel qu'il apparaît dans l'expression d'appel de fonction. JavaScript évalue toujours chaque expression dans une liste de paramètres d'appel de fonction avant d' appeler la fonction, donc les paramètres sont toujours des valeurs. La partie déroutante est que les références aux objets sont des valeurs JavaScript courantes. Cependant, cela n'en fait pas un langage de «passage par référence».
Pointy

2
@DanailNachev "passer par référence" signifie spécifiquement que si vous avez var x=3, y=x; f(x); alert(y === x);alors la fonction f()peut faire le rapport d'alerte falseet non true. En JavaScript, ce n'est pas possible, donc ce n'est pas un passage par référence. C'est bien qu'il soit possible de passer des références à des objets modifiables, mais ce n'est pas ce que signifie "passer par référence". Comme je l'ai dit, c'est dommage que la terminologie soit si confuse.
Pointy du

Réponses:


1599

C'est intéressant en JavaScript. Considérez cet exemple:

function changeStuff(a, b, c)
{
  a = a * 10;
  b.item = "changed";
  c = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};

changeStuff(num, obj1, obj2);

console.log(num);
console.log(obj1.item);
console.log(obj2.item);

Cela produit la sortie:

10
changed
unchanged
  • S'il obj1ne s'agissait pas du tout d'une référence, la modification obj1.itemn'aurait aucun effet sur l' obj1extérieur de la fonction.
  • Si l'argument était une référence appropriée, alors tout aurait changé. numserait 100, et obj2.itemlirait "changed".

Au lieu de cela, la situation est que l'élément transmis est transmis par valeur. Mais l'élément passé par valeur est lui - même une référence. Techniquement, cela s'appelle appel par partage .

Concrètement, cela signifie que si vous modifiez le paramètre lui-même (comme avec numet obj2), cela n'affectera pas l'élément qui a été introduit dans le paramètre. Mais si vous modifiez les INTERNES du paramètre, cela se propagera (comme avec obj1).


32
C'est exactement la même chose (ou du moins sémantiquement) que C #. L'objet a deux types: Valeur (types primitifs) et Référence.
Peter Lee

53
Je pense que cela est également utilisé en Java: référence par valeur.
Jpnh

297
la vraie raison est que dans changeStuff, num, obj1 et obj2 sont des références. Lorsque vous modifiez la itempropriété de l'objet référencé par obj1, vous modifiez la valeur de la propriété d'élément qui était initialement définie sur "inchangée". Lorsque vous attribuez à obj2 une valeur de {item: "changed"}, vous changez la référence à un nouvel objet (qui sort immédiatement de la portée lorsque la fonction se termine). Il devient plus évident ce qui se passe si vous nommez les paramètres de fonction comme numf, obj1f et obj2f. Ensuite, vous voyez que les paramètres masquaient les noms var externes.
jinglesthula

13
@BartoNaz Pas vraiment. Ce que vous voulez, c'est passer la référence par référence, au lieu de passer la référence par valeur. Mais JavaScript transmet toujours la référence par valeur, tout comme il transmet tout le reste par valeur. (À titre de comparaison, C # a un comportement de référence par valeur similaire à JavaScript et Java, mais vous permet de spécifier la référence par référence avec le refmot clé.) Habituellement, la fonction renvoie simplement le nouvel objet et fait l'affectation au point où vous appelez la fonction. Par exemple, foo = GetNewFoo();au lieu deGetNewFoo(foo);
Tim Goodman

56
Bien que cette réponse soit la plus populaire, elle peut être légèrement déroutante car elle indique "Si elle était pure valeur de passage". JavaScript est purement pass-by-value. Mais la valeur transmise est une référence. Ceci n'est pas du tout contraint à passer des paramètres. Vous pouvez simplement copier la variable par var obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed';et observer le même effet que dans votre exemple. Par conséquent, je renvoie personnellement la réponse de Tim Goodman
chiccodoro

476

Elle passe toujours par valeur, mais pour les objets, la valeur de la variable est une référence. Pour cette raison, lorsque vous passez un objet et modifiez ses membres , ces modifications persistent en dehors de la fonction. Cela rend l' air comme passe par référence. Mais si vous modifiez réellement la valeur de la variable objet, vous verrez que la modification ne persiste pas, prouvant qu'elle passe vraiment par la valeur.

Exemple:

function changeObject(x) {
  x = { member: "bar" };
  console.log("in changeObject: " + x.member);
}

function changeMember(x) {
  x.member = "bar";
  console.log("in changeMember: " + x.member);
}

var x = { member: "foo" };

console.log("before changeObject: " + x.member);
changeObject(x);
console.log("after changeObject: " + x.member); /* change did not persist */

console.log("before changeMember: " + x.member);
changeMember(x);
console.log("after changeMember: " + x.member); /* change persists */

Production:

before changeObject: foo
in changeObject: bar
after changeObject: foo

before changeMember: foo
in changeMember: bar
after changeMember: bar

14
@daylight: En fait, vous vous trompez; s'il était passé par const ref, essayer de faire changeObject provoquerait une erreur, plutôt que l'échec. Essayez d'affecter une nouvelle valeur à une référence const en C ++ et le compilateur la rejette. En termes d'utilisateurs, c'est la différence entre passer par valeur et passer par référence const.
deworde

5
@daylight: Ce n'est pas une référence constante. Dans changeObject, j'ai changé xpour contenir une référence au nouvel objet. x = {member:"bar"};est équivalent à x = new Object(); x.member = "bar"; ce que je dis est également vrai de C #, soit dit en passant.
Tim Goodman

2
@daylight: Pour C #, vous pouvez le voir en dehors de la fonction, si vous utilisez le refmot clé que vous pouvez passer la référence par référence ( au lieu de la valeur par défaut de passer la référence en valeur), puis le changement du point à new Object() vous persister .
Tim Goodman

11
@adityamenon Il est difficile de répondre "pourquoi", mais je noterais que les concepteurs de Java et C # ont fait un choix similaire; ce n'est pas seulement une bizarrerie JavaScript. Vraiment, c'est une valeur de transmission très cohérente, ce qui le rend déroutant pour les gens, c'est qu'une valeur peut être une référence. Ce n'est pas très différent de passer un pointeur (par valeur) en C ++, puis de le déréférencer pour définir les membres. Personne ne serait surpris que ce changement persiste. Mais parce que ces langues font abstraction du pointeur et font silencieusement le déréférencement pour vous, les gens sont confus.
Tim Goodman

41
En d'autres termes, la chose déroutante ici n'est pas passe-par-valeur / passe-par-référence. Tout est pass-by-value, point final. La chose déroutante est que vous ne pouvez pas passer un objet, ni stocker un objet dans une variable. Chaque fois que vous pensez que vous faites cela, vous êtes en fait passer ou stocker une référence à cet objet. Mais lorsque vous accédez à ses membres, il se produit un déréférencement silencieux qui perpétue la fiction selon laquelle votre variable contenait l'objet réel.
Tim Goodman

150

La variable ne "tient" pas l'objet; il détient une référence. Vous pouvez affecter cette référence à une autre variable, et maintenant les deux référencent le même objet. C'est toujours passer par valeur (même lorsque cette valeur est une référence ...).

Il n'y a aucun moyen de modifier la valeur détenue par une variable passée en paramètre, ce qui serait possible si JavaScript supportait le passage par référence.


2
Cela me déroute un peu. N'est-ce pas passer une référence passe-par-référence?

8
L'auteur signifie qu'en passant une référence, vous passez une valeur de référence (une autre façon de penser est de passer la valeur de l'adresse mémoire). C'est pourquoi, si vous redéclarez l'objet, l'original ne change pas, car vous créez un nouvel objet à un emplacement mémoire différent. Si vous modifiez une propriété, l'objet d'origine change car vous l'avez modifié à l'emplacement de mémoire d'origine (qui n'a pas été réaffecté).
Huy-Anh Hoang

113

Mes deux cents ... C'est ainsi que je le comprends. (N'hésitez pas à me corriger si je me trompe)

Il est temps de jeter tout ce que vous savez sur le passage par valeur / référence.

Parce qu'en JavaScript, peu importe qu'il soit passé par valeur ou par référence ou autre. Ce qui compte, c'est la mutation vs l'affectation des paramètres passés dans une fonction.

OK, laissez-moi faire de mon mieux pour expliquer ce que je veux dire. Disons que vous avez quelques objets.

var object1 = {};
var object2 = {};

Ce que nous avons fait, c'est "affectation" ... Nous avons affecté 2 objets vides séparés aux variables "objet1" et "objet2".

Maintenant, disons que nous aimons mieux object1 ... Donc, nous "assignons" une nouvelle variable.

var favoriteObject = object1;

Ensuite, pour une raison quelconque, nous décidons que nous aimons mieux l'objet 2. Donc, nous faisons simplement une petite réaffectation.

favoriteObject = object2;

Rien n'est arrivé à object1 ou à object2. Nous n'avons modifié aucune donnée. Tout ce que nous avons fait, c'est de réaffecter notre objet préféré. Il est important de savoir que object2 et favoriteObject sont tous deux affectés au même objet. Nous pouvons changer cet objet via l'une de ces variables.

object2.name = 'Fred';
console.log(favoriteObject.name) // Logs Fred
favoriteObject.name = 'Joe';
console.log(object2.name); // Logs Joe

OK, regardons maintenant les primitives comme les chaînes par exemple

var string1 = 'Hello world';
var string2 = 'Goodbye world';

Encore une fois, nous choisissons un favori.

var favoriteString = string1;

Nos variables favoriteString et string1 sont affectées à «Hello world». Et maintenant, si nous voulons changer notre chaîne favorite ??? Que va-t-il se passer ???

favoriteString = 'Hello everyone';
console.log(favoriteString); // Logs 'Hello everyone'
console.log(string1); // Logs 'Hello world'

Euh oh .... Qu'est-ce qui s'est passé. Nous ne pouvions pas changer string1 en changeant favoriteString ... Pourquoi ?? Parce que nous n'avons pas changé notre objet chaîne . Tout ce que nous avons fait, c'est "RE ASSIGNER" la variable favoriteString à une nouvelle chaîne. Cela l'a essentiellement déconnecté de string1. Dans l'exemple précédent, lorsque nous avons renommé notre objet, nous n'avons rien attribué. (Eh bien, pas à la variable elle - même , ... nous avons cependant assigné la propriété name à une nouvelle chaîne.) Au lieu de cela, nous avons simplement muté l'objet qui conserve les connexions entre les 2 variables et les objets sous-jacents. (Même si nous avions voulu modifier ou muter l'objet chaîne lui-même, nous ne pourrions pas l'avoir, car les chaînes sont en fait immuables en JavaScript.)

Passons maintenant aux fonctions et à la transmission de paramètres .... Lorsque vous appelez une fonction et passez un paramètre, ce que vous faites essentiellement est une "affectation" à une nouvelle variable, et cela fonctionne exactement de la même manière que si vous l'attribuiez simplement à l'aide de le signe égal (=).

Prenez ces exemples.

var myString = 'hello';

// Assign to a new variable (just like when you pass to a function)
var param1 = myString;
param1 = 'world'; // Re assignment

console.log(myString); // Logs 'hello'
console.log(param1);   // Logs 'world'

Maintenant, la même chose, mais avec une fonction

function myFunc(param1) {
    param1 = 'world';

    console.log(param1);   // Logs 'world'
}

var myString = 'hello';
// Calls myFunc and assigns param1 to myString just like param1 = myString
myFunc(myString);

console.log(myString); // logs 'hello'

OK, donnons maintenant quelques exemples en utilisant des objets à la place ... d'abord, sans la fonction.

var myObject = {
    firstName: 'Joe',
    lastName: 'Smith'
};

// Assign to a new variable (just like when you pass to a function)
var otherObj = myObject;

// Let's mutate our object
otherObj.firstName = 'Sue'; // I guess Joe decided to be a girl

console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Sue'

// Now, let's reassign the variable
otherObj = {
    firstName: 'Jack',
    lastName: 'Frost'
};

// Now, otherObj and myObject are assigned to 2 very different objects
// And mutating one object has no influence on the other
console.log(myObject.firstName); // Logs 'Sue'
console.log(otherObj.firstName); // Logs 'Jack';

Maintenant, la même chose, mais avec un appel de fonction

function myFunc(otherObj) {

    // Let's mutate our object
    otherObj.firstName = 'Sue';
    console.log(otherObj.firstName); // Logs 'Sue'

    // Now let's re-assign
    otherObj = {
        firstName: 'Jack',
        lastName: 'Frost'
    };
    console.log(otherObj.firstName); // Logs 'Jack'

    // Again, otherObj and myObject are assigned to 2 very different objects
    // And mutating one object doesn't magically mutate the other
}

var myObject = {
    firstName: 'Joe',
    lastName: 'Smith'
};

// Calls myFunc and assigns otherObj to myObject just like otherObj = myObject
myFunc(myObject);

console.log(myObject.firstName); // Logs 'Sue', just like before

OK, si vous lisez l'intégralité de cet article, vous avez peut-être maintenant une meilleure compréhension du fonctionnement des appels de fonction en JavaScript. Peu importe que quelque chose soit passé par référence ou par valeur ... Ce qui compte, c'est l'affectation vs la mutation.

Chaque fois que vous passez une variable à une fonction, vous "assignez" à quelque soit le nom de la variable de paramètre, comme si vous utilisiez le signe égal (=).

Rappelez-vous toujours que le signe égal (=) signifie affectation. Souvenez-vous toujours que passer un paramètre à une fonction en JavaScript signifie également une affectation. Ils sont identiques et les 2 variables sont connectées exactement de la même manière (c'est-à-dire qu'elles ne le sont pas, sauf si vous comptez qu'elles sont affectées au même objet).

Le seul moment où la "modification d'une variable" affecte une variable différente est lorsque l'objet sous-jacent est muté (auquel cas vous n'avez pas modifié la variable, mais l'objet lui-même.

Il est inutile de faire une distinction entre les objets et les primitives, car cela fonctionne exactement de la même manière que si vous n'aviez pas de fonction et que vous utilisiez simplement le signe égal pour attribuer à une nouvelle variable.

Le seul problème est que le nom de la variable que vous passez dans la fonction est le même que le nom du paramètre de fonction. Lorsque cela se produit, vous devez traiter le paramètre à l'intérieur de la fonction comme s'il s'agissait d'une toute nouvelle variable privée de la fonction (car elle l'est)

function myFunc(myString) {
    // myString is private and does not affect the outer variable
    myString = 'hello';
}

var myString = 'test';
myString = myString; // Does nothing, myString is still 'test';

myFunc(myString);
console.log(myString); // Logs 'test'

2
Pour tous les programmeurs C, pensez à char *. foo(char *a){a="hello";} ne fait rien, mais si vous le faites, foo(char *a){a[0]='h';a[1]='i';a[2]=0;}il est modifié à l'extérieur car il as'agit d'un emplacement mémoire transmis par une valeur qui fait référence à une chaîne (tableau de caractères). Passer des structures (similaires aux objets js) par valeur en C est autorisé, mais non recommandé. JavaScript applique simplement ces meilleures pratiques et masque la corruption inutile et généralement indésirable ... et cela facilite la lecture.
technosaurus

2
C'est exact - les termes passe-par-valeur et passe-par-référence ont des significations dans la conception du langage de programmation, et ces significations n'ont rien à voir avec la mutation d'objet. Tout dépend du fonctionnement des paramètres de fonction.
Pointy

2
Maintenant que je comprends que obj1 = obj2 signifie que obj1 et obj2 pointent maintenant vers le même emplacement de référence, et si je modifie les internes de obj2, référencer obj1 exposera les mêmes internes. Comment copier un objet de telle sorte que lorsque je le fais, source = { "id":"1"}; copy = source /*this is wrong*/; copy.id="2"cette source est toujours {"id": "1"}?
Machtyn

1
J'ai posté une autre réponse avec des définitions traditionnelles pour, espérons-le, réduire la confusion. Les définitions traditionnelles de «passe-par-valeur» et de «passe-par-référence» ont été définies à l'époque des pointeurs de mémoire avant le déréférencement automatique. Il était parfaitement bien entendu que la valeur d'une variable d'objet était en fait l'emplacement du pointeur de mémoire, et non l'objet. Bien que votre discussion sur l'affectation vs la mutation soit peut-être utile, il n'est pas nécessaire de jeter les termes traditionnels ni leurs définitions. La mutation, l'affectation, le passage par valeur, le passage par référence, etc. ne doivent pas se contredire.
C Perkins

"Number" est-il aussi "immuable"?
ebram khalil

72

Considérer ce qui suit:

  1. Les variables sont des pointeurs vers des valeurs en mémoire.
  2. La réaffectation d'une variable pointe simplement ce pointeur sur une nouvelle valeur.
  3. La réaffectation d'une variable n'affectera jamais les autres variables qui pointaient vers le même objet

Donc, oubliez "passer par référence / valeur" ne vous raccrochez pas sur "passer par référence / valeur" car:

  1. Les termes ne sont utilisés que pour décrire le comportement d'un langage, pas nécessairement l'implémentation sous-jacente réelle. À la suite de cette abstraction, les détails critiques qui sont essentiels pour une explication décente sont perdus, ce qui conduit inévitablement à la situation actuelle où un seul terme ne décrit pas correctement le comportement réel et des informations supplémentaires doivent être fournies
  2. Ces concepts n'ont pas été définis à l'origine dans le but de décrire le javascript en particulier et je ne me sens donc pas obligé de les utiliser lorsqu'ils ne font qu'ajouter à la confusion.

Pour répondre à votre question: des pointeurs sont passés.


// code
var obj = {
    name: 'Fred',
    num: 1
};

// illustration
               'Fred'
              /
             /
(obj) ---- {}
             \
              \
               1


// code
obj.name = 'George';


// illustration
                 'Fred'


(obj) ---- {} ----- 'George'
             \
              \
               1


// code
obj = {};

// illustration
                 'Fred'


(obj)      {} ----- 'George'
  |          \
  |           \
 { }            1


// code
var obj = {
    text: 'Hello world!'
};

/* function parameters get their own pointer to 
 * the arguments that are passed in, just like any other variable */
someFunc(obj);


// illustration
(caller scope)        (someFunc scope)
           \             /
            \           /
             \         /
              \       /
               \     /
                 { }
                  |
                  |
                  |
            'Hello world'

Quelques derniers commentaires:

  • Il est tentant de penser que les primitives sont appliquées par des règles spéciales alors que les objets ne le sont pas, mais les primitives ne sont que la fin de la chaîne de pointeurs.
  • Comme dernier exemple, considérez pourquoi une tentative courante d'effacer un tableau ne fonctionne pas comme prévu.


var a = [1,2];
var b = a;

a = [];
console.log(b); // [1,2]
// doesn't work because `b` is still pointing at the original array

Questions de suivi pour un crédit supplémentaire;) Comment fonctionne la collecte des ordures? Si je fais défiler une variable à travers un million de {'George', 1}valeurs, mais n'utilise qu'une seule à la fois, comment les autres sont-elles gérées? Et que se passe-t-il lorsque j'attribue une variable à la valeur d'une autre variable? Suis-je alors pointé sur un pointeur, ou pointé sur la pointe de l'opérande droit? A pour var myExistingVar = {"blah", 42}; var obj = myExistingVar;résultat de objpointer vers {"blah", 42}ou vers myExistingVar?
Michael Hoffmann

@MichaelHoffmann Ceux-ci méritent leurs propres questions SO et sont probablement déjà mieux répondu que je ne peux gérer. Cela étant dit, 1)j'ai exécuté un profil de mémoire dans les outils de développement du navigateur pour une fonction de boucle telle que celle que vous avez décrite et j'ai vu des pics d'utilisation de la mémoire tout au long du processus de boucle. Cela semble indiquer que de nouveaux objets identiques sont effectivement créés à chaque itération de la boucle. Lorsque les pointes tombent soudainement, le garbage collector vient de nettoyer un groupe de ces objets inutilisés.
geg

1
@MichaelHoffmann 2)Concernant quelque chose comme var a = b, javascript ne fournit pas de mécanisme pour utiliser des pointeurs et donc une variable ne peut jamais pointer vers un pointeur (comme vous pouvez en C), bien que le moteur javascript sous-jacent les utilise sans aucun doute. Alors ... var a = bpointera a"vers la pointe de l'opérande droit"
geg

J'ai posé la question n ° 1 ici (en particulier sur Chrome car l'implémentation est probablement différente dans chaque navigateur) stackoverflow.com/q/42778439/539997 et j'essaie toujours de savoir comment formuler la question n ° 2. Toute aide est appréciée.
Michael Hoffmann

1
Il n'est pas nécessaire d' oublier "passer par référence / valeur" ! Ces termes ont des significations historiques qui décrivent exactement ce que vous essayez de décrire. Si nous jetons les termes et définitions historiques et devenons trop paresseux pour savoir ce qu'ils signifiaient à l'origine, nous perdons la capacité de communiquer efficacement entre les générations. Il n'y aurait aucun bon moyen de discuter des différences entre les différents langages et systèmes. Au lieu de cela, les nouveaux programmeurs doivent apprendre et comprendre les termes traditionnels et pourquoi et d'où ils viennent. Sinon, nous perdons collectivement les connaissances et la compréhension.
C Perkins

24

Un objet en dehors d'une fonction est passé dans une fonction en donnant une référence à l'objet extérieur.

Lorsque vous utilisez cette référence pour manipuler son objet, l'objet extérieur est ainsi affecté. Cependant, si à l'intérieur de la fonction vous avez décidé de pointer la référence vers autre chose, vous n'avez pas affecté l'objet du tout, car vous n'avez fait que rediriger la référence vers autre chose.


20

Pensez-y comme ceci: c'est toujours passer par la valeur. Cependant, la valeur d'un objet n'est pas l'objet lui-même, mais une référence à cet objet.

Voici un exemple, en passant un nombre (un type primitif)

function changePrimitive(val) {
    // At this point there are two '10's in memory.
    // Changing one won't affect the other
    val = val * 10;
}
var x = 10;
changePrimitive(x);
// x === 10

La répétition de ceci avec un objet donne des résultats différents:

function changeObject(obj) {
    // At this point there are two references (x and obj) in memory,
    // but these both point to the same object.
    // changing the object will change the underlying object that
    // x and obj both hold a reference to.
    obj.val = obj.val * 10;
}
var x = { val: 10 };
changeObject(x);
// x === { val: 100 }

Un autre exemple:

function changeObject(obj) {
    // Again there are two references (x and obj) in memory,
    // these both point to the same object.
    // now we create a completely new object and assign it.
    // obj's reference now points to the new object.
    // x's reference doesn't change.
    obj = { val: 100 };
}
var x = { val: 10 };
changeObject(x);
// x === { val: 10}

19

Une explication très détaillée sur la copie, le passage et la comparaison par valeur et par référence se trouve dans ce chapitre du livre "JavaScript: The Definitive Guide" .

Avant de quitter le sujet de la manipulation d'objets et de tableaux par référence, nous devons clarifier un point de nomenclature.

L'expression «passer par référence» peut avoir plusieurs sens. Pour certains lecteurs, l'expression fait référence à une technique d'invocation de fonction qui permet à une fonction d'affecter de nouvelles valeurs à ses arguments et de voir ces valeurs modifiées visibles en dehors de la fonction. Ce n'est pas ainsi que le terme est utilisé dans ce livre.

Ici, nous voulons simplement dire qu'une référence à un objet ou à un tableau - et non à l'objet lui-même - est passée à une fonction. Une fonction peut utiliser la référence pour modifier les propriétés de l'objet ou des éléments du tableau. Mais si la fonction remplace la référence par une référence à un nouvel objet ou tableau, cette modification n'est pas visible en dehors de la fonction.

Les lecteurs familiers avec l'autre sens de ce terme peuvent préférer dire que les objets et les tableaux sont passés par valeur, mais la valeur qui est passée est en fait une référence plutôt que l'objet lui-même.


Wow, c'est incroyablement déroutant. Qui, dans son bon sens, définirait un terme bien établi pour signifier exactement le contraire et l'utiliserait ainsi? Pas étonnant que tant de réponses ici sur cette question soient si confuses.
Jörg W Mittag

16

JavaScript est toujours pass-by-value ; tout est de type valeur.

Les objets sont des valeurs et les fonctions membres des objets sont des valeurs elles-mêmes (rappelez-vous que les fonctions sont des objets de première classe en JavaScript). En outre, concernant le concept selon lequel tout en JavaScript est un objet ; c'est faux. Les chaînes, symboles, nombres, booléens, nuls et non définis sont des primitives .

À l'occasion, ils peuvent tirer parti de certaines fonctions membres et propriétés héritées de leurs prototypes de base, mais ce n'est que pour plus de commodité. Cela ne signifie pas qu'ils sont eux-mêmes des objets. Essayez ce qui suit pour référence:

x = "test";
alert(x.foo);
x.foo = 12;
alert(x.foo);

Dans les deux alertes, vous trouverez la valeur indéfinie.


12
-1, ce n'est pas toujours passer par valeur. De MDC: "Si vous passez un objet (c'est-à-dire une valeur non primitive, comme un tableau ou un objet défini par l'utilisateur) comme paramètre, une référence à l'objet est transmise à la fonction."
Nick

37
@Nick: C'est toujours passer par valeur. Période. Une référence à l'objet est transmise par valeur à la fonction. Cela ne passe pas par référence. «Passer par référence» pourrait presque être considéré comme passant la variable elle-même, plutôt que sa valeur; toutes les modifications apportées par la fonction à l'argument (y compris le remplacer entièrement par un objet différent!) seraient reflétées dans l'appelant. Ce dernier bit n'est pas possible dans JS, car JS ne passe pas par référence - il transmet les références par valeur. La distinction est subtile, mais plutôt importante pour comprendre ses limites.
cHao

1
Pour les futurs gerbeurs ... À propos de votre référence: x = "teste"; x.foo = 12;etc. Ce n'est pas parce qu'une propriété n'est pas persistante que ce n'est pas un objet. Comme le dit MDN: En JavaScript, presque tout est un objet. Tous les types primitifs, à l'exception de null et undefined, sont traités comme des objets. Des propriétés peuvent leur être attribuées (les propriétés attribuées de certains types ne sont pas persistantes) et elles ont toutes les caractéristiques des objets. link
slacktracer

9
MDN est un wiki édité par l'utilisateur et il ne fonctionne pas correctement. La référence normative est ECMA-262. Voir S. 8 "The Reference Specification Type", qui explique comment les références sont résolues, et aussi 8.12.5 "[[Put]]", qui est utilisé pour expliquer AssignmentExpression à une référence, et, pour la conversion d'objet 9.9 ToObject. Pour les valeurs primitives, Michael a déjà expliqué ce que fait ToObject, comme dans la spécification. Mais voir aussi l'art. 4.3.2 valeur primitive.
Garrett

1
@WonderLand: Non, il ne l'est pas. Les personnes qui n'ont jamais pu passer par référence peuvent ne jamais comprendre les différences entre passer par référence et passer une référence par valeur. Mais ils sont là et ils comptent. Je me fiche de mal désinformer les gens simplement parce que cela semble plus facile.
cHao

12

En JavaScript, le type de la valeur contrôle uniquement si cette valeur sera attribuée par copie de valeur ou par copie de référence .

Les valeurs primitives sont toujours attribuées / transmises par value-copy :

  • null
  • undefined
  • chaîne
  • nombre
  • booléen
  • symbole dans ES6

Les valeurs composées sont toujours attribuées / transmises par référence-copie

  • objets
  • tableaux
  • une fonction

Par exemple

var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3

var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]

Dans l'extrait ci-dessus, car 2est une primitive scalaire, acontient une copie initiale de cette valeur et bse voit attribuer une autre copie de la valeur. Lors de la modification b, vous ne modifiez en aucun cas la valeur en a.

Mais les deux cet dsont des références distinctes à la même valeur partagée [1,2,3], qui est une valeur composée. Il est important de noter que cni dplus ni plus ne "possède" la [1,2,3]valeur - les deux sont juste des références homologues égales à la valeur. Ainsi, lorsque vous utilisez l'une ou l'autre référence pour modifier ( .push(4)) la arrayvaleur partagée réelle elle-même, cela affecte uniquement la seule valeur partagée, et les deux références feront référence à la valeur nouvellement modifiée [1,2,3,4].

var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]

// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]

Lorsque nous faisons l'affectation b = [4,5,6], nous ne faisons absolument rien pour affecter où ase réfère toujours ( [1,2,3]). Pour ce faire, il bfaudrait être un pointeur vers aplutôt qu'une référence à la array- mais aucune telle capacité n'existe dans JS!

function foo(x) {
    x.push( 4 );
    x; // [1,2,3,4]

    // later
    x = [4,5,6];
    x.push( 7 );
    x; // [4,5,6,7]
}

var a = [1,2,3];

foo( a );

a; // [1,2,3,4]  not  [4,5,6,7]

Lorsque nous transmettons l'argument a, il affecte une copie de la aréférence à x. xet asont des références distinctes pointant vers la même [1,2,3]valeur. Maintenant, à l'intérieur de la fonction, nous pouvons utiliser cette référence pour muter la valeur elle-même ( push(4)). Mais lorsque nous faisons l'affectation x = [4,5,6], cela n'affecte en rien où la référence initiale apointe - pointe toujours sur la valeur (maintenant modifiée) [1,2,3,4].

Pour transmettre efficacement une valeur composée (comme un array) par copie de valeur, vous devez en faire une copie manuellement, afin que la référence transmise ne pointe toujours pas vers l'original. Par exemple:

foo( a.slice() );

Valeur composée (objet, tableau, etc.) qui peut être transmise par référence-copie

function foo(wrapper) {
    wrapper.a = 42;
}

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

Ici, objagit comme un wrapper pour la propriété primitive scalaire a. Une fois transmis à foo(..), une copie de la objréférence est transmise et définie sur le wrapperparamètre. Nous pouvons maintenant utiliser la wrapperréférence pour accéder à l'objet partagé et mettre à jour sa propriété. Une fois la fonction terminée, obj.ala valeur mise à jour s'affiche 42.

La source


Vous déclarez d'abord "Les valeurs composées sont toujours attribuées / transmises par référence-copie", puis vous déclarez "attribue une copie de la référence à x". Dans le cas de ce que vous appelez une "valeur composée", la valeur de variable réelle EST la référence (c'est-à-dire le pointeur de mémoire). Tout comme vous l'avez expliqué, la référence est copiée ... donc la valeur des variables est copiée , soulignant encore une fois que la RÉFÉRENCE EST LA VALEUR. Cela signifie que JavaScript est une valeur de passage pour tous les types. Pass-by-value signifie passer une copie de la valeur des variables. Peu importe que la valeur soit une référence à un objet / tableau.
C Perkins

Vous introduisez une nouvelle terminologie (copie de valeur / copie de référence) et cela rend les choses plus complexes. Il n'y a que des copies, point final. Si vous transmettez une primitive, vous transmettez une copie des données primitives réelles, si vous transmettez un objet, vous transmettez une copie de l'emplacement de mémoire de l'objet. C'est tout ce que tu as à dire. Rien de plus ne fait qu'embrouiller les gens.
Scott Marcus

9

eh bien, il s'agit de «performances» et de «vitesse» et dans le simple mot «gestion de la mémoire» dans un langage de programmation.

en javascript, nous pouvons mettre les valeurs en deux couches: type1 - objectset type2 -tous les autres types de valeurs tels que string& boolean& etc

si vous imaginez la mémoire comme des carrés ci-dessous qui, dans chacun d'eux, une seule valeur type2 peut être enregistrée:

entrez la description de l'image ici

chaque valeur de type2 (vert) est un seul carré tandis qu'une valeur de type1 (bleu) en est un groupe :

entrez la description de l'image ici

le fait est que si vous voulez indiquer une valeur type2, l'adresse est simple mais si vous voulez faire la même chose pour la valeur type1 ce n'est pas facile du tout! :

entrez la description de l'image ici

et dans une histoire plus compliquée:

entrez la description de l'image ici

alors ici les références peuvent nous sauver: entrez la description de l'image ici

alors que la flèche verte ici est une variable typique, la violette est une variable objet, donc parce que la flèche verte (variable typique) n'a qu'une seule tâche (et cela indique une valeur typique), nous n'avons pas besoin de séparer sa valeur de nous déplaçons donc la flèche verte avec la valeur de celle-ci partout où elle va et dans toutes les affectations, fonctions et ainsi de suite ...

mais nous ne pouvons pas faire la même chose avec la flèche violette, nous pouvons vouloir déplacer la cellule 'john' ici ou bien d'autres choses ..., donc la flèche violette restera à sa place et juste les flèches typiques qui lui ont été assignées se déplaceront ...

une situation très déroutante est où vous ne pouvez pas réaliser comment votre variable référencée change, jetons un coup d'œil à un très bon exemple:

let arr = [1, 2, 3, 4, 5]; //arr is an object now and a purple arrow is indicating it
let obj2 = arr; // now, obj2 is another purple arrow that is indicating the value of arr obj
let obj3 = ['a', 'b', 'c'];
obj2.push(6); // first pic below - making a new hand for the blue circle to point the 6
//obj2 = [1, 2, 3, 4, 5, 6]
//arr = [1, 2, 3, 4, 5, 6]
//we changed the blue circle object value (type1-value) and due to arr and obj2 are indicating that so both of them changed
obj2 = obj3; //next pic below - changing the direction of obj2 array from blue circle to orange circle so obj2 is no more [1,2,3,4,5,6] and it's no more about changing anything in it but we completely changed its direction and now obj2 is pointing to obj3
//obj2 = ['a', 'b', 'c'];
//obj3 = ['a', 'b', 'c'];

entrez la description de l'image ici entrez la description de l'image ici


8

C'est un peu plus d'explication pour passer par valeur et passer par référence (JavaScript). Dans ce concept, ils parlent de passer la variable par référence et de passer la variable par référence.

Passer par valeur (type primitif)

var a = 3;
var b = a;

console.log(a); // a = 3
console.log(b); // b = 3

a=4;
console.log(a); // a = 4
console.log(b); // b = 3
  • s'applique à tous les types primitifs en JavaScript (chaîne, nombre, booléen, non défini et null).
  • a est alloué une mémoire (disons 0x001) et b crée une copie de la valeur en mémoire (disons 0x002).
  • Ainsi, la modification de la valeur d'une variable n'affecte pas l'autre, car elles résident toutes deux à deux endroits différents.

Passer par référence (objets)

var c = { "name" : "john" };
var d = c;

console.log(c); // { "name" : "john" }
console.log(d); // { "name" : "john" }

c.name = "doe";

console.log(c); // { "name" : "doe" }
console.log(d); // { "name" : "doe" }
  • Le moteur JavaScript assigne l'objet à la variable cet pointe vers de la mémoire, par exemple (0x012).
  • Lorsque d = c, dans cette étape, dpointe vers le même emplacement (0x012).
  • La modification de la valeur de toute valeur change pour la variable.
  • Les fonctions sont des objets

Cas particulier, passer par référence (objets)

c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
  • L'opérateur égal (=) configure un nouvel espace mémoire ou une nouvelle adresse

Dans votre soi-disant cas spécial, ce n'est pas l'opérateur d'affectation qui provoque l'allocation d'espace mémoire, c'est le littéral objet lui-même. La notation de parenthèse curley provoque la création d'un nouvel objet. La propriété cest définie sur une copie de la référence du nouvel objet.
georgeawg

6

partager ce que je sais des références en JavaScript

En JavaScript, lors de l'affectation d'un objet à une variable, la valeur affectée à la variable est une référence à l'objet:

var a = {
  a: 1,
  b: 2,
  c: 3
};
var b = a;

// b.c is referencing to a.c value
console.log(b.c) // Output: 3
// Changing value of b.c
b.c = 4
// Also changes the value of a.c
console.log(a.c) // Output: 4


1
Il s'agit d'une réponse trop simpliste qui ne dit rien que les réponses antérieures n'ont pas mieux expliqué. Je ne comprends pas pourquoi vous appelez les tableaux comme cas spécial.
Quentin

1
"les objets sont stockés comme références " est trompeur. Je pense que vous voulez dire que lorsque vous affectez un objet à une variable, la valeur attribuée à la variable est une référence à l'objet.
RobG

cela ne résout pas le problème de la mise à jour d'un objet à l'intérieur d'une fonction qui ne met pas à jour l'objet à l'extérieur de la fonction. C'est toute l'image où cela semble fonctionner comme des valeurs au lieu de référence. D'où -1
amaster

@amaster Merci de l'avoir signalé! Pouvez-vous suggérer une modification, s'il vous plaît?
xameeramir

Haha, j'ai essayé ... ma modification suggérée a trop changé et n'a pas été autorisée
amaster

4

Sémantique!! Définir des définitions concrètes rendra nécessairement certaines réponses et commentaires incompatibles car ils ne décrivent pas la même chose même lorsque vous utilisez les mêmes mots et phrases, mais il est essentiel de surmonter la confusion (en particulier pour les nouveaux programmeurs).

Tout d'abord, il existe plusieurs niveaux d'abstraction que tout le monde ne semble pas comprendre. Les programmeurs plus récents qui ont appris sur les langages de 4e ou 5e génération peuvent avoir du mal à se concentrer sur des concepts familiers à l'assemblage ou sur des programmeurs C non organisés par des pointeurs vers des pointeurs vers des pointeurs. Le passage par référence ne signifie pas simplement la possibilité de modifier un objet référencé à l'aide d'une variable de paramètre de fonction.

Variable : concept combiné d'un symbole qui fait référence à une valeur à un emplacement particulier dans la mémoire. Ce terme est généralement trop chargé pour être utilisé seul dans la discussion des détails.

Symbole : chaîne de texte utilisée pour faire référence à une variable (c'est-à-dire le nom de la variable).

Valeur : bits particuliers stockés en mémoire et référencés à l'aide du symbole de variable.

Emplacement de mémoire : où la valeur d'une variable est stockée. (L'emplacement lui-même est représenté par un nombre distinct de la valeur stockée à l'emplacement.)

Paramètre de fonction : variable déclarée dans une définition de fonction, utilisée pour référencer les variables passées à la fonction.

Argument de fonction : Variable en dehors de la fonction qui est transmise à la fonction par l'appelant.

Variable d'objet : variable dont la valeur sous-jacente de base n'est pas "l'objet" lui-même, mais plutôt sa valeur est un pointeur (valeur d'emplacement de mémoire) vers un autre emplacement en mémoire où les données réelles de l'objet sont stockées. Dans la plupart des langages de génération supérieure, l'aspect "pointeur" est effectivement masqué par le dé-référencement automatique dans divers contextes.

Variable primitive : variable dont la valeur EST la valeur réelle. Même ce concept peut être compliqué par la mise en boîte automatique et les contextes similaires à des objets de divers langages, mais l'idée générale est que la valeur de la variable EST la valeur réelle représentée par le symbole de la variable plutôt qu'un pointeur vers un autre emplacement de mémoire.

Les arguments de fonction et les paramètres ne sont pas la même chose. De plus, la valeur d'une variable n'est pas l'objet de la variable (comme l'ont déjà souligné diverses personnes, mais apparemment ignoré). Ces distinctions sont essentielles à une bonne compréhension.

Pass-by-value ou Call-by-sharing (pour les objets): La valeur de l'argument de fonction est COPIÉE vers un autre emplacement mémoire qui est référencé par le symbole de paramètre de la fonction (qu'il soit sur la pile ou sur le tas). En d'autres termes, le paramètre de fonction a reçu une copie de la valeur de l'argument passé ... ET (critique) la valeur de l'argument N'EST JAMAIS MISE À JOUR / MODIFIÉE / MODIFIÉE par la fonction appelante. N'oubliez pas que la valeur d'une variable objet n'est PAS l'objet lui-même, mais plutôt le pointeur vers l'objet, donc le passage d'une variable objet par valeur copie le pointeur vers la variable de paramètre de fonction. La valeur du paramètre de fonction pointe vers exactement le même objet en mémoire. Les données d'objet elles-mêmes peuvent être modifiées directement via le paramètre de fonction, MAIS la valeur de l'argument de fonction N'EST JAMAIS MISE À JOUR, il continuera donc à pointer vers le mêmeobjet tout au long et même après l'appel de fonction (même si les données de son objet ont été modifiées ou si le paramètre de fonction est affecté à un autre objet). Il est incorrect de conclure que l'argument de fonction a été transmis par référence uniquement parce que l'objet référencé peut être mis à jour via la variable de paramètre de fonction.

Appel / Pass-by-reference : La valeur de l'argument de fonction peut / sera mise à jour directement par le paramètre de fonction correspondant. Si cela aide, le paramètre de fonction devient un "alias" efficace pour l'argument - ils se réfèrent effectivement à la même valeur au même emplacement de mémoire. Si un argument de fonction est une variable d'objet, la possibilité de modifier les données de l'objet n'est pas différente de la casse de valeur car le paramètre de fonction pointera toujours vers le même objet que l'argument. Mais dans le cas de la variable objet, si le paramètre de la fonction est défini sur un objet complètement différent, l'argument pointera également vers l'objet différent - cela ne se produit pas dans le cas du passage par valeur.

JavaScript ne passe pas par référence. Si vous lisez attentivement, vous vous rendrez compte que toutes les opinions contraires comprennent mal ce que l'on entend par valeur de passage et ils concluent à tort que la possibilité de mettre à jour les données d'un objet via le paramètre de fonction est synonyme de "valeur de passage".

Clonage / copie d' objet: un nouvel objet est créé et les données de l'objet d'origine sont copiées. Cela peut être une copie complète ou une copie superficielle, mais le fait est qu'un nouvel objet est créé. La création d'une copie d'un objet est un concept distinct de la valeur de passage. Certains langages font la distinction entre l'objet de classe et les structures (ou similaires), et peuvent avoir un comportement différent pour transmettre des variables de différents types. Mais JavaScript ne fait rien de tel automatiquement lors du passage de variables d'objet. Mais l'absence de clonage d'objet automatique ne se traduit pas par un passage par référence.


4

JavaScript transmet les types primitifs par valeur et les types d'objets par référence

Maintenant, les gens aiment se chamailler sans cesse pour savoir si «passer par référence» est la bonne façon de décrire ce que Java et al. en fait. Le point est le suivant:

  1. Passer un objet ne le copie pas.
  2. Un objet passé à une fonction peut voir ses membres modifiés par la fonction.
  3. Une valeur primitive passée à une fonction ne peut pas être modifiée par la fonction. Une copie est faite.

Dans mon livre, cela s'appelle passer par référence.

- Brian Bi - Quels langages de programmation sont passés par référence?


Mise à jour

Voici une réfutation de ceci:

Il n'y a pas de "passe par référence" disponible en JavaScript.


@Amy Parce que cela décrit passer par valeur, pas passer par référence. Cette réponse est bonne et montre la différence: stackoverflow.com/a/3638034/3307720
nasch

@nasch, je comprends la différence. # 1 et # 2 décrivent la sémantique pass-by-ref. # 3 décrit la sémantique passe-par-valeur.
Amy

@Amy 1, 2 et 3 sont tous conformes à la valeur de passage. Pour passer par référence, vous aurez également besoin de 4: attribuer la référence à une nouvelle valeur à l'intérieur de la fonction (avec l'opérateur =) réaffecte également la référence à l'extérieur de la fonction. Ce n'est pas le cas avec Javascript, ce qui le fait passer exclusivement par valeur. Lorsque vous passez un objet, vous passez un pointeur à l'objet et vous passez ce pointeur par valeur.
nasch

Ce n'est généralement pas ce que l'on entend par «passe-par-référence». Vous avez satisfait ma requête et je ne suis pas d'accord avec vous. Merci.
Amy

"Dans mon livre qui s'appelle passer par référence." - Dans chaque livre de compilation, livre d'interprète, livre de théorie du langage de programmation et livre d'informatique jamais écrit, ce n'est pas le cas.
Jörg W Mittag

3

Ma façon simple de comprendre cela ...

  • Lorsque vous appelez une fonction, vous transmettez le contenu (référence ou valeur) des variables d'argument, pas les variables elles-mêmes.

    var var1 = 13;
    var var2 = { prop: 2 };
    
    //13 and var2's content (reference) are being passed here
    foo(var1, var2); 
  • À l'intérieur de la fonction, les variables de paramètre inVar1et inVar2reçoivent le contenu transmis.

    function foo(inVar1, inVar2){
        //changing contents of inVar1 and inVar2 won't affect variables outside
        inVar1 = 20;
        inVar2 = { prop: 7 };
    }
  • Depuis que vous avez inVar2reçu la référence de { prop: 2 }, vous pouvez modifier la valeur de la propriété de l'objet.

    function foo(inVar1, inVar2){
        inVar2.prop = 7; 
    }

3

Passer des arguments à une fonction en JavaScript est analogue à passer des paramètres par valeur de pointeur en C:

/*
The following C program demonstrates how arguments
to JavaScript functions are passed in a way analogous
to pass-by-pointer-value in C. The original JavaScript
test case by @Shog9 follows with the translation of
the code into C. This should make things clear to
those transitioning from C to JavaScript.

function changeStuff(num, obj1, obj2)
{
    num = num * 10;
    obj1.item = "changed";
    obj2 = {item: "changed"};
}

var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num);
console.log(obj1.item);    
console.log(obj2.item);

This produces the output:

10
changed
unchanged
*/

#include <stdio.h>
#include <stdlib.h>

struct obj {
    char *item;
};

void changeStuff(int *num, struct obj *obj1, struct obj *obj2)
{
    // make pointer point to a new memory location
    // holding the new integer value
    int *old_num = num;
    num = malloc(sizeof(int));
    *num = *old_num * 10;
    // make property of structure pointed to by pointer
    // point to the new value
    obj1->item = "changed";
    // make pointer point to a new memory location
    // holding the new structure value
    obj2 = malloc(sizeof(struct obj));
    obj2->item = "changed";
    free(num); // end of scope
    free(obj2); // end of scope
}

int num = 10;
struct obj obj1 = { "unchanged" };
struct obj obj2 = { "unchanged" };

int main()
{
    // pass pointers by value: the pointers
    // will be copied into the argument list
    // of the called function and the copied
    // pointers will point to the same values
    // as the original pointers
    changeStuff(&num, &obj1, &obj2);
    printf("%d\n", num);
    puts(obj1.item);
    puts(obj2.item);
    return 0;
}

1
Je ne pense pas que ce soit le cas en JavaScript: `` `javascript var num = 5;
Danail Nachev

@DanailNachev: Bien que cela puisse être techniquement vrai, la différence n'est observable que pour les objets mutables, contrairement aux primitives ECMAScript.
Jörg W Mittag

3

Pour les avocats en langage de programmation, j'ai parcouru les sections suivantes d'ECMAScript 5.1 (qui est plus facile à lire que la dernière édition), et je suis allé jusqu'à le demander sur la liste de diffusion ECMAScript.

TL; DR : Tout est passé par valeur, mais les propriétés des objets sont des références, et la définition de l'objet fait cruellement défaut dans la norme.

Construction de listes d'arguments

La section 11.2.4 «Listes d'arguments» indique ce qui suit sur la production d'une liste d'arguments composée d'un seul argument:

La production ArgumentList: AssignmentExpression est évaluée comme suit:

  1. Soit ref le résultat de l'évaluation de AssignmentExpression.
  2. Soit arg la valeur GetValue (ref).
  3. Renvoie une liste dont le seul élément est arg.

La section énumère également les cas où la liste d'arguments a 0 ou> 1 arguments.

Ainsi, tout est passé par référence.

Accès aux propriétés des objets

Section 11.2.1 "Accesseurs de propriété"

La production MemberExpression: MemberExpression [Expression] est évaluée comme suit:

  1. Soit baseReference le résultat de l'évaluation de MemberExpression.
  2. Laissez baseValue être GetValue (baseReference).
  3. Soit propertyNameReference le résultat de l'évaluation de l'expression.
  4. Laissez propertyNameValue être GetValue (propertyNameReference).
  5. Appelez CheckObjectCoercible (baseValue).
  6. Soit propertyNameString être ToString (propertyNameValue).
  7. Si la production syntaxique en cours d'évaluation est contenue dans du code en mode strict, laissez strict être vrai, sinon laissez strict être faux.
  8. Renvoie une valeur de type Reference dont la valeur de base est baseValue et dont le nom référencé est propertyNameString et dont l'indicateur de mode strict est strict.

Ainsi, les propriétés des objets sont toujours disponibles comme référence.

Sur référence

Il est décrit dans la section 8.7 «Le type de spécification de référence», que les références ne sont pas de vrais types dans le langage - elles sont uniquement utilisées pour décrire le comportement de la suppression, du typeof et des opérateurs d'affectation.

Définition d '"objet"

Il est défini dans l'édition 5.1 que "Un objet est une collection de propriétés". Par conséquent, nous pouvons en déduire que la valeur de l'objet est la collection, mais quant à la valeur de la collection est mal définie dans la spécification et nécessite un peu d' effort pour la comprendre.


Cela ne cesse de m'étonner combien de personnes sont déroutées par les distinctions entre les arguments passés par valeur, les arguments passés par référence, les opérations sur des objets entiers et les opérations sur leurs propriétés. En 1979, je n'ai pas obtenu mon diplôme en informatique, choisissant plutôt d'ajouter 15 heures ou plus de cours optionnels CS à mon programme de MBA. Néanmoins, il m'est rapidement devenu évident que ma compréhension de ces concepts était au moins aussi bonne que celle de mes collègues titulaires d'un diplôme en informatique ou en mathématiques. Étudiez l'assembleur, et cela deviendra très clair.
David A. Gray,

3

Les documents MDN l'expliquent clairement, sans être trop verbeux:

Les paramètres d'un appel de fonction sont les arguments de la fonction . Les arguments sont passés aux fonctions par valeur . Si la fonction modifie la valeur d'un argument, cette modification n'est pas reflétée globalement ou dans la fonction appelante. Cependant, les références d'objet sont également des valeurs et elles sont spéciales: si la fonction modifie les propriétés de l'objet référencé, cette modification est visible en dehors de la fonction, (...)

Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions#Description


1

Dans un langage de bas niveau, si vous souhaitez passer une variable par référence, vous devez utiliser une syntaxe spécifique dans la création de la fonction:

int myAge = 14;
increaseAgeByRef(myAge);
function increaseAgeByRef(int &age) {
  *age = *age + 1;
}

La &ageest une référence à myAge, mais si vous voulez la valeur, vous devez convertir la référence en utilisant *age.

Javascript est un langage de haut niveau qui effectue cette conversion pour vous. Ainsi, bien que les objets soient passés par référence, le langage convertit le paramètre de référence en valeur. Vous n'avez pas besoin d'utiliser &, sur la définition de la fonction, pour la passer par référence, ni *sur le corps de la fonction, pour convertir la référence en valeur, JS le fait pour vous.

C'est pourquoi lorsque vous essayez de changer un objet à l'intérieur d'une fonction, en remplaçant sa valeur (ie age = {value:5}), le changement ne persiste pas, mais si vous changez ses propriétés (ie age.value = 5), il le fait.

Apprendre encore plus


1

J'ai lu ces réponses plusieurs fois, mais je ne les ai VRAIMENT pas obtenues jusqu'à ce que j'apprenne la définition technique de "Appeler en partageant", comme l'a dit Barbara Liskov.

La sémantique de l'appel par partage diffère de l'appel par référence en ce que les affectations aux arguments de fonction dans la fonction ne sont pas visibles pour l'appelant (contrairement à la sémantique de référence) [citation nécessaire], donc par exemple si une variable a été passée, ce n'est pas possible pour simuler une affectation sur cette variable dans la portée de l'appelant. Cependant, étant donné que la fonction a accès au même objet que l'appelant (aucune copie n'est effectuée), les mutations de ces objets, si les objets sont mutables, au sein de la fonction sont visibles par l'appelant, ce qui peut sembler différent de l'appel par valeur sémantique. Les mutations d'un objet mutable dans la fonction sont visibles par l'appelant car l'objet n'est pas copié ou cloné - il est partagé.

Autrement dit, les références de paramètres sont modifiables si vous accédez à la valeur du paramètre elle-même. En revanche, l'affectation à un paramètre disparaîtra après évaluation et ne sera pas accessible à l'appelant de fonction.


Non, qu'un objet soit modifiable ou non n'est pas vraiment le problème. Tout est toujours transmis par valeur. Cela dépend simplement de ce que vous passez (une valeur ou une référence). Regardez ça .
Scott Marcus

Ce qu'elle décrit, c'est passer une référence BY-VALUE. Il n'y a aucune raison d'introduire une nouvelle terminologie.
Sanjeev

1

Moyen le plus simple

// Copy JS object without reference
var first = {"a":"value1","b":"value2"};
var clone = JSON.parse( JSON.stringify( first ) ); 

var second = ["a","b","c"];
var clone = JSON.parse( JSON.stringify( second ) ); 

JSON.parse( JSON.stringify( obj ) )est un moyen horrible de cloner des objets en profondeur. Non seulement il est non seulement lent, mais il peut également entraîner une perte de données.
D.Pardal

0

J'ai trouvé la méthode extend de la bibliothèque Underscore.js très utile lorsque je veux passer un objet en tant que paramètre qui peut être soit modifié, soit remplacé entièrement.

function replaceOrModify(aObj) {
  if (modify) {

    aObj.setNewValue('foo');

  } else {

   var newObj = new MyObject();
   // _.extend(destination, *sources) 
   _.extend(newObj, aObj);
  }
}

0

L'explication la plus succincte que j'ai trouvée était dans le guide de style AirBNB :

  • Primitives : lorsque vous accédez à un type primitif, vous travaillez directement sur sa valeur

    • chaîne
    • nombre
    • booléen
    • nul
    • indéfini

Par exemple:

var foo = 1,
    bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9
  • Complexe : lorsque vous accédez à un type complexe, vous travaillez sur une référence à sa valeur

    • objet
    • tableau
    • une fonction

Par exemple:

var foo = [1, 2],
    bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

Autrement dit, les types primitifs sont passés par valeur et les types complexes sont passés par référence.


Non, tout est toujours transmis par valeur. Cela dépend simplement de ce que vous passez (une valeur ou une référence). Regardez ça .
Scott Marcus

-1

Je dirais que c'est copie par copie -

Considérez que les arguments et les objets variables sont des objets créés pendant le contexte d'exécution créé au début de l'invocation de la fonction - et votre valeur / référence réelle transmise à la fonction est simplement stockée dans ces arguments + objets variables.

En termes simples, pour les types primitifs, les valeurs sont copiées au début de l'appel de fonction, pour le type d'objet, la référence est copiée.


1
"passe-par-copie" === passe par valeur
Scott Marcus

-1

Il y a une discussion sur l'utilisation du terme "passer par référence" en JavaScript ici , mais pour répondre à votre question:

Un objet est automatiquement passé par référence, sans qu'il soit nécessaire de le déclarer spécifiquement

(D'après l'article mentionné ci-dessus.)


7
L'article lié n'inclut plus ces déclarations et évite d'utiliser complètement «passer par référence».
C Perkins

La valeur est une référence

-2

Un moyen facile de déterminer si quelque chose est "passer par référence" est de savoir si vous pouvez écrire une fonction "swap". Par exemple, en C, vous pouvez faire:

void swap(int *i, int *j)
{
    int t;
    t = *i;
    *i = *j;
    *j = t;
}

Si vous ne pouvez pas faire l'équivalent de cela en JavaScript, ce n'est pas "passer par référence".


21
Ce n'est pas vraiment une référence. Vous passez des pointeurs dans la fonction et ces pointeurs sont passés par valeur. Un meilleur exemple serait l'opérateur & C ++ ou le mot-clé "ref" de C #, les deux sont vraiment passés par référence.
Matt Greer

Encore plus facile, tout est transmis par valeur en JavaScript.
Scott Marcus


-3
  1. variable de type primitif comme chaîne, nombre sont toujours transmis comme valeur de passage.
  2. Array and Object est passé en tant que passage par référence ou passage par valeur en fonction de ces deux conditions.

    • si vous modifiez la valeur de cet objet ou tableau avec un nouvel objet ou tableau, il est passé par valeur.

      object1 = {item: "car"}; array1=[1,2,3];

    ici, vous affectez un nouvel objet ou un tableau à l'ancien. vous ne modifiez pas la valeur de la propriété de l'ancien objet. il est donc transmis par valeur.

    • si vous modifiez une valeur de propriété d'un objet ou d'un tableau, elle est transmise par référence.

      object1.key1= "car"; array1[0]=9;

    ici, vous modifiez une valeur de propriété de l'ancien objet. vous n'affectez pas un nouvel objet ou un tableau à l'ancien. il est donc transmis par référence.

Code

    function passVar(object1, object2, number1) {

        object1.key1= "laptop";
        object2 = {
            key2: "computer"
        };
        number1 = number1 + 1;
    }

    var object1 = {
        key1: "car"
    };
    var object2 = {
        key2: "bike"
    };
    var number1 = 10;

    passVar(object1, object2, number1);
    console.log(object1.key1);
    console.log(object2.key2);
    console.log(number1);

Output: -
    laptop
    bike
    10

1
L'opérateur d'affectation ne doit pas être confondu avec un appel de fonction. Lorsque vous affectez de nouvelles données à une variable existante, le nombre de références des anciennes données diminue et les nouvelles données sont associées à l'ancienne variable. Fondamentalement, la variable finit par pointer vers les nouvelles données. Il en va de même pour les variables de propriété. Étant donné que ces affectations ne sont pas des appels de fonction, elles n'ont rien à voir avec le passage par valeur ou le passage par référence.
John Sonderson

1
Non, tout est toujours transmis par valeur. Cela dépend simplement de ce que vous passez (une valeur ou une référence). Regardez ça .
Scott Marcus

-3
  1. Les primitives (nombre, booléens, etc.) sont passées par valeur.
    • Les cordes sont immuables, donc cela n'a pas vraiment d'importance pour elles.
  2. Les objets sont passés par référence (la référence est passée par valeur).

Non, tout est toujours transmis par valeur. Cela dépend simplement de ce que vous passez (une valeur ou une référence). Regardez ça .
Scott Marcus

Votre deuxième déclaration se contredit.
Jörg W Mittag

-5

Les valeurs simples à l'intérieur des fonctions ne changeront pas ces valeurs en dehors de la fonction (elles sont transmises par valeur), tandis que les valeurs complexes le seront (elles sont transmises par référence).

function willNotChange(x) {

    x = 1;
}

var x = 1000;

willNotChange(x);

document.write('After function call, x = ' + x + '<br>'); // Still 1000

function willChange(y) {

    y.num = 2;
}

var y = {num: 2000};

willChange(y);
document.write('After function call y.num = ' + y.num + '<br>'); // Now 2, not 2000

c'est ridicule, y changera à cause de la portée du niveau fonctionnel, il sera hissé non pas parce qu'il est passé par référence.
Parijat Kalia

Non, tout est toujours transmis par valeur. Cela dépend simplement de ce que vous passez (une valeur ou une référence). Regardez ça .
Scott Marcus
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.