1753 Stimmen

Ist JavaScript eine pass-by-reference oder pass-by-value Sprache?

Die primitiven Typen (Zahl, Zeichenkette usw.) werden als Wert übergeben, aber Objekte sind unbekannt, da sie sowohl als Wert (wenn wir davon ausgehen, dass eine Variable, die ein Objekt enthält, in Wirklichkeit ein Verweis auf das Objekt ist) als auch als Verweis (wenn wir davon ausgehen, dass die Variable auf das Objekt das Objekt selbst enthält) übergeben werden können.

Auch wenn es am Ende keine Rolle spielt, möchte ich wissen, wie man die Argumente richtig darstellt, indem man die Konventionen übergeht. Gibt es einen Auszug aus JavaScript-Spezifikation, die definiert, was sollte die Semantik in Bezug auf diese sein?

0 Stimmen

Ich denke, wir haben genügend Beweise dafür, was die genaue Semantik der Sprache ist. Jetzt fehlt nur noch der Auszug aus der EcmaScript-Spezifikation, der sie definiert, und wir werden eine Antwort auf diese Frage haben.

8 Stimmen

Ich glaube, Sie haben versehentlich Ihre Definitionen von "passed-by-value" und "passed-by-reference" vertauscht... "passed-by-value (wenn wir davon ausgehen, dass eine Variable, die ein Objekt enthält, in Wirklichkeit ein Verweis auf das Objekt ist) und passed-by-reference (wenn wir davon ausgehen, dass die Variable auf das Objekt das Objekt selbst enthält)"

11 Stimmen

Ja. Unabhängig von der Syntax bedeutet "pass-by-reference" bei einem Funktionsaufruf in einer beliebigen Programmiersprache, dass die mit der übergebenen Variablen verbundenen Daten bei der Übergabe an die Funktion nicht kopiert werden und somit alle von der Funktion an der übergebenen Variablen vorgenommenen Änderungen nach Beendigung des Funktionsaufrufs im Programm erhalten bleiben. Pass-by-Value bedeutet, dass die mit der Variablen verbundenen Daten bei der Übergabe an die Funktion tatsächlich kopiert werden und dass alle von der Funktion an der Variablen vorgenommenen Änderungen verloren gehen, wenn die Variable bei der Rückkehr der Funktion aus dem Anwendungsbereich des Funktionskörpers verschwindet.

1992voto

deworde Punkte 2549

Interessant ist es in JavaScript. Betrachten Sie dieses Beispiel:

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

Dies ergibt die Ausgabe:

10
changed
unchanged
  • Si obj1 überhaupt kein Verweis war, dann ändern Sie obj1.item hätte keine Auswirkungen auf die obj1 außerhalb der Funktion.
  • Wäre das Argument ein richtiger Verweis, dann wäre alles anders. num wäre 100 y obj2.item würde lauten "changed" . Stattdessen, num bleibt 10 y obj2.item bleibt "unchanged ".

Stattdessen ist es so, dass das übergebene Element als Wert übergeben wird. Aber das Element, das als Wert übergeben wird, ist selbst eine Referenz. Technisch gesehen wird dies als Call-by-Sharing .

In der Praxis bedeutet dies, dass Sie, wenn Sie den Parameter selbst ändern (wie bei num y obj2 ), wirkt sich das nicht auf das Element aus, das in den Parameter eingegeben wurde. Wenn Sie jedoch den Interna des Parameters, der sich wieder nach oben fortpflanzt (wie bei obj1 ).

49 Stimmen

Dies ist genau dasselbe (oder zumindest semantisch) wie C#. Object hat zwei Typen: Wert (primitive Typen) und Referenz.

72 Stimmen

Ich glaube, dies wird auch in Java verwendet: Referenz durch Wert.

348 Stimmen

Der eigentliche Grund ist, dass innerhalb von changeStuff num, obj1 und obj2 Referenzen sind. Wenn Sie die item Eigenschaft des Objekts, auf das obj1 verweist, ändern Sie den Wert der Elementeigenschaft, die ursprünglich auf "unverändert" gesetzt war. Wenn Sie obj2 einen Wert von {item: "changed"} zuweisen, ändern Sie den Verweis auf ein neues Objekt (das sofort aus dem Anwendungsbereich verschwindet, wenn die Funktion beendet wird). Es wird deutlicher, was passiert, wenn Sie den Funktionsparametern Namen wie numf, obj1f und obj2f geben. Dann sehen Sie, dass die Parameter die externen Var-Namen verbergen.

632voto

Tim Goodman Punkte 21947

Es ist immer eine Wertübergabe, aber bei Objekten ist der Wert der Variablen eine Referenz. Wenn Sie also ein Objekt übergeben und dessen Mitglieder bleiben diese Änderungen auch außerhalb der Funktion bestehen. Dies macht es siehe wie bei der Referenzübergabe. Wenn Sie jedoch den Wert der Objektvariablen tatsächlich ändern, werden Sie feststellen, dass die Änderung nicht bestehen bleibt, was beweist, dass es sich in Wirklichkeit um eine Wertübergabe handelt.

Beispiel:

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

出力します。

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

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

0 Stimmen

Hallo Tim, danke für die hervorragende Antwort. Ich habe einen winzigen Vorschlag für die Klarheit - können Sie bitte entweder die Reihenfolge der Funktionsdeklarationen oder die Reihenfolge der Funktionsaufrufe umkehren, damit sie übereinstimmen? Ich habe eine Weile gebraucht, um zu erkennen, dass die Ausgabe nicht mit deiner Schlussfolgerung übereinstimmt! :)

16 Stimmen

@daylight: Eigentlich sind Sie falsch; wenn es von const ref übergeben wurde, würde der Versuch, changeObject zu tun, einen Fehler verursachen, anstatt einfach fehlzuschlagen. Versuchen Sie, einer const-Referenz in C++ einen neuen Wert zuzuweisen, und der Compiler lehnt dies ab. Für den Benutzer ist das der Unterschied zwischen pass by value und pass by const reference.

5 Stimmen

@daylight: Es ist nicht konstant ref. Unter changeObject Ich habe mich geändert. x um einen Verweis auf das neue Objekt zu enthalten. x = {member:"bar"}; ist gleichbedeutend mit x = new Object(); x.member = "bar"; Was ich sage, gilt übrigens auch für C#.

170voto

Shog9 Punkte 151504

Die Variable "enthält" nicht das Objekt, sondern einen Verweis. Sie können diesen Verweis einer anderen Variablen zuweisen, und nun verweisen beide auf dasselbe Objekt. Es handelt sich immer um eine Wertübergabe (auch wenn dieser Wert ein Verweis ist...).

Es gibt keine Möglichkeit, den Wert einer als Parameter übergebenen Variablen zu ändern, was möglich wäre, wenn JavaScript die Übergabe per Referenz unterstützen würde.

6 Stimmen

Das verwirrt mich ein wenig. Ist die Übergabe einer Referenz nicht pass-by-reference?

13 Stimmen

Der Autor meint damit, dass Sie durch die Übergabe einer Referenz einen Referenzwert übergeben (eine andere Möglichkeit ist die Übergabe des Wertes der Speicheradresse). Wenn Sie also das Objekt neu deklarieren, ändert sich das Original nicht, da Sie ein neues Objekt an einem anderen Speicherort erstellen. Wenn Sie eine Eigenschaft ändern, ändert sich das ursprüngliche Objekt, weil Sie es an der ursprünglichen Speicherstelle (die nicht neu zugewiesen wurde) geändert haben.

0 Stimmen

Die Formulierung "Übergabe einer Referenz durch einen Wert" scheint unnötig verwirrend und redundant zu sein. Bei der Übergabe einer Referenz natürlich irgendein Wert bestanden werden muss. Obwohl dies technisch gesehen richtig ist, gehen die meisten Menschen standardmäßig davon aus, dass alles als Wert übergeben wird, sofern nichts anderes angegeben ist. So ist natürlich ein Verweis durch Wert übergeben, es sei denn, es selbst ist durch Verweis übergeben (wie ein Zeiger auf einen Zeiger in C), aber in diesem Fall Javascript nicht einmal unterstützen, dass so ich glaube nicht, dass es hilft, das Konzept klarer

150voto

Ray Perea Punkte 5242

Meine Meinung... Das ist die Art und Weise, wie ich es verstehe. (Sie können mich gerne korrigieren, wenn ich falsch liege)

Es ist an der Zeit, alles, was Sie über Wertübergabe / Referenz wissen, über Bord zu werfen.

Denn in JavaScript spielt es keine Rolle, ob es als Wert oder als Referenz oder wie auch immer übergeben wird. Worauf es ankommt, ist die Mutation bzw. Zuweisung der an eine Funktion übergebenen Parameter.

Okay, ich werde mein Bestes tun, um zu erklären, was ich meine. Nehmen wir an, Sie haben ein paar Objekte.

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

Was wir getan haben, ist "Zuweisung"... Wir haben 2 getrennte leere Objekte den Variablen "object1" und "object2" zugewiesen.

Nehmen wir nun an, dass uns Objekt1 besser gefällt... Also "weisen" wir eine neue Variable zu.

var favoriteObject = object1;

Dann entscheiden wir, aus welchen Gründen auch immer, dass uns Objekt 2 besser gefällt. Also nehmen wir eine kleine Neuzuweisung vor.

favoriteObject = object2;

Weder mit Objekt1 noch mit Objekt2 ist etwas passiert. Wir haben überhaupt keine Daten geändert. Wir haben lediglich neu zugewiesen, was unser Lieblingsobjekt ist. Es ist wichtig zu wissen, dass object2 und favoriteObject beide demselben Objekt zugewiesen sind. Wir können dieses Objekt über eine der beiden Variablen ändern.

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

OK, schauen wir uns nun Primitive wie z.B. Strings an

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

Wieder wählen wir einen Favoriten.

var favoriteString = string1;

Die Variablen favoriteString und string1 sind beide der Variable "Hello world" zugeordnet. Was passiert nun, wenn wir unseren favoriteString ändern wollen? Was wird dann passieren?

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

Uh oh.... Was ist passiert? Wir konnten string1 nicht ändern, indem wir favoriteString geändert haben... Warum nur? Weil wir nicht ändern unser String Objekt . Alles, was wir getan haben, war, den Favoritenring neu zuzuweisen. variabel zu einer neuen Zeichenkette. Damit wurde sie im Wesentlichen von string1 getrennt. Im vorherigen Beispiel haben wir unser Objekt umbenannt, ohne ihm etwas zuzuweisen. (Nun, nicht dem Variable selbst ... wir haben jedoch die Eigenschaft name einem neuen String zugewiesen). Stattdessen haben wir das Objekt mutiert, das die Verbindungen zwischen den beiden Variablen und den zugrunde liegenden Objekten aufrechterhält. (Selbst wenn wir das Objekt ändern wollten oder mutieren das String-Objekt selbst nicht möglich, da Strings in JavaScript eigentlich unveränderlich sind).

Nun zu den Funktionen und der Übergabe von Parametern.... Wenn Sie eine Funktion aufrufen und einen Parameter übergeben, handelt es sich im Wesentlichen um eine "Zuweisung" an eine neue Variable, die genauso funktioniert, wie wenn Sie sie mit dem Gleichheitszeichen (=) zuweisen.

Nehmen Sie diese Beispiele.

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'

Nun das Gleiche, aber mit einer Funktion

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, jetzt ein paar Beispiele mit Objekten stattdessen... zuerst ohne die Funktion.

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

Nun das Gleiche, aber mit einem Funktionsaufruf

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, wenn Sie diesen Beitrag ganz durchgelesen haben, verstehen Sie jetzt vielleicht besser, wie Funktionsaufrufe in JavaScript funktionieren. Es spielt keine Rolle, ob etwas als Referenz oder als Wert übergeben wird... Was zählt, ist Zuweisung vs. Mutation.

Jedes Mal, wenn Sie eine Variable an eine Funktion übergeben, weisen Sie ihr den Namen der Parametervariablen zu, genau wie bei der Verwendung des Gleichheitszeichens (=).

Denken Sie immer daran, dass das Gleichheitszeichen (=) eine Zuordnung bedeutet. Denken Sie immer daran, dass die Übergabe eines Parameters an eine Funktion in JavaScript bedeutet auch Zuweisung. Sie sind dasselbe, und die beiden Variablen sind auf genau dieselbe Weise miteinander verbunden (das heißt, sie sind es nicht, es sei denn, man zählt, dass sie demselben Objekt zugewiesen sind).

Das einzige Mal, dass sich die "Änderung einer Variablen" auf eine andere Variable auswirkt, ist, wenn das zugrunde liegende Objekt geändert wird (in diesem Fall haben Sie nicht die Variable, sondern das Objekt selbst geändert).

Es macht keinen Sinn, zwischen Objekten und Primitiven zu unterscheiden, denn es funktioniert genau so, als ob Sie keine Funktion hätten und nur das Gleichheitszeichen für die Zuweisung an eine neue Variable verwenden würden.

Das einzige Problem besteht darin, dass der Name der Variablen, die Sie an die Funktion übergeben, mit dem Namen des Funktionsparameters identisch ist. In diesem Fall müssen Sie den Parameter innerhalb der Funktion so behandeln, als ob es sich um eine ganz neue, der Funktion vorbehaltene Variable handelt (denn das ist sie auch)

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'

3 Stimmen

Für alle C-Programmierer: Denken Sie an char*. foo(char *a){a="hello";} bewirkt nichts, aber wenn Sie foo(char *a){a[0]='h';a[1]='i';a[2]=0;} es wird draußen geändert, weil a ist eine Speicherstelle, die als Wert übergeben wird und auf eine Zeichenkette (char array) verweist. Die Übergabe von Structs (ähnlich wie js-Objekte) als Wert in C ist erlaubt, aber nicht empfohlen. JavaScript erzwingt einfach diese besten Praktiken und versteckt die unnötigen und in der Regel unerwünschte Cruft ... und es macht sicher für leichteres Lesen.

3 Stimmen

Dies ist richtig - die Begriffe Pass-by-Value y Pass-by-Reference haben eine Bedeutung für das Design von Programmiersprachen, und diese Bedeutung hat überhaupt nichts mit Objektmutation zu tun. Es geht nur darum, wie Funktionsparameter funktionieren.

2 Stimmen

Nun, da ich verstanden habe, dass obj1 = obj2 bedeutet, dass sowohl obj1 als auch obj2 jetzt auf dieselbe Referenzstelle zeigen, und wenn ich die Interna von obj2 ändere, wird der Verweis auf obj1 dieselben Interna offenlegen. Wie kann ich ein Objekt so kopieren, dass ich bei source = { "id":"1"}; copy = source /*this is wrong*/; copy.id="2" die Quelle immer noch {"id": "1"} ist?

98voto

geg Punkte 3794

Diese Ausdrücke/Konzepte wurden ursprünglich definiert, lange bevor JS erstellt wurde, und sie beschreiben nicht genau die Semantik für Javascript. Ich denke, der Versuch, sie auf JS anwenden verursacht mehr Verwirrung als nicht.

Lassen Sie sich also nicht von "pass by reference/value" aufhalten.

Bedenken Sie Folgendes:

  1. Die Variablen sind Zeiger zu Werten.
  2. Bei der Neuzuweisung einer Variablen wird der Zeiger lediglich auf einen neuen Wert gesetzt.
  3. Die Neuzuweisung einer Variablen wirkt sich niemals auf andere Variablen aus, die auf dasselbe Objekt zeigen, da jede Variable ihren eigenen Zeiger hat.

Wenn ich ihr einen Namen geben müsste, würde ich sagen "Pass-by-Pointer" -- Wir arbeiten in JS nicht mit Zeigern, aber die zugrunde liegende Engine tut es.

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

Einige abschließende Bemerkungen:

  • Die Ausdrücke "pass by value/reference" werden nur verwendet, um die Verhalten einer Sprache, nicht notwendigerweise die eigentliche zugrundeliegende Implementierung. Durch diese Abstraktion gehen kritische Details, die für eine vernünftige Erklärung unerlässlich sind, verloren, was unweigerlich zu der derzeitigen Situation führt, in der ein einzelner Begriff das tatsächliche Verhalten ohne zusätzliche Informationen nicht angemessen beschreibt.
  • Es ist verlockend zu denken, dass Primitive werden durch besondere Regeln durchgesetzt, während Objekte sind es nicht, aber Primitive sind einfach das Ende der Zeigerkette.
  • Ein letztes Beispiel zeigt, warum ein üblicher Versuch, ein Array zu löschen, nicht wie erwartet funktioniert.

    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

0 Stimmen

Folgefragen für zusätzliche Punkte ;) Wie funktioniert die Garbage Collection? Wenn ich eine Variable durch eine Million {'George', 1} Werte haben, aber immer nur einen von ihnen verwenden, wie werden dann die anderen verwaltet? Und was passiert, wenn ich eine Variable dem Wert einer anderen Variablen zuweise? Zeige ich dann auf einen Zeiger, oder zeige ich auf den Zeiger des rechten Operanden? Ist var myExistingVar = {"blah", 42}; var obj = myExistingVar; zur Folge haben obj zeigend auf {"blah", 42} oder an myExistingVar ?

0 Stimmen

@MichaelHoffmann Diese Fragen verdienen eine eigene SO und sind wahrscheinlich schon besser beantwortet, als ich es kann. Davon abgesehen, 1) Ich habe ein Speicherprofil in den Browser-Entwicklungstools für eine Schleifenfunktion wie die von Ihnen beschriebene ausgeführt und sah Spitzen in der Speichernutzung während des Schleifenprozesses. Dies scheint darauf hinzuweisen, dass bei jeder Iteration der Schleife tatsächlich neue identische Objekte erstellt werden. Wenn die Spitzen plötzlich abnehmen, hat der Garbage Collector gerade eine Gruppe dieser ungenutzten Objekte aufgeräumt.

1 Stimmen

@MichaelHoffmann 2) In Bezug auf etwas wie var a = b Javascript bietet keinen Mechanismus für die Verwendung von Zeigern und so eine Variable kann nie auf einen Zeiger (wie Sie in C können), obwohl die zugrunde liegende Javascript-Engine zweifellos verwendet sie. Also... var a = b wird zeigen a "bis zum Punkt des rechten Operanden"

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X