JavaScript è un linguaggio pass-by-reference o pass-by-value?


1405

I tipi primitivi (numero, stringa, ecc.) Vengono passati per valore, ma gli oggetti sono sconosciuti, poiché possono essere entrambi passati per valore (nel caso in cui consideriamo che una variabile che contiene un oggetto sia in realtà un riferimento all'oggetto ) e passati per riferimento (se si considera che la variabile per l'oggetto contiene l'oggetto stesso).

Anche se alla fine non ha molta importanza, voglio sapere qual è il modo corretto di presentare gli argomenti che approvano le convenzioni. C'è un estratto dalla specifica JavaScript, che definisce quale dovrebbe essere la semantica al riguardo?


2
Penso che tu abbia capovolto inavvertitamente le tue definizioni di passato per valore e passato per riferimento ... "passato per valore (nel caso in cui consideriamo che una variabile che contiene un oggetto sia in realtà un riferimento all'oggetto) e passi -da-riferimento (se consideriamo che la variabile per l'oggetto contiene l'oggetto stesso) "
Niko Bellic

5
Sì. Indipendentemente dalla sintassi, in qualsiasi chiamata di funzione in qualsiasi linguaggio di programmazione, pass-by-reference significa che i dati associati alla variabile passata non vengono copiati quando passati alla funzione, e quindi tutte le modifiche apportate dalla funzione alla variabile passata verranno mantenute nel programma al termine della chiamata di funzione. Pass-by-value significa che i dati associati alla variabile vengono effettivamente copiati quando passati alla funzione e qualsiasi modifica apportata da tale funzione a tale variabile andrà persa quando la variabile esce dall'ambito del corpo della funzione quando la funzione ritorna.
John Sonderson,

5
Questa vecchia domanda è alquanto tossica perché la sua risposta fortemente votata non è corretta. JavaScript è rigorosamente pass-by-value .
Punta appuntita

6
@DanailNachev La terminologia è purtroppo confusa. Il fatto è che "passa per valore" e "passa per riferimento" sono termini che precedono molte più moderne funzionalità del linguaggio di programmazione. Le parole "valore" e "riferimento" si riferiscono specificamente al parametro come appare nell'espressione della chiamata di funzione. JavaScript valuta sempre ogni espressione in un elenco di parametri di chiamata di funzione prima di chiamare la funzione, quindi i parametri sono sempre valori. La parte confusa è che i riferimenti agli oggetti sono valori JavaScript comuni. Ciò non lo rende un linguaggio "passa per riferimento", tuttavia.
Punta il

2
@DanailNachev "passa per riferimento" significa specificamente che se si dispone di una var x=3, y=x; f(x); alert(y === x);funzione, è f()possibile effettuare il report degli avvisi falsee non true. In JavaScript non è possibile, quindi non è pass-by-reference. È positivo che sia possibile passare riferimenti a oggetti modificabili, ma non è questo che significa "passare per riferimento". Come ho detto, è un peccato che la terminologia sia così confusa.
Punta il

Risposte:


1599

È interessante in JavaScript. Considera questo esempio:

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);

Questo produce l'output:

10
changed
unchanged
  • Se obj1non fosse affatto un riferimento, la modifica non obj1.itemavrebbe alcun effetto obj1all'esterno della funzione.
  • Se l'argomento fosse un riferimento adeguato, allora tutto sarebbe cambiato. numsarebbe 100e obj2.itemleggerebbe "changed".

Al contrario, la situazione è che l'elemento passato viene passato per valore. Ma l'elemento che viene passato per valore è esso stesso un riferimento. Tecnicamente, questo si chiama call-by-sharing .

In termini pratici, ciò significa che se si modifica il parametro stesso (come con nume obj2), ciò non influirà sull'elemento che è stato inserito nel parametro. Ma se si modificano gli INTERNI del parametro, questo si propagherà indietro (come con obj1).


32
Questo è esattamente lo stesso (o almeno semanticamente) di C #. L'oggetto ha due tipi: Valore (tipi primitivi) e Riferimento.
Peter Lee,

53
Penso che questo sia usato anche in Java: riferimento per valore.
Jpnh,

296
la vera ragione è che all'interno di changeStuff, num, obj1 e obj2 sono riferimenti. Quando si modifica la itemproprietà dell'oggetto a cui fa riferimento obj1, si modifica il valore della proprietà dell'oggetto che era originariamente impostata su "invariato". Quando assegni a obj2 un valore di {item: "cambiato"} stai cambiando il riferimento a un nuovo oggetto (che esce immediatamente dall'ambito quando la funzione esce). Diventa più evidente cosa sta succedendo se si nomina la funzione params cose come numf, obj1f e obj2f. Quindi vedi che i parametri nascondevano i nomi var esterni.
jinglesthula,

13
@BartoNaz Non proprio. Quello che vuoi è passare il riferimento per riferimento, invece di passare il riferimento per valore. Ma JavaScript passa sempre il riferimento per valore, proprio come passa tutto il resto per valore. (Per fare un confronto, C # ha un comportamento pass-reference-by-value simile a JavaScript e Java, ma consente di specificare pass-reference-by-reference con la refparola chiave.) Solitamente si dovrebbe semplicemente avere la funzione restituire il nuovo oggetto, e fare l'assegnazione nel punto in cui si chiama la funzione. Ad esempio, foo = GetNewFoo();invece diGetNewFoo(foo);
Tim Goodman il

56
Sebbene questa risposta sia la più popolare, può essere leggermente confusa perché afferma "Se fosse puro passare per valore". JavaScript è puro valore per passaggio. Ma il valore che viene passato è un riferimento. Ciò non è affatto limitato al passaggio dei parametri. Potresti semplicemente copiare la variabile var obj1 = { item: 'unchanged' }; var obj2 = obj1; obj2.item = 'changed';e osservare lo stesso effetto del tuo esempio. Pertanto, mi riferisco personalmente alla risposta di Tim Goodman
Chiccodoro,

476

Passa sempre per valore, ma per gli oggetti il ​​valore della variabile è un riferimento. Per questo motivo, quando si passa un oggetto e si cambiano i suoi membri , tali modifiche persistono al di fuori della funzione. Questo fa sembrare passare per riferimento. Ma se in realtà cambi il valore della variabile oggetto vedrai che la modifica non persiste, dimostrando che passa davvero per valore.

Esempio:

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 */

Produzione:

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

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

14
@daylight: in realtà, ti sbagli; se fosse passato da const ref nel tentativo di fare changeObject avrebbe causato un errore, piuttosto che fallire. Prova ad assegnare un nuovo valore a un riferimento const in C ++ e il compilatore lo rifiuta. In termini utente, questa è la differenza tra passa per valore e passa per riferimento const.
deworde,

5
@daylight: non è un riferimento costante. In changeObject, ho cambiato xper contenere un riferimento al nuovo oggetto. x = {member:"bar"};è equivalente a x = new Object(); x.member = "bar"; quello che sto dicendo vale anche per C #, comunque.
Tim Goodman,

2
@daylight: per C #, è possibile vedere questo dal di fuori della funzione, se si utilizza la refparola chiave è possibile passare il riferimento di riferimento (al posto del default di passare il riferimento per valore), e quindi il passaggio a punto da una new Object() sarà persistere .
Tim Goodman,

11
@adityamenon È difficile rispondere "perché", ma vorrei notare che i progettisti di Java e C # hanno fatto una scelta simile; questo non è solo un po 'di stranezza JavaScript. In realtà, è molto coerente con il pass-by-value, la cosa che lo rende confuso per le persone è che un valore può essere un riferimento. Non è molto diverso dal passare un puntatore (in base al valore) in C ++ e quindi dereferenziarlo per impostare i membri. Nessuno sarebbe sorpreso che quel cambiamento persista. Ma poiché questi linguaggi allontanano il puntatore e fanno silenziosamente la dereferenziazione per te, le persone si confondono.
Tim Goodman,

41
In altre parole, la cosa confusa qui non è pass-by-value / pass-by-reference. Tutto è pass-per-valore, punto e basta. La cosa confusa è che non è possibile passare un oggetto, né è possibile memorizzare un oggetto in una variabile. Ogni volta che pensi di farlo, in realtà stai passando o memorizzando un riferimento a quell'oggetto. Ma quando vai ad accedere ai suoi membri, succede una silenziosa dereferenziazione, che perpetua la finzione che la tua variabile contenesse l'oggetto reale.
Tim Goodman,

150

La variabile non "trattiene" l'oggetto; contiene un riferimento. È possibile assegnare quel riferimento a un'altra variabile e ora entrambi fanno riferimento allo stesso oggetto. Passa sempre per valore (anche quando quel valore è un riferimento ...).

Non è possibile modificare il valore trattenuto da una variabile passata come parametro, cosa che sarebbe possibile se JavaScript supportasse il passaggio per riferimento.


2
Questo mi confonde solo un po '. Non è passare un riferimento pass-by-reference?

8
L'autore indica che passando un riferimento, si passa un valore di riferimento (un altro modo di pensarlo è passare il valore dell'indirizzo di memoria). Ecco perché, se si dichiara nuovamente l'oggetto, l'originale non cambia, poiché si sta creando un nuovo oggetto in una posizione di memoria diversa. Se si modifica una proprietà, l'oggetto originale cambia perché è stato modificato nella posizione di memoria originale (che non è stata riassegnata).
Huy-Anh Hoang,

113

I miei due centesimi ... Questo è il modo in cui lo capisco. (Sentiti libero di correggermi se sbaglio)

È tempo di buttare via tutto ciò che sai sul passaggio per valore / riferimento.

Perché in JavaScript, non importa se viene passato per valore o per riferimento o altro. Ciò che conta è la mutazione vs assegnazione dei parametri passati in una funzione.

OK, lasciami fare del mio meglio per spiegare cosa intendo. Diciamo che hai alcuni oggetti.

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

Quello che abbiamo fatto è "assegnazione" ... Abbiamo assegnato 2 oggetti vuoti separati alle variabili "oggetto1" e "oggetto2".

Ora, diciamo che ci piace meglio object1 ... Quindi, "assegniamo" una nuova variabile.

var favoriteObject = object1;

Successivamente, per qualsiasi motivo, decidiamo che ci piace meglio l'oggetto 2. Quindi, facciamo semplicemente un piccolo riassegnazione.

favoriteObject = object2;

Non è successo nulla all'oggetto1 o all'oggetto2. Non abbiamo modificato alcun dato. Tutto ciò che abbiamo fatto è stato riassegnare il nostro oggetto preferito. È importante sapere che object2 e favoriteObject sono entrambi assegnati allo stesso oggetto. Possiamo cambiare quell'oggetto tramite una di quelle variabili.

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

OK, ora diamo un'occhiata alle primitive come le stringhe per esempio

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

Ancora una volta, scegliamo un preferito.

var favoriteString = string1;

Entrambe le nostre variabili favoriteString e string1 sono assegnate a "Hello world". E se volessimo cambiare la nostra stringa preferita ??? Cosa accadrà???

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

Uh oh .... Cosa è successo. Non abbiamo potuto cambiare string1 cambiando favoriteString ... Perché ?? Perché non abbiamo cambiato il nostro oggetto stringa . Tutto ciò che abbiamo fatto è stato "RE ASSIGN" la variabile favoriteString in una nuova stringa. Questo essenzialmente lo ha disconnesso da string1. Nell'esempio precedente, quando abbiamo rinominato il nostro oggetto, non abbiamo assegnato nulla. (Beh, non alla variabile stessa , ... tuttavia, abbiamo assegnato la proprietà name a una nuova stringa.) Invece, abbiamo semplicemente mutato l'oggetto che mantiene le connessioni tra le 2 variabili e gli oggetti sottostanti. (Anche se avessimo voluto modificare o mutare l'oggetto stringa stesso , non avremmo potuto, perché le stringhe sono in realtà immutabili in JavaScript.)

Ora, passiamo alle funzioni e passiamo i parametri .... Quando chiami una funzione e passi un parametro, quello che stai essenzialmente facendo è un "incarico" a una nuova variabile, e funziona esattamente come se ti assegnassi semplicemente usando il segno uguale (=).

Prendi questi esempi.

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'

Ora, la stessa cosa, ma con una funzione

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, ora diamo alcuni esempi usando gli oggetti invece ... prima, senza la funzione.

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';

Ora, la stessa cosa, ma con una chiamata di funzione

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, se leggi tutto questo post, forse ora hai una migliore comprensione di come funzionano le chiamate di funzione in JavaScript. Non importa se qualcosa viene passato per riferimento o per valore ... Ciò che conta è assegnazione vs mutazione.

Ogni volta che passi una variabile a una funzione, stai "assegnando" a qualunque sia il nome della variabile parametro, proprio come se avessi usato il segno uguale (=).

Ricorda sempre che il segno di uguale (=) significa assegnazione. Ricorda sempre che passare un parametro a una funzione in JavaScript significa anche assegnare. Sono uguali e le 2 variabili sono collegate esattamente allo stesso modo (vale a dire che non lo sono, a meno che non si contino che sono assegnate allo stesso oggetto).

L'unica volta in cui "la modifica di una variabile" influenza una variabile diversa è quando l'oggetto sottostante viene mutato (nel qual caso non è stata modificata la variabile, ma l'oggetto stesso.

Non ha senso fare una distinzione tra oggetti e primitive, perché funziona esattamente come se non si avesse una funzione e si utilizzasse semplicemente il segno uguale per assegnare a una nuova variabile.

L'unico gotcha è quando il nome della variabile che passi nella funzione è uguale al nome del parametro della funzione. Quando ciò accade, devi trattare il parametro all'interno della funzione come se fosse una variabile completamente nuova privata alla funzione (perché lo è)

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
Per tutti i programmatori C, pensa a char *. foo(char *a){a="hello";} non fa nulla, ma se lo fai foo(char *a){a[0]='h';a[1]='i';a[2]=0;}viene cambiato all'esterno perché aè una posizione di memoria passata per valore che fa riferimento a una stringa (array di caratteri). Il passaggio di strutture (simili agli oggetti js) per valore in C è consentito, ma non raccomandato. JavaScript applica semplicemente queste best practice e nasconde l'innesto non necessario e solitamente indesiderato ... e sicuramente facilita la lettura.
technosaurus,

2
Questo è corretto: i termini pass-by-value e pass-by-reference hanno significati nella progettazione del linguaggio di programmazione e quei significati non hanno nulla a che fare con la mutazione dell'oggetto. È tutto su come funzionano i parametri di funzione.
Punta il

2
Ora che capisco che obj1 = obj2 significa che sia obj1 che obj2 stanno puntando alla stessa posizione di riferimento, e se modifico gli interni di obj2, facendo riferimento a obj1 esporranno gli stessi interni. Come faccio a copiare un oggetto in modo tale che quando faccio source = { "id":"1"}; copy = source /*this is wrong*/; copy.id="2"quella fonte è ancora {"id": "1"}?
Machtyn,

1
Ho pubblicato un'altra risposta con definizioni tradizionali per ridurre la confusione. Le definizioni tradizionali di "pass-by-value" e "pass-by-reference" sono state definite nel giorno dei puntatori di memoria prima del dereferenziamento automatico. Si comprendeva perfettamente che il valore di una variabile oggetto era in realtà la posizione del puntatore della memoria, non l'oggetto. Sebbene la discussione sull'assegnazione rispetto alla mutazione sia forse utile, non è necessario eliminare i termini tradizionali né le loro definizioni. Mutazione, assegnazione, pass-by-value, pass-by-reference, ecc. Non devono contraddirsi.
C Perkins,

"Numero" è anche un "immutabile"?
ebram khalil,

72

Considera quanto segue:

  1. Le variabili sono puntatori a valori in memoria.
  2. La riassegnazione di una variabile punta semplicemente quel puntatore su un nuovo valore.
  3. La riassegnazione di una variabile non influirà mai sulle altre variabili che puntavano allo stesso oggetto

Quindi, dimentica di "passa per riferimento / valore" non rimanere bloccato su "passa per riferimento / valore" perché:

  1. I termini vengono utilizzati solo per descrivere il comportamento di una lingua, non necessariamente l'implementazione sottostante effettiva. Come risultato di questa astrazione, si perdono dettagli critici che sono essenziali per una spiegazione decente, il che porta inevitabilmente alla situazione attuale in cui un singolo termine non descrive adeguatamente il comportamento effettivo e devono essere fornite informazioni supplementari
  2. Questi concetti non erano stati originariamente definiti con l'intento di descrivere javascript in particolare e quindi non mi sento in dovere di usarli quando si aggiungono solo alla confusione.

Per rispondere alla tua domanda: i puntatori vengono passati.


// 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'

Alcuni commenti finali:

  • È allettante pensare che le primitive siano imposte da regole speciali mentre gli oggetti non lo sono, ma le primitive sono semplicemente la fine della catena di puntatori.
  • Come ultimo esempio, considera perché un tentativo comune di cancellare un array non funziona come previsto.


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

Domande di follow-up per credito extra;) Come funziona la raccolta dei rifiuti? Se ciclo una variabile attraverso un milione di {'George', 1}valori, ma ne uso solo uno alla volta, come vengono gestiti gli altri? E cosa succede quando assegno una variabile al valore di un'altra variabile? Sto quindi indicando un puntatore o puntando la punta dell'operando destro? Fa var myExistingVar = {"blah", 42}; var obj = myExistingVar;risultato objindicando {"blah", 42}, o myExistingVar?
Michael Hoffmann,

@MichaelHoffmann Questi meritano le loro domande SO e probabilmente hanno già risposto meglio di quanto io possa gestire. Detto questo, 1)ho eseguito un profilo di memoria negli strumenti di sviluppo del browser per una funzione di loop come quella che hai descritto e ho visto picchi nell'uso della memoria durante il processo di loop. Ciò sembrerebbe indicare che nuovi oggetti identici vengono effettivamente creati in ogni iterazione del ciclo. Quando all'improvviso cadono le punte, il cestino della spazzatura ha appena ripulito un gruppo di questi oggetti inutilizzati.
Geg

1
@MichaelHoffmann 2)Per quanto riguarda qualcosa di simile var a = b, javascript non fornisce un meccanismo per usare i puntatori e quindi una variabile non può mai puntare a un puntatore (come puoi fare in C), sebbene il motore javascript sottostante li indubbiamente li usi. Quindi ... var a = bindicherà a"la punta dell'operando destro"
geg

Ho fatto la domanda n. 1 qui (in particolare su Chrome perché l'implementazione è probabilmente diversa in ogni browser) stackoverflow.com/q/42778439/539997 e sto ancora cercando di pensare a come pronunciare la domanda n. 2. Qualsiasi aiuto è apprezzato.
Michael Hoffmann,

1
Non è necessario dimenticare "passa per riferimento / valore" ! Questi termini hanno significati storici che descrivono esattamente ciò che si tenta di descrivere. Se eliminiamo i termini e le definizioni storiche e diventiamo troppo pigri per apprendere cosa significassero in origine, perdiamo la capacità di comunicare efficacemente tra generazioni. Non ci sarebbe modo di discutere delle differenze tra lingue e sistemi diversi. Invece, i nuovi programmatori devono imparare e comprendere i termini tradizionali e perché e da dove provengono. Altrimenti, perdiamo collettivamente conoscenza e comprensione.
C Perkins,

24

Un oggetto esterno a una funzione viene passato in una funzione fornendo un riferimento all'oggetto esterno.

Quando si utilizza quel riferimento per manipolarne l'oggetto, l'oggetto esterno viene quindi influenzato. Tuttavia, se all'interno della funzione hai deciso di puntare il riferimento a qualcos'altro, non hai influenzato affatto l'oggetto esterno, perché tutto ciò che hai fatto è stato reindirizzare il riferimento a qualcos'altro.


20

Pensala in questo modo: passa sempre per valore. Tuttavia, il valore di un oggetto non è l'oggetto stesso, ma un riferimento a quell'oggetto.

Ecco un esempio, passando un numero (un tipo primitivo)

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

Ripetendolo con un oggetto si ottengono risultati diversi:

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 altro esempio:

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

Una spiegazione molto dettagliata sulla copia, il passaggio e il confronto per valore e per riferimento è in questo capitolo del libro "JavaScript: la guida definitiva" .

Prima di abbandonare l'argomento della manipolazione di oggetti e matrici per riferimento, è necessario chiarire un punto della nomenclatura.

La frase "passa per riferimento" può avere diversi significati. Per alcuni lettori, la frase si riferisce a una tecnica di invocazione di funzioni che consente a una funzione di assegnare nuovi valori ai suoi argomenti e di rendere visibili quei valori modificati all'esterno della funzione. Questo non è il modo in cui il termine è usato in questo libro.

Qui intendiamo semplicemente che un riferimento a un oggetto o a una matrice - non l'oggetto stesso - viene passato a una funzione. Una funzione può utilizzare il riferimento per modificare le proprietà dell'oggetto o degli elementi dell'array. Ma se la funzione sovrascrive il riferimento con un riferimento a un nuovo oggetto o array, tale modifica non è visibile al di fuori della funzione.

I lettori che hanno familiarità con l'altro significato di questo termine possono preferire dire che gli oggetti e le matrici vengono passati per valore, ma il valore che viene passato è in realtà un riferimento piuttosto che l'oggetto stesso.


Wow, questo è incredibilmente confuso. Chi nella loro mente giusta definirebbe un termine ben definito per indicare l' esatto contrario e quindi usarlo in quel modo? Non c'è da stupirsi che così tante risposte qui su questa domanda siano così confuse.
Jörg W Mittag,

16

JavaScript è sempre pass-by-value ; tutto è di tipo valore.

Gli oggetti sono valori e le funzioni membro degli oggetti sono valori stessi (ricordare che le funzioni sono oggetti di prima classe in JavaScript). Inoltre, per quanto riguarda il concetto che tutto in JavaScript è un oggetto ; questo è sbagliato. Stringhe, simboli, numeri, valori booleani, null e indefiniti sono primitivi .

A volte possono sfruttare alcune funzioni e proprietà dei membri ereditate dai loro prototipi di base, ma questo è solo per comodità. Non significa che sono oggetti stessi. Prova quanto segue per riferimento:

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

In entrambi gli avvisi troverai che il valore non è definito.


12
-1, non sempre passa per valore. Da MDC: "Se si passa un oggetto (ovvero un valore non primitivo, come Array o un oggetto definito dall'utente) come parametro, un riferimento all'oggetto viene passato alla funzione."
Nick,

37
@Nick: passa sempre per valore. Periodo. Un riferimento all'oggetto viene passato per valore alla funzione. Questo non passa per riferimento. "Passa per riferimento" potrebbe quasi essere pensato come passare la variabile stessa, piuttosto che il suo valore; qualsiasi modifica apportata dalla funzione all'argomento (inclusa la sua sostituzione con un oggetto completamente diverso!) verrebbe riflessa nel chiamante. Quest'ultimo bit non è possibile in JS, perché JS non passa per riferimento - passa riferimenti per valore. La distinzione è sottile, ma piuttosto importante per comprenderne i limiti.
cHao,

1
Per i futuri stacker ... Informazioni su questo tuo riferimento: x = "teste"; x.foo = 12;ecc. Solo perché una proprietà non è persistente non significa che non sia un oggetto. Come dice MDN: in JavaScript, quasi tutto è un oggetto. Tutti i tipi primitivi tranne null e indefinito vengono trattati come oggetti. Possono essere assegnate proprietà (le proprietà assegnate di alcuni tipi non sono persistenti) e hanno tutte le caratteristiche degli oggetti. link
slacktracer

9
MDN è un wiki modificato dall'utente ed è sbagliato lì. Il riferimento normativo è ECMA-262. Vedere S. 8 "Il tipo di specifica di riferimento", che spiega come vengono risolti i riferimenti, e anche 8.12.5 "[[Put]]", che viene utilizzato per spiegare AssignmentExpression a un riferimento e, per la coesione di oggetti 9.9 ToObject. Per i valori primitivi, Michael ha già spiegato cosa fa ToObject, come nelle specifiche. Ma vedi anche s. 4.3.2 valore primitivo.
Garrett,

1
@WonderLand: No, non lo è. Le persone che non sono mai state in grado di passare per riferimento potrebbero non comprendere mai le differenze tra passaggio per riferimento e passaggio di un riferimento per valore. Ma sono lì e contano. Non mi interessa disinformare le persone solo perché sembra più facile.
cHao,

12

In JavaScript, il tipo di valore controlla solo se quel valore verrà assegnato da copia valore o copia riferimento .

I valori primitivi vengono sempre assegnati / passati da value-copy :

  • null
  • undefined
  • corda
  • numero
  • booleano
  • simbolo in ES6

I valori composti vengono sempre assegnati / passati dalla copia di riferimento

  • oggetti
  • array
  • funzione

Per esempio

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]

Nel frammento precedente, poiché 2è una primitiva scalare, acontiene una copia iniziale di quel valore e gli bviene assegnata un'altra copia del valore. Quando si modifica b, non si modifica in alcun modo il valore a.

Ma entrambi ce dsono riferimenti separati allo stesso valore condiviso [1,2,3], che è un valore composto. È importante notare che né cdpiù né più "possiede" il [1,2,3]valore - entrambi sono solo riferimenti uguali al valore. Pertanto, quando si utilizza uno dei due riferimenti per modificare ( .push(4)) il arrayvalore condiviso effettivo stesso, influisce solo sull'unico valore condiviso ed entrambi i riferimenti faranno riferimento al valore appena modificato [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]

Quando eseguiamo il compito b = [4,5,6], non stiamo facendo assolutamente nulla per influire su dove afa ancora riferimento ( [1,2,3]). Per farlo, bdovrebbe essere un puntatore apiuttosto che un riferimento a array- ma in JS non esiste tale capacità!

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]

Quando passiamo l'argomento a, assegna una copia del ariferimento a x. xe asono riferimenti separati che puntano allo stesso [1,2,3]valore. Ora, all'interno della funzione, possiamo usare quel riferimento per mutare il valore stesso ( push(4)). Ma quando eseguiamo l'assegnazione x = [4,5,6], ciò non influisce in alcun modo sul punto in cui apunta il riferimento iniziale - indica ancora il valore (ora modificato) [1,2,3,4].

Per passare efficacemente un valore composto (come un array) per copia valore, è necessario crearne una copia manualmente, in modo che il riferimento passato non punti ancora all'originale. Per esempio:

foo( a.slice() );

Valore composto (oggetto, matrice, ecc.) Che può essere passato dalla copia di riferimento

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

var obj = {
    a: 2
};

foo( obj );

obj.a; // 42

Qui, objfunge da wrapper per la proprietà primitiva scalare a. Quando passato a foo(..), una copia del objriferimento viene passata e impostata sul wrapperparametro. Ora possiamo usare il wrapperriferimento per accedere all'oggetto condiviso e aggiornarne la proprietà. Al termine della funzione, obj.avedrà il valore aggiornato 42.

fonte


Prima si afferma "I valori composti sono sempre assegnati / passati dalla copia di riferimento", quindi si afferma "assegna una copia del riferimento a a x". Nel caso di quello che si chiama un "valore composto", il valore della variabile effettiva È il riferimento (ovvero il puntatore di memoria). Proprio come hai spiegato, il riferimento viene copiato ... quindi viene copiato il valore della variabile , sottolineando nuovamente che il RIFERIMENTO È IL VALORE. Ciò significa che JavaScript è pass-by-value per tutti i tipi. Pass-by-value significa passare una copia del valore delle variabili. Non importa che il valore sia un riferimento a un oggetto / array.
C Perkins,

Introduci una nuova terminologia (copia-valore / copia-riferimento) e ciò rende le cose più complesse. Ci sono solo copie, punto. Se passi una primitiva hai passato una copia dei dati primitivi reali, se passi un oggetto, hai passato una copia della posizione di memoria dell'oggetto. Questo è tutto ciò che devi dire. Qualcosa di più confonde ulteriormente le persone.
Scott Marcus,

9

beh, si tratta di "prestazioni" e "velocità" e in parole semplici "gestione della memoria" in un linguaggio di programmazione.

in JavaScript possiamo mettere i valori in due livelli: type1 - objectse type2 - tutti gli altri tipi di valore come string& boolean& etc

se immagini la memoria come sotto quadrati che in ognuno di essi può essere salvato solo un valore di tipo2:

inserisci qui la descrizione dell'immagine

ogni valore di tipo2 (verde) è un singolo quadrato mentre un valore di tipo1 (blu) ne costituisce un gruppo :

inserisci qui la descrizione dell'immagine

il punto è che se vuoi indicare un valore type2, l'indirizzo è semplice ma se vuoi fare la stessa cosa per il valore type1 non è affatto facile! :

inserisci qui la descrizione dell'immagine

e in una storia più complicata:

inserisci qui la descrizione dell'immagine

quindi qui i riferimenti possono salvarci: inserisci qui la descrizione dell'immagine

mentre la freccia verde qui è una variabile tipica, quella viola è una variabile oggetto, quindi poiché la freccia verde (variabile tipica) ha un solo compito (e questo indica un valore tipico) non abbiamo bisogno di separare il suo valore da così spostiamo la freccia verde con il valore di quella ovunque vada e in tutti gli incarichi, funzioni e così via ...

ma non possiamo fare la stessa cosa con la freccia viola, potremmo voler spostare la cella "john" qui o molte altre cose ..., quindi la freccia viola si attaccherà al suo posto e si sposteranno solo le frecce tipiche che le sono state assegnate ...

una situazione molto confusa è quella in cui non riesci a capire come cambia la tua variabile referenziata, diamo un'occhiata a un ottimo esempio:

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'];

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine


8

Questa è una spiegazione in più per passare per valore e passare per riferimento (JavaScript). In questo concetto, stanno parlando del passaggio della variabile per riferimento e del passaggio della variabile per riferimento.

Passa per valore (tipo primitivo)

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
  • si applica a tutti i tipi primitivi in ​​JavaScript (stringa, numero, booleano, non definito e null).
  • a è allocata una memoria (diciamo 0x001) e b crea una copia del valore in memoria (diciamo 0x002).
  • Pertanto, la modifica del valore di una variabile non influisce sull'altra, poiché entrambe risiedono in due posizioni diverse.

Passa per riferimento (oggetti)

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" }
  • Il motore JavaScript assegna l'oggetto alla variabile ce punta ad un po 'di memoria, diciamo (0x012).
  • Quando d = c, in questo passaggio dpunta nella stessa posizione (0x012).
  • Modifica del valore di qualsiasi valore di modifica per entrambe le variabili.
  • Le funzioni sono oggetti

Caso speciale, passa per riferimento (oggetti)

c = {"name" : "jane"};
console.log(c); // { "name" : "jane" }
console.log(d); // { "name" : "doe" }
  • L'operatore uguale (=) imposta nuovo spazio o indirizzo di memoria

Nel tuo cosiddetto caso speciale, non è l'operatore di assegnazione che causa l'allocazione dello spazio di memoria, è l' oggetto stesso letterale . La notazione della parentesi graffa provoca la creazione di un nuovo oggetto. La proprietà cè impostata su una copia del riferimento del nuovo oggetto.
georgeawg,

6

condividendo ciò che so di riferimenti in JavaScript

In JavaScript, quando si assegna un oggetto a una variabile, il valore assegnato alla variabile è un riferimento all'oggetto:

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
Questa è una risposta eccessivamente semplicistica che non dice nulla che le risposte precedenti non hanno spiegato meglio. Sono confuso sul perché definisci array come un caso speciale.
Quentin,

1
"Gli oggetti sono memorizzati come riferimenti " è fuorviante. Quello che penso che intendi è che quando si assegna un oggetto a una variabile, il valore assegnato alla variabile è un riferimento all'oggetto.
RobG,

ciò non risolve il problema dell'aggiornamento di un oggetto all'interno di una funzione che non aggiorna l'oggetto al di fuori della funzione. Questa è l'intera immagine in cui sembra funzionare come valori anziché come riferimento. Quindi -1
amaster

@amaster Grazie per averlo sottolineato! Puoi suggerire una modifica, per favore?
xameeramir,

Haha, ho provato ... la mia modifica suggerita cambiato troppo AMD non è stato permesso
APrincipale

4

Semantica!! L'impostazione di definizioni concrete renderà necessariamente alcune risposte e commenti incompatibili poiché non descrivono la stessa cosa anche quando si usano le stesse parole e frasi, ma è fondamentale superare la confusione (specialmente per i nuovi programmatori).

Prima di tutto, ci sono più livelli di astrazione che non tutti sembrano cogliere. I programmatori più recenti che hanno appreso sui linguaggi di quarta o quinta generazione potrebbero avere difficoltà ad avvolgere la propria mente attorno a concetti familiari all'assemblatore o ai programmatori C non graduali da puntatori a puntatori a puntatori. Pass-by-reference non significa semplicemente la possibilità di cambiare un oggetto referenziato usando una variabile di parametro di funzione.

Variabile : concetto combinato di un simbolo che fa riferimento a un valore in una determinata posizione nella memoria. Questo termine è di solito troppo caricato per essere usato da solo nella discussione dei dettagli.

Simbolo : stringa di testo utilizzata per fare riferimento alla variabile (ovvero il nome della variabile).

Valore : bit particolari memorizzati in memoria e referenziati usando il simbolo della variabile.

Posizione memoria : dove è memorizzato il valore di una variabile. (La posizione stessa è rappresentata da un numero separato dal valore memorizzato nella posizione.)

Parametro funzione : variabile dichiarata in una definizione di funzione, utilizzata per fare riferimento alle variabili passate alla funzione.

Argomento della funzione : variabile esterna alla funzione che viene passata alla funzione dal chiamante.

Variabile oggetto : variabile il cui valore di base sottostante non è "l'oggetto" stesso, ma il suo valore è un puntatore (valore della posizione di memoria) verso un'altra posizione nella memoria in cui sono memorizzati i dati effettivi dell'oggetto. Nella maggior parte dei linguaggi di generazione superiore, l'aspetto "puntatore" è effettivamente nascosto dal de-referenziamento automatico in vari contesti.

Variabile primitiva : variabile il cui valore è il valore effettivo. Anche questo concetto può essere complicato da auto-boxing e contesti simili a oggetti di vari linguaggi, ma l'idea generale è che il valore della variabile sia il valore effettivo rappresentato dal simbolo della variabile piuttosto che un puntatore a un'altra posizione di memoria.

Gli argomenti e i parametri delle funzioni non sono la stessa cosa. Inoltre, il valore di una variabile non è l'oggetto della variabile (come già sottolineato da varie persone, ma apparentemente ignorato). Queste distinzioni sono fondamentali per una corretta comprensione.

Pass-by-value o Call-by-sharing (per oggetti): Il valore dell'argomento della funzione è COPIATO in un'altra posizione di memoria a cui fa riferimento il simbolo del parametro della funzione (indipendentemente dal fatto che si trovi nello stack o nell'heap). In altre parole, il parametro della funzione ha ricevuto una copia del valore dell'argomento passato ... E (critico) il valore dell'argomento NON È MAI AGGIORNATO / ALTERATO / MODIFICATO dalla funzione chiamante. Ricorda, il valore di una variabile oggetto NON è l'oggetto stesso, ma piuttosto il puntatore all'oggetto, quindi il passaggio di una variabile oggetto per valore copia il puntatore nella variabile del parametro funzione. Il valore del parametro funzione punta allo stesso oggetto esatto in memoria. I dati dell'oggetto stesso possono essere modificati direttamente tramite il parametro della funzione, MA il valore dell'argomento della funzione NON È MAI AGGIORNATO, quindi continuerà a puntare al stessooggetto durante e anche dopo la chiamata della funzione (anche se i dati del suo oggetto sono stati modificati o se al parametro della funzione è stato assegnato un oggetto completamente diverso). Non è corretto concludere che l'argomento della funzione sia stato passato per riferimento solo perché l'oggetto a cui si fa riferimento è aggiornabile tramite la variabile del parametro della funzione.

Call / Pass-by-reference : il valore dell'argomento della funzione può / sarà aggiornato direttamente dal parametro della funzione corrispondente. Se aiuta, il parametro della funzione diventa un "alias" efficace per l'argomento: si riferiscono effettivamente allo stesso valore nella stessa posizione di memoria. Se un argomento della funzione è una variabile oggetto, la possibilità di modificare i dati dell'oggetto non è diversa dal caso del valore pass-by poiché il parametro funzione punterà comunque allo stesso oggetto dell'argomento. Ma nel caso della variabile oggetto, se il parametro della funzione è impostato su un oggetto completamente diverso, anche l'argomento punterà anche al diverso oggetto - questo non accade nel caso del valore pass-by.

JavaScript non passa per riferimento. Se leggi attentamente, ti renderai conto che tutte le opinioni contrarie fraintendono ciò che si intende per valore per passaggio e concludono erroneamente che la capacità di aggiornare i dati di un oggetto tramite il parametro funzione è sinonimo di "valore per passaggio".

Clone / copia oggetto: viene creato un nuovo oggetto e vengono copiati i dati dell'oggetto originale. Questa può essere una copia profonda o superficiale, ma il punto è che viene creato un nuovo oggetto. La creazione di una copia di un oggetto è un concetto separato dal valore pass-by. Alcuni linguaggi distinguono tra oggetto di classe e strutture (o simili) e possono avere comportamenti diversi per il passaggio di variabili di diversi tipi. Ma JavaScript non fa nulla di simile automaticamente quando si passano le variabili oggetto. Ma l'assenza della clonazione automatica degli oggetti non si traduce in pass-by-reference.


4

JavaScript passa i tipi primitivi per valore e i tipi di oggetto per riferimento

Ora, alla gente piace litigare all'infinito sul fatto che "passare per riferimento" sia il modo corretto di descrivere ciò che Java e altri. davvero. Il punto è questo:

  1. Il passaggio di un oggetto non copia l'oggetto.
  2. Un oggetto passato a una funzione può avere i suoi membri modificati dalla funzione.
  3. Un valore di base passato a una funzione non può essere modificato dalla funzione. Viene fatta una copia.

Nel mio libro si chiama passare per riferimento.

- Brian Bi - Quali linguaggi di programmazione sono passati per riferimento?


Aggiornare

Ecco una confutazione a questo:

Non esiste un "pass per riferimento" disponibile in JavaScript.


@Amy Perché sta descrivendo il passaggio per valore, non per riferimento. Questa risposta è una buona che mostra la differenza: stackoverflow.com/a/3638034/3307720
Nasch

@nasch Capisco la differenza. # 1 e # 2 descrivono la semantica pass-by-ref. # 3 descrive la semantica pass-by-value.
Amy,

@Amy 1, 2 e 3 sono tutti coerenti con il passaggio per valore. Per avere il passaggio per riferimento è necessario anche 4: assegnare il riferimento a un nuovo valore all'interno della funzione (con l'operatore =) riassegna anche il riferimento all'esterno della funzione. Questo non è il caso di Javascript, facendolo passare esclusivamente per valore. Quando si passa un oggetto, si passa un puntatore all'oggetto e si passa quel puntatore per valore.
nasch

Questo non è generalmente ciò che si intende per "pass-by-reference". Hai soddisfatto la mia domanda e non sono d'accordo con te. Grazie.
Amy,

"Nel mio libro si chiama passare per riferimento." - In ogni singolo libro di compilazione, libro di interpreti, libro di teoria del linguaggio di programmazione e libro di informatica mai scritto, non lo è.
Jörg W Mittag,

3

Il mio modo semplice per capire questo ...

  • Quando si chiama una funzione, si passa il contenuto (riferimento o valore) delle variabili dell'argomento, non le variabili stesse.

    var var1 = 13;
    var var2 = { prop: 2 };
    
    //13 and var2's content (reference) are being passed here
    foo(var1, var2); 
  • All'interno della funzione, le variabili dei parametri inVar1e inVar2, ricevono il contenuto che viene passato.

    function foo(inVar1, inVar2){
        //changing contents of inVar1 and inVar2 won't affect variables outside
        inVar1 = 20;
        inVar2 = { prop: 7 };
    }
  • Poiché ha inVar2ricevuto il riferimento di { prop: 2 }, è possibile modificare il valore della proprietà dell'oggetto.

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

3

Il passaggio di argomenti a una funzione in JavaScript è analogo al passaggio di parametri in base al valore del puntatore in 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
Non penso che sia il caso di JavaScript: `` `javascript var num = 5;
Danail Nachev,

@DanailNachev: Sebbene ciò possa essere tecnicamente vero, la differenza è osservabile solo per oggetti mutabili che non sono le primitive ECMAScript.
Jörg W Mittag,

3

Per gli avvocati del linguaggio di programmazione, ho esaminato le seguenti sezioni di ECMAScript 5.1 (che è più facile da leggere rispetto all'ultima edizione) e sono arrivato fino a chiederlo sulla mailing list di ECMAScript.

TL; DR : ogni cosa viene passata per valore, ma le proprietà degli oggetti sono riferimenti e la definizione di oggetto è spaventosamente carente nello standard.

Costruzione di elenchi di argomenti

La sezione 11.2.4 "Elenchi di argomenti" dice quanto segue sulla produzione di un elenco di argomenti composto da 1 solo argomento:

La ArgumentList di produzione: AssignmentExpression viene valutata come segue:

  1. Lascia che ref sia il risultato della valutazione di AssignmentExpression.
  2. Sia arg sia GetValue (ref).
  3. Restituisce un elenco il cui unico articolo è arg.

La sezione elenca anche i casi in cui l'elenco degli argomenti ha 0 o> 1 argomenti.

Quindi, tutto è passato per riferimento.

Accesso alle proprietà dell'oggetto

Sezione 11.2.1 "Accesso alla proprietà"

La produzione MemberExpression: MemberExpression [Expression] viene valutata come segue:

  1. Consenti a baseReference di essere il risultato della valutazione di MemberExpression.
  2. Lascia che baseValue sia GetValue (baseReference).
  3. Lascia che propertyNameReference sia il risultato della valutazione di Expression.
  4. Lascia che propertyNameValue sia GetValue (propertyNameReference).
  5. Chiama CheckObjectCoercible (baseValue).
  6. Lascia che propertyNameString sia ToString (propertyNameValue).
  7. Se la produzione sintattica che viene valutata è contenuta in un codice di modalità rigoroso, sia rigoroso vero, altrimenti rigoroso sia falso.
  8. Restituisce un valore di tipo Riferimento il cui valore di base è baseValue e il cui nome di riferimento è propertyNameString e il cui flag di modalità rigorosa è rigoroso.

Pertanto, le proprietà degli oggetti sono sempre disponibili come riferimento.

Su riferimento

È descritto nella sezione 8.7 "Il tipo di specifica di riferimento", che i riferimenti non sono tipi reali nella lingua - sono usati solo per descrivere il comportamento dell'eliminazione, il tipo di e gli operatori di assegnazione.

Definizione di "Oggetto"

Nella versione 5.1 è definito che "Un oggetto è una raccolta di proprietà". Pertanto, possiamo dedurre che il valore dell'oggetto è la raccolta, ma per quanto riguarda il valore della raccolta è scarsamente definito nelle specifiche e richiede un po 'di sforzo per capire.


Non smette mai di stupirmi di quante persone vengono confuse dalle distinzioni tra argomenti passati per valore, argomenti passati per riferimento, operazioni su interi oggetti e operazioni sulle loro proprietà. Nel 1979, non mi sono laureato in informatica, scegliendo invece di aggiungere circa 15 ore di elettivi CS al mio programma MBA. Tuttavia, presto mi è diventato chiaro che la mia comprensione di questi concetti era almeno altrettanto buona di quella di uno dei miei colleghi che avevano una laurea in informatica o matematica. Studia Assembler e diventerà abbastanza chiaro.
David A. Gray,

3

I documenti MDN lo spiegano chiaramente, senza essere troppo prolissi:

I parametri di una chiamata di funzione sono gli argomenti della funzione . Gli argomenti vengono passati alle funzioni in base al valore . Se la funzione modifica il valore di un argomento, questa modifica non si riflette globalmente o nella funzione chiamante. Tuttavia, anche i riferimenti agli oggetti sono valori e sono speciali: se la funzione modifica le proprietà dell'oggetto riferito, tale modifica è visibile all'esterno della funzione, (...)

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


1

In un linguaggio di basso livello, se si desidera passare una variabile per riferimento, è necessario utilizzare una sintassi specifica nella creazione della funzione:

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

Il &ageè un riferimento a myAge, ma se si desidera che il valore è necessario convertire il riferimento, utilizzando *age.

Javascript è un linguaggio di alto livello che esegue questa conversione per te. Pertanto, sebbene gli oggetti vengano passati per riferimento, la lingua converte il parametro di riferimento nel valore. Non è necessario utilizzare &, sulla definizione della funzione, per passarlo per riferimento, né *sul corpo della funzione per convertire il riferimento nel valore, JS lo fa per te.

Ecco perché quando provi a cambiare un oggetto all'interno di una funzione, sostituendo il suo valore (cioè age = {value:5}), la modifica non persiste, ma se cambi le sue proprietà (cioè age.value = 5), lo fa.

Per saperne di più


1

Ho letto queste risposte più volte, ma REALMENTE non l'ho capito finché non ho appreso della definizione tecnica di "Chiama per condivisione" come definita da Barbara Liskov

La semantica della chiamata mediante condivisione differisce dalla chiamata per riferimento in quanto le assegnazioni agli argomenti della funzione all'interno della funzione non sono visibili al chiamante (diversamente dalla semantica di riferimento) [citazione necessaria], quindi ad esempio se è stata passata una variabile, non è possibile per simulare un'assegnazione su quella variabile nell'ambito del chiamante. Tuttavia, poiché la funzione ha accesso allo stesso oggetto del chiamante (non viene effettuata alcuna copia), le mutazioni a quegli oggetti, se gli oggetti sono mutabili, all'interno della funzione sono visibili al chiamante, che possono apparire diverse dalla chiamata in base al valore semantica. Le mutazioni di un oggetto mutabile all'interno della funzione sono visibili al chiamante perché l'oggetto non viene copiato o clonato, ma condiviso.

Cioè, i riferimenti ai parametri sono modificabili se vai e accedi al valore del parametro stesso. D'altra parte, l'assegnazione a un parametro scompare dopo la valutazione e non è accessibile al chiamante della funzione.


No, se un oggetto è mutabile o meno non è davvero il problema. Tutto viene sempre passato per valore. Dipende solo da cosa stai passando (un valore o un riferimento). Vedere questo .
Scott Marcus,

Quello che sta descrivendo è passare un riferimento BY-VALUE. Non c'è motivo di introdurre una nuova terminologia.
Sanjeev,

1

Il modo più semplice

// 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 ) )è un modo orribile per clonare in profondità oggetti. Non solo non è solo lento, ma può anche causare la perdita di dati.
D. Pardal,

0

Ho trovato il metodo di estensione della libreria Underscore.js molto utile quando voglio passare un oggetto come parametro che può essere modificato o sostituito del tutto.

function replaceOrModify(aObj) {
  if (modify) {

    aObj.setNewValue('foo');

  } else {

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

0

La spiegazione più concisa che ho trovato è stata nella guida di stile AirBNB :

  • Primitive : quando accedi a un tipo primitivo, lavori direttamente sul suo valore

    • corda
    • numero
    • booleano
    • nullo
    • non definito

Per esempio:

var foo = 1,
    bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9
  • Complesso : quando accedi a un tipo complesso, lavori su un riferimento al suo valore

    • oggetto
    • Vettore
    • funzione

Per esempio:

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

bar[0] = 9;

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

Vale a dire che i tipi primitivi vengono passati per valore e i tipi complessi vengono passati per riferimento.


No, tutto viene sempre passato per valore. Dipende solo da cosa stai passando (un valore o un riferimento). Vedere questo .
Scott Marcus,

-1

Direi che è pass-by-copy -

Considera argomenti e oggetti variabili sono oggetti creati durante il contesto di esecuzione creato all'inizio della chiamata della funzione - e il tuo valore / riferimento effettivo passato nella funzione viene semplicemente memorizzato in questi argomenti + oggetti variabili.

In parole povere, per i tipi primitivi, i valori vengono copiati all'inizio della chiamata di funzione, per il tipo di oggetto, il riferimento viene copiato.


1
"pass-by-copy" === passa per valore
Scott Marcus,

-1

C'è qualche discussione sull'uso del termine "passa per riferimento" in JavaScript qui , ma per rispondere alla tua domanda:

Un oggetto viene automaticamente passato per riferimento, senza che sia necessario specificarlo

(Dall'articolo sopra citato.)


7
L'articolo collegato non include più tali dichiarazioni ed evita di utilizzare del tutto il "passaggio per riferimento".
C Perkins,

Il valore è un riferimento

-2

Un modo semplice per determinare se qualcosa è "passa per riferimento" è se è possibile scrivere una funzione di "scambio". Ad esempio, in C, puoi fare:

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

Se non puoi fare l'equivalente di quello in JavaScript, non è "passa per riferimento".


21
Questo non è veramente un riferimento. Stai passando i puntatori nella funzione e questi puntatori vengono passati per valore. Un esempio migliore potrebbe essere l'operatore & C ++ o la parola chiave "ref" di C #, entrambi sono realmente passati per riferimento.
Matt Greer,

Ancora più semplice è che tutto viene passato per valore in JavaScript.
Scott Marcus,


-3
  1. variabile di tipo primitivo come stringa, numero viene sempre passato come valore di passaggio.
  2. Array e Object vengono passati come passaggio per riferimento o passaggio per valore in base a queste due condizioni.

    • se si sta modificando il valore di quell'oggetto o matrice con un nuovo oggetto o matrice, allora viene passato per valore.

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

    qui stai assegnando un nuovo oggetto o un array a quello vecchio. Non stai modificando il valore della proprietà del vecchio oggetto, quindi viene passato per valore.

    • se si sta modificando un valore di proprietà di un oggetto o di un array, questo viene passato da Reference.

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

    qui stai cambiando un valore di proprietà del vecchio oggetto. Non stai assegnando un nuovo oggetto o un array a quello vecchio, quindi è passato per riferimento.

Codice

    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'operatore di assegnazione non deve essere confuso con una chiamata di funzione. Quando si assegnano nuovi dati a una variabile esistente, il conteggio dei riferimenti dei vecchi dati diminuisce e i nuovi dati vengono associati alla vecchia variabile. Fondamentalmente, la variabile finisce per indicare i nuovi dati. Lo stesso vale per le variabili di proprietà. Poiché queste assegnazioni non sono chiamate di funzione, non hanno nulla a che fare con il pass-by-value o pass-by-reference.
John Sonderson,

1
No, tutto viene sempre passato per valore. Dipende solo da cosa stai passando (un valore o un riferimento). Vedere questo .
Scott Marcus,

-3
  1. Le primitive (numero, booleano, ecc.) Vengono passate per valore.
    • Le stringhe sono immutabili, quindi non importa davvero per loro.
  2. Gli oggetti vengono passati per riferimento (il riferimento viene passato per valore).

No, tutto viene sempre passato per valore. Dipende solo da cosa stai passando (un valore o un riferimento). Vedere questo .
Scott Marcus,

La tua seconda affermazione si sta contraddicendo.
Jörg W Mittag,

-5

I valori semplici all'interno delle funzioni non cambieranno quei valori al di fuori della funzione (sono passati per valore), mentre quelli complessi lo faranno (sono passati per riferimento).

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

che è ridicolo, y cambierà a causa del scoping a livello funzionale, non viene sollevato perché viene passato per riferimento.
Parijat Kalia,

No, tutto viene sempre passato per valore. Dipende solo da cosa stai passando (un valore o un riferimento). Vedere questo .
Scott Marcus,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.