Taker: Zeiger-Verhalten bei Arrays

Hi Leute,

ich musste heute etwas komisches feststellen bei der Übergabe eines Arrays an eine Funktion.

Zur besseren Veranschaulichung hier ein Beipiel:

  
    function test( v ) {  
        v[0] = 'geändert';  
    }  
    var a = [];  
    a[0] = 'nicht geändert';  
    test( a );  
    alert( a );  

Da es keine Zeiger in Javascript gibt, dürfte, wenn man Variable "a" ausgibt, "nicht geändert" kommen, aber dem ist nicht so. In der Variable steht "geändert", was letztendlich ein Zeiger-Verhalten darstellt.

Wieso ist das so und bei "normalen" Variablen, wie z.B. vom Typ String, ist es nicht?

  1. Hi,

    function test( v ) {
            v[0] = 'geändert';
        }
        var a = [];
        a[0] = 'nicht geändert';
        test( a );
        alert( a );

    
    >   
    > Da es keine Zeiger in Javascript gibt, dürfte, wenn man Variable "a" ausgibt, "nicht geändert" kommen, aber dem ist nicht so.  
      
    Es gibt keine expliziten „Zeiger“, aber Objekte werden immer per Referenz übergeben.  
      
    
    > In der Variable steht "geändert", was letztendlich ein Zeiger-Verhalten darstellt.  
      
    Ja - nur ohne, dass du als Programmierer explizit Zeiger anlegst.  
      
    
    > Wieso ist das so und bei "normalen" Variablen, wie z.B. vom Typ String, ist es nicht?  
      
    Objekte werden per Referenz übergeben, skalare Datentypen by value.  
      
    MfG ChrisB  
      
    
    -- 
    RGB is totally confusing - I mean, at least #C0FFEE should be brown, right?
    
    1. Danke für die schnelle Antwort,
      aber gibt es auch ne Möglichkeit ein Array an eine Funktion zu übergeben und zu ändern, ohne dass das globale Array verändert wird?

      1. Grüße,

        Danke für die schnelle Antwort,
        aber gibt es auch ne Möglichkeit ein Array an eine Funktion zu übergeben und zu ändern, ohne dass das globale Array verändert wird?

        ich habs bisher einfach in der function kopiert, nicht elegant, aber...
        MFG
        bleicher

        --
        __________________________-

        FirefoxMyth
      2. aber gibt es auch ne Möglichkeit ein Array an eine Funktion zu übergeben und zu ändern, ohne dass das globale Array verändert wird?

        Nur mit einer Kopie.

        Struppi.

    2. Objekte werden per Referenz übergeben, skalare Datentypen by value.

      Das heißt in ECMAScript übrigens »primitive value«.

      Mathias

    3. Hi,

      Objekte werden per Referenz übergeben, skalare Datentypen by value.

      Stimmt nicht ganz (oder legt zumindest falsche Schlüsse nahe ;-).

      Variablen in JavaScript können nie Objekte enthalten, sondern immer nur Objektreferenzen. Diese werden aber auch by value übergeben. Somit kennt JavaScript kein „call by reference“ sondern _nur_ „call by value“.

      Diese Unterscheidung wird oft nicht sauber dargestellt; siehe auch Referenzparameter – Wikipedia und den weiterführenden Link (auch wenn es da um Java geht, die Problematik ist dieselbe).

      Gruß,
      Nerog

      1. Objekte werden per Referenz übergeben, skalare Datentypen by value.

        Stimmt nicht ganz (oder legt zumindest falsche Schlüsse nahe ;-).

        Welche falschen Schlüsse legt es denn nah?

        Variablen in JavaScript können nie Objekte enthalten, sondern immer nur Objektreferenzen. Diese werden aber auch by value übergeben. Somit kennt JavaScript kein „call by reference“ sondern _nur_ „call by value“.

        Diese Unterscheidung wird oft nicht sauber dargestellt

        Okay, ist es eine vereinfachte Darstellung.
        Aber was ist der praktische Unterschied?

        Mathias

        1. Objekte werden per Referenz übergeben, skalare Datentypen by value.

          Stimmt nicht ganz (oder legt zumindest falsche Schlüsse nahe ;-).

          Welche falschen Schlüsse legt es denn nah?

          Dass in JavaScript Objekte by reference übergeben werden.

          Variablen in JavaScript können nie Objekte enthalten, sondern immer nur Objektreferenzen. Diese werden aber auch by value übergeben. Somit kennt JavaScript kein „call by reference“ sondern _nur_ „call by value“.

          Diese Unterscheidung wird oft nicht sauber dargestellt

          Okay, ist es eine vereinfachte Darstellung.
          Aber was ist der praktische Unterschied?

          Hast du dir das Beispiel im von mir verlinkten Wikipedia-Artikel angesehen?

          Das lässt sich direkt auf JavaScript übertragen:

          function main()  
          {  
              s = new Date(100000000000);  
              alert("main 1: " + s);  
              call(s);  
              alert("main 2: " + s);  
          }  
            
          function call(s)  
          {  
              alert("call 1: " + s);  
              s = new Date(200000000000);  
              alert("call 2: " + s);  
          }
          

          Ein Aufruf von main() ergibt die Ausgabe

          main 1: Sat Mar 03 1973 10:46:40 GMT+0100
          call 1: Sat Mar 03 1973 10:46:40 GMT+0100
          call 2: Mon May 03 1976 21:33:20 GMT+0200
          main 2: Sat Mar 03 1973 10:46:40 GMT+0100

          Das Ändern der „Referenz“ s (die ja eben keine ist) in call hat keine Auswirkung auf s in main; bei „call by reference“ wäre dies aber der Fall.
          Sprachen wie C++ oder Pascal unterstützen „call by reference“, JavaScript (oder auch z.B. Java) unterstützen nur „call by value“.

          Um das Verhalten von JavaScript bei obigem Anwendungsfall nachvollziehen zu können, muss man wissen, dass JavaScript Parameter by value übergibt.
          Die Ausgabe, die man bei einer Übergabe by reference erwarten würde, wäre:

          main 1: Sat Mar 03 1973 10:46:40 GMT+0100
          call 1: Sat Mar 03 1973 10:46:40 GMT+0100
          call 2: Mon May 03 1976 21:33:20 GMT+0200
          main 2: Mon May 03 1976 21:33:20 GMT+0200

          Gruß,
          Nerog

          1. Das Ändern der „Referenz“ s (die ja eben keine ist) in call hat keine Auswirkung auf s in main; bei „call by reference“ wäre dies aber der Fall.
            Sprachen wie C++ oder Pascal unterstützen „call by reference“, JavaScript (oder auch z.B. Java) unterstützen nur „call by value“.

            Achso, klar. Dann war mir nicht bekannt, was »by reference« wirklich bedeutet, weil in den mir geläufigen Sprachen nicht vorhanden. Insofern ist die Unterscheidung auf JavaScript nicht anwendbar. Man kann natürlich nicht in einem Scope einer Variable (bzw. Parameter) einen Wert zuweisen und damit in einem anderen Scope den Wert ändern (also die Referenz auf ein neues Objekt umbiegen - Scope Chain mal ausgenommen).

            JavaScript macht insofern etwas drittes (Java wohl auch, weshalb es als Sonderfall bezeichnet wird), wie Dmitry Soshnikov schreibt:
            http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/

            »Call by sharing
            ...
            Alternative names of this strategy are “call by object” or “call by object-sharing”.
            Strategy “by sharing” has been offered and named first by Barbara Liskov for CLU programming language in 1974.
            The main point of this strategy is that function receives the copy of the reference to object. This reference copy is associated with the formal parameter and is its value.«

            Er schlägt dann vor, es entweder als dritte Strategie »Call by Sharing« zu nennen oder als Unterfall »call by value where value is the reference copy«.

            Objekte werden per Referenz übergeben, skalare Datentypen by value.

            ... müsste dann eher heißen, Objects haben einen veränderbaren Status und man übergibt eine Referenz auf ein und dasselbe Objekt, Primitives werden kopiert (und haben keinen veränderbaren Status, es kommen bei Methodenaufrufen usw. wieder neue Primitives heraus).

            Ein Array ist da ja ein klassisches Beispiel, das ist ein Objekt mit numerischen Eigenschaften. Fügt man Eigenschaften hinzu, geschieht es beim selben Array wie im Scope, aus dem der Array heraus übergeben wurde (weil beide Variablen auf dieselbe Speicherstelle referenzieren).

            Mathias

            1. Achso, klar. Dann war mir nicht bekannt, was »by reference« wirklich bedeutet, weil in den mir geläufigen Sprachen nicht vorhanden.

              Okay, dann wäre das ja geklärt :-)

              JavaScript macht insofern etwas drittes (Java wohl auch, weshalb es als Sonderfall bezeichnet wird), wie Dmitry Soshnikov schreibt:

              »Call by sharing

              […] Er schlägt dann vor, es entweder als dritte Strategie »Call by Sharing« zu nennen oder als Unterfall »call by value where value is the reference copy«.

              Ja, der Artikel stellt die Unterschiede gut dar. Ich halte es jedoch weiterhin mit der Formulierung, JavaScript benutze „call by value where value is the reference copy“ ;-)

              Objekte werden per Referenz übergeben, skalare Datentypen by value.

              ... müsste dann eher heißen, Objects haben einen veränderbaren Status und man übergibt eine Referenz auf ein und dasselbe Objekt, Primitives werden kopiert (und haben keinen veränderbaren Status, es kommen bei Methodenaufrufen usw. wieder neue Primitives heraus).

              Das kann man so formulieren (mit „übergibt eine Referenz“ im Sinne von „kopiert eine Referenz“).

              Persönlich finde ich es intuitiver, keine Unterscheidung zwischen der Parameterübergabe eines Primitives und eines Objects zu machen; beide werden by value übergeben. Man muss nur im Hinterkopf behalten, dass Variablen in JavaScript _nie_ Objekte beinhalten, sondern _immer_ Objektreferenzen (wieder im Unterschied zu z.B. C++).
              var a = new Date; erzeugt (irgendwo im Speicher für den JavaScript-Programmierer nicht direkt zugänglich) ein Date-Objekt und speichert eine Referenz auf dieses Objekt (nicht das Objekt selbst) in a.

              Gruß,
              Nerog

              1. Man muss nur im Hinterkopf behalten, dass Variablen in JavaScript _nie_ Objekte beinhalten, sondern _immer_ Objektreferenzen (wieder im Unterschied zu z.B. C++).

                Das finde ich verwirrend - wahrscheinlich weil ich ebenfalls nicht so recht weiß, was es bedeutet, dass eine Variable *direkt* ein Objekt enthält.

                var a = new Date; erzeugt (irgendwo im Speicher für den JavaScript-Programmierer nicht direkt zugänglich) ein Date-Objekt und speichert eine Referenz auf dieses Objekt (nicht das Objekt selbst) in a.

                Ich kenne Low-Level-Sprachen nicht, wo man jedes Quäntchen Speicher selbst reservieren und freigeben muss, daher hilft mir diese Unterscheidung nicht weiter.
                Ist eine Variable nicht immer ein Verweis auf die Speicherstelle, wo dann die zugehörigen Nullen und Einsen liegen?
                Was heißt es, das Objekt selbst ist in der Variable? Meinst du etwa Pascal, wo eben echte Referenzparameter wie im Wikipedia-Beispiel möglich sind?
                Aber dort ist die Variable dann auch eine Referenz auf eine Speicherstelle. Klar, bei einer Zuweisung an die Variable überschreibe ich diese Speicherstelle selbst, anstatt wie bei ECMAScript eine neue Referenz auf dieselbe oder, wenn es ein anderer Wert ist, eine andere Speicherstelle zu erzeugen.

                Mathias

                1. Das finde ich verwirrend - wahrscheinlich weil ich ebenfalls nicht so recht weiß, was es bedeutet, dass eine Variable *direkt* ein Objekt enthält.

                  Eine Variable ist ja erstmal ein symbolischer Name im Quellcode. Man kann sich nun vorstellen, dass sie direkt ein Objekt repräsentiert oder dass sie eine Referenz auf ein Objekt darstellt – unabhängig davon wie die Realisierung genau aussieht. Man sollte seine Vorstellung eben so wählen, dass sie das Verhalten der Variable im Code widerspiegelt.
                  Welche Vorstellung ich als am intuitivsten ansehe, habe ich ja schon geschrieben (weil ich denke, dass diese der tatsächlichen Implementierung entspricht), wenn du ein andere Vorstellung besser verständlich findest, ist das ja erst mal kein großes Problem, solange wir die selben Schlüsse über das Verhalten der Variable daraus ziehen.

                  Ist eine Variable nicht immer ein Verweis auf die Speicherstelle, wo dann die zugehörigen Nullen und Einsen liegen?

                  Warum? Es gibt keine Variable, die ein Verweis auf die Speicherstelle hält, es gibt nur die Speicherstelle selbst (z.B. im Stack oder im Datensegment). Die Prozessorbefehle können direkt auf der Speicherstelle arbeiten.
                  Die Variable ist ja nur der symbolische Name im Quell-Code für diese Speicherstelle, im kompilierten Code gibt es die Variable (im Sinne eines Verweises) ja nicht mehr.

                  Es _kann_ aber natürlich auch Speicherstellen geben, die auf andere verweisen, eben Zeiger/Referenzen.

                  Klar, bei einer Zuweisung an die Variable überschreibe ich diese Speicherstelle selbst

                  Exakt.
                  Und zwar immer, überall, ohne Ausnahmen oder komplizierte Sonderfälle.
                  Nur in ECMAScript speichert eine Variable immer nur eine Referenz auf ein Objekt (sofern sie kein Primitive ist).

                  Meinst du etwa Pascal, wo eben echte Referenzparameter wie im Wikipedia-Beispiel möglich sind?
                  Aber dort ist die Variable dann auch eine Referenz auf eine Speicherstelle.

                  Nein, die Variable repräsentiert auch hier das Objekt selbst. Das unterscheidet ja eben kopierte Werte (call by value) von Referenzen (call by reference).
                  Dass eine Referenzvariable letztendlich auf Assembler-Ebene auf eine Art Zeiger heruntergebrochen wird, ist wohl naheliegend; aber das ist aus Programmierersicht ein Implementierungsdetail des Compilers, um das man sich normalerweise nicht weiter kümmern muss. Aus Programmierersicht repräsentiert ein by reference übergebenes Objekt das Objekt selbst. Ein by value übergebenes Objekt ist entweder eine Kopie des Objekts oder (wie im Falle von ECMAScript) eine Kopie der Objektreferenz.

                  Gruß,
                  Nerog

  2. hi,

    weil's grad mal passt, auch in Perl heißt es aufgepasst:

      
    my @ary = (0..9);  
    foreach my $d(@ary){  
      $d = 'Hugo' if $d == 7;  
    }  
    print join("\n", @ary), "\n";  
    
    

    Ausgabe:
    0
    1
    2
    3
    4
    5
    6
    Hugo
    8
    9

    Nurmalso nebenbei...
    Hotti (vom Krankenbett)

    1. Die Doku ist wie immer exzellent und beschreibt ganz genau, was passiert, hier gibt es keinen Raum für Überraschung. Die Folgen des Nichtlesens derselben ist allein dem Programmierer anzulasten.

      perlsyn#Foreach-Loops:
      | the foreach loop index variable is an implicit alias for each item in the list
      | that you're looping over.

      Der Sachverhalt ist Aliasing, nicht Call-by-something, bitte nicht in einen Topf schmeißen.