seth: (pseudo-)assoziative arrays in javascript: naming collisions mit methoden

gudn tach!

nach vielen jahren muss ich mich mal wieder mit js beschaeftigen und fuehle mich soo doof.

assoziative arrays sind in js wohl objekte (oder umgekehrt, je nach sichtweise). das kann, wenn man hashes/maps von anderen sprachen wie perl oder c++ kennt, zu unerwarteten ergebnissen fuehren.

der code-schnipsel

var some_object = {};
some_object['init'] = true;
if(some_object['constructor']){
  destroy_earth();
}

wuerde (wenn die funktion destroy_earth korrekt implementiert ist), fatale folgen haben.

denn some_object besitzt als objekt standardmaessig die methode constructor (sowie weitere methoden wie toString etc.)

im konkreten fall koennte ich durch explizites abfragen den code wohl retten:

var some_object = {};
some_object['init'] = true;
if(some_object['constructor'] === true){
  destroy_earth(); // wird nun nicht mehr aufgerufen
}

oder ich mach's noch expliziter:

var some_object = {};
some_object['init'] = true;
const key = 'constructor';
if(Object.keys(some_object).includes(key) 
    && some_object[key] === true){
  destroy_earth();
}

aber so richtig sauber sieht mir das noch nicht aus -- geschweige denn elegant.

was waere denn in modernem javascript (und ohne jquery oder andere libs) ein sauberer weg, hashes/maps nutzen zu koennen, sodass ich mir keine gedanken darueber zu machen brauche, ob evtl. bereits eine methode existiert, die den gleichen namen traegt wie ein key eines key-value-pairs?

prost

seth

  1. Hallo seth,

    gudn tach!

    Schön, mal wieder von dir zu lesen. Ich hoffe, du bekommst es hin, die Erde nicht zu zerstören 😀.

    Bis demnächst
    Matthias

    --
    Du kannst das Projekt SELFHTML unterstützen,
    indem du bei Amazon-Einkäufen Amazon smile (Was ist das?) nutzt.
    1. gudn tach!

      Schön, mal wieder von dir zu lesen. Ich hoffe, du bekommst es hin, die Erde nicht zu zerstören 😀.

      schoen, mal wieder hier zu sein und bekannte namen zu lesen. 25 jahre, krasser scheiss. 😀

      die erde wird bereits von den anderen zerstoert. da brauche ich gar nix mehr zu machen.

      prost

      seth

      1. Hallo,

        die erde wird bereits von den anderen zerstoert. da brauche ich gar nix mehr zu machen.

        du sprichst von den Vogonen?

        Gruß
        Kalk

  2. Tach!

    assoziative arrays sind in js wohl objekte (oder umgekehrt, je nach sichtweise).

    Es sind Objekte. Auch Arrays sind Objekte. Sagt zumindest der typeof. Es andersrum zu betrachten ist theoretisch möglich, aber praktisch nicht weiter sinnvoll.

    denn some_object besitzt als objekt standardmaessig die methode constructor (sowie weitere methoden wie toString etc.)

    Genauer gesagt, der Prototyp besitzt diese Methoden. Das ist von Vorteil, denn some_object.hasOwnProperty() liefert in dem Fall false.

    oder ich mach's noch expliziter: […] Object.keys(some_object) […]

    Auch so, aber eher, wenn man darüber iterieren möchte.

    was waere denn in modernem javascript (und ohne jquery oder andere libs) ein sauberer weg, hashes/maps nutzen zu koennen, sodass ich mir keine gedanken darueber zu machen brauche, ob evtl. bereits eine methode existiert, die den gleichen namen traegt wie ein key eines key-value-pairs?

    Object.keys() beim Iterieren, .hasOwnProperty() bei einzelner Abfrage.

    Übrigens, in modernem Javascript braucht man auch kein var mehr. In deinen Beispielen kannst du auch für some_object const verwenden.

    dedlfix.

    1. gudn tach!

      assoziative arrays sind in js wohl objekte (oder umgekehrt, je nach sichtweise).

      Es sind Objekte. Auch Arrays sind Objekte. Sagt zumindest der typeof. Es andersrum zu betrachten ist theoretisch möglich, aber praktisch nicht weiter sinnvoll.

      ok. gelegentlich bin ich ueber solche aussagen gestolpert:

      "I explain how JavaScript objects are also associative arrays (hashes)." (quelle: Objects as associative arrays, quirksmode.org)

      some_object.hasOwnProperty() liefert in dem Fall false.

      ok, das heisst, ich muss, wenn ich hashes nutze, tatsaechlich diese zusaetzliche bedingung einbauen?

      const some_object = {};
      some_object['init'] = true;
      const key = 'constructor';
      if(some_object.hasOwnProperty(key) && some_object[key] === true){
        destroy_earth();
      }
      

      finde ich immer noch nicht huebsch, sondern fehleranfaellig.

      ich habe bei meiner weiteren recherche soeben noch Map gefunden. vielleicht ist das besser geeignet. damit waere der obige code wohl:

      const some_object = new Map();
      some_object.set('init', true);
      const key = 'constructor';
      if(some_object.get(key)){
        destroy_earth();
      }
      

      das sieht sauberer aus.

      Übrigens, in modernem Javascript braucht man auch kein var mehr.

      ach so, stimmt ja. das heisst jetzt let (und verhaelt sich dann endlich so, wie man's von anderen sprachen bereits kennt), hatte ich sogar gelesen, aber wieder vergessen, danke fuer die erinnerung.

      In deinen Beispielen kannst du auch für some_object const verwenden.

      ah, das const, bezieht sich nur auf die oberste ebene des objekts und nicht auf die properties, ok. finde ich nicht intuitiv, aber gut zu wissen. hab's jetzt nachgelesen.

      prost

      seth

      1. Tach!

        some_object.hasOwnProperty() liefert in dem Fall false.

        ok, das heisst, ich muss, wenn ich hashes nutze, tatsaechlich diese zusaetzliche bedingung einbauen?

        const some_object = {};
        some_object['init'] = true;
        const key = 'constructor';
        if(some_object.hasOwnProperty(key) && some_object[key] === true){
          destroy_earth();
        }
        

        finde ich immer noch nicht huebsch, sondern fehleranfaellig.

        Es kommt auf die eigentliche Aufgabenstellung an. Das Problem tritt doch nur auf, wenn key kein bekannter konstanter Wert ist, also aus einer unbekannten Quelle kommt. In dem Fall ist etwas mehr Aufwand nötig, um ihn für alle Werte sicher einsetzen zu können.

        ich habe bei meiner weiteren recherche soeben noch Map gefunden. vielleicht ist das besser geeignet. damit waere der obige code wohl:

        const some_object = new Map();
        some_object.set('init', true);
        const key = 'constructor';
        if(some_object.get(key)){
          destroy_earth();
        }
        

        das sieht sauberer aus.

        Ja, Map gibt es auch. Wenn man nur einen einfachen Key-Value-Speicher braucht, kann man das nehmen. Objekte haben noch andere Eigenschaften. Das muss man für seinen konkreten Fall klären, was da sinnvoller ist.

        In deinen Beispielen kannst du auch für some_object const verwenden.

        ah, das const, bezieht sich nur auf die oberste ebene des objekts und nicht auf die properties, ok. finde ich nicht intuitiv, aber gut zu wissen.

        Muss aber so sein. Man kann nicht garantieren, dass alle Unterelemente konstant bleiben. Man kann ja beliebig Referenzen auf diese erstellen, beziehungsweise die Referenzen können schon existieren. Darüber kann man die Unterlemente ändern.

        dedlfix.

        1. gudn tach!

          Es kommt auf die eigentliche Aufgabenstellung an. Das Problem tritt doch nur auf, wenn key kein bekannter konstanter Wert ist, also aus einer unbekannten Quelle kommt.

          genau. das ist bei mir der fall. der key kommt aus einem freitextfeld. (im minimalbeispiel hattee ich das vernachlaessigt, weil es mir um den allgemeinen fall ging.)

          In dem Fall ist etwas mehr Aufwand nötig, um ihn für alle Werte sicher einsetzen zu können.

          meinst du damit den check mittels hasOwnProperties() oder noch mehr aufwand als das?

          Map

          Ja, Map gibt es auch. Wenn man nur einen einfachen Key-Value-Speicher braucht, kann man das nehmen. Objekte haben noch andere Eigenschaften. Das muss man für seinen konkreten Fall klären, was da sinnvoller ist.

          gesucht war in meinem fall eine entsprechung eines hashes (in perl) bzw. einer map (in c++). dafuer scheint mir Map eher zu passen.

          interessant finde ich auch die loesung, die kai345 vorschlaegt.

          ah, das const, bezieht sich nur auf die oberste ebene des objekts und nicht auf die properties, ok. finde ich nicht intuitiv, aber gut zu wissen.

          Muss aber so sein. Man kann nicht garantieren, dass alle Unterelemente konstant bleiben. Man kann ja beliebig Referenzen auf diese erstellen, beziehungsweise die Referenzen können schon existieren. Darüber kann man die Unterlemente ändern.

          naja, in c++ ist es halt anders. wenn ich da ein objekt als const bezeichne, dann sind auch grundsaetzlich dessen eigenschaften fixiert.

          prost

          seth

        2. Hallo dedlfix,

          Ja, Map gibt es auch.

          Nein, Map gibt's genau dafür. Leider noch nicht sooo lange, aber immerhin kennt es der Internet Explorer 11 und darum sollte man dieses Objekt dringend empfehlen.

          Der Begriff des assoziativen Arrays ist ohnehin fragwürdig. In PHP gibt's den, weil da der Array-Typ so weit gefasst ist, dass er als Hash oder Dictionary herhalten kann.

          In JavaScript kann man plain objects verwenden, um Hashes oder Dictionaries zu implementieren, aber man darf nicht den Meißel als Schraubendreher verwenden (oder umgekehrt).

          • wenn man einen Hash als {} initialisiert, bekommt man einen Standard-Objektprototypen mit allen Konsequenzen. Das kann man vermeiden, wenn man myHash=Object.create(null) aufruft, das erzeugt ein Objekt ohne Prototyp. Dann gibt's keine störenden Properties mehr, aber auch kein Basis-Set an Methoden wie hasOwnProperty. Statt dessen muss man Object.hasOwnProperty.call(myHash, 'foo') aufrufen.
          • Einen Prototypen zu haben ist bequemer, aber dann darf man die Existenz eines Propertys nicht mit if (myHash['foo']) implementieren, sondern mit myHash.hasOwnProperty('foo'). Genau dafür ist die Methode da.

          Wichtig ist aber die Erkenntnis, dass Plain Old Javascript Objects keine idealen Maps sind. Dafür gibt es das Map Objekt. Man muss gucken, welcher Browser welche Methoden unterstützt (bei MDN oder Kangax), und nach Bedarf Polyfills bereithalten.

          Rolf

          --
          sumpsi - posui - obstruxi
      2. const some_object = new Map();
        some_object.set('init', true);
        const key = 'constructor';
        if(some_object.get(key)){
          destroy_earth();
        }
        

        das sieht sauberer aus.

        Alternativ, könnte man auch eine Funktion als Map benutzen:

        const map = key => (
         (key === 'foo') ? 42 :
         (key === 'bar') ? 43 : undefined
        )
        
        console.assert(map('foo') === 42)
        console.assert(map('bar') === 43)
        console.assert(map('test') === undefined)
        

        Solche funktionalen Maps kann man sich auch dynamisch zusammenbauen:

        function empty(key) {
          return undefined;
        }
        
        function set(key, value, map) {
          return lookup => (key === lookup) ? value : map(lookup);
        }
        
        const map = set('foo', 42, set('bar', 43, empty))
        console.assert(map('foo') === 42)
        console.assert(map('bar') === 43)
        console.assert(map('test') === undefined)
        

        Ist aber syntaktisch leider nicht so schön und Lesezugriffe brauchen lineare Laufzeit.

  3. der code-schnipsel

    var some_object = {};
    some_object['init'] = true;
    if(some_object['constructor']){
      destroy_earth();
    }
    

    wuerde (wenn die funktion destroy_earth korrekt implementiert ist), fatale folgen haben.

    denn some_object besitzt als objekt standardmaessig die methode constructor (sowie weitere methoden wie toString etc.)

    var some_object = Object.create(null);
    --
    Stur lächeln und winken, Männer!
    1. gudn tach!

      var some_object = Object.create(null);
      

      oh, das ist ja cool. ein objekt ohne eigenschaft. wusste nicht, dass das geht.

      prost

      seth