Javascriptový problém: pojmenování obsahu pole

Narazil jsem na docela zajímavou otázku v Javascriptu a zajímalo by mě, jak takovou věc co nejelegantněji vyřešit. Řekněme, že mám funkci, která od nějkaého cizího externího zdroje dostává jediný parametr, který je pole. Pořadí a význam jeho položek jsou pevně dány, délka pole je proměnná (nemusí být uvedeny všechny definované položky) a podobu a způsob předávání toho parametru nemohu nijak ovlivnit. Otázka zní, jak co nejlépe načíst toto pole tak, abych na jeho položky nepřistupoval přes indexy, ale přes nějaká smysluplná jména.

V PHP to udělám snadno:

list($jmeno,$prijmeni,$datum,...,$status) = $arr;

V Javascriptu ale (pokud vím) podobný konstrukt není. Úplně nejpitomější řešení je nasnadě:

function X(arr) {
   var jmeno = arr[0];
   var prijmeni = arr[1];
   var datum = arr[2];
// ...
   var status = arr[arr.length-1];
// ...
   alert(prijmeni);
   }

Což je vážně docela hloupé. Malinko lepší (nebo spíš čistší) se jeví přiřazení do objektu:

function X(arr) {
   var osoba = {
      jmeno: arr[0],
      prijmeni: arr[1],
      datum: arr[2],
//    ...
      status: arr[arr.length-1]
      }
// ...
   alert(osoba.prijmeni);
   }

Taky si můžu ty názvy předdefinovat a přiřazení zautomatizovat:

function X(arr) {
   var def = ['jmeno','prijmeni','datum', ..., 'status'];
   var n = def.length;
   if (arr.length<n) n = arr.length;
   var osoba;
   for (var i=0;i<n;i++) osoba[def[i]] = arr[i];
// ...
   alert(osoba['prijmeni']);
   }

anebo rovnou jako správný drsoň:

Array.prototype.addKeys = function(def) {
   var n = Math.min(def.length, this.length);
   for (var i=0;i<n;i++) this[def[i].toString()] = this[i];
   }
function X(arr) {
   arr.addKeys(['jmeno','prijmeni','datum', ..., 'status']);
// ...
   alert(arr.prijmeni);
   }

Ale určitě existuje nějaký lepší, elegantnější způsob, jak si ten obsah pole pojmenovat. Napadne někoho něco?


Update: Moc pěkné řešení

Kolega wiki přišel na poměrně elegantní a podle mě vtipné řešení: předat pole, které funkce dostane, jako standardní parametry druhé funkci, která už s nimi bude pracovat normálně. Nejlépe ukázat na příkladu:

function X(arr) {
   X2.apply(null,arr);
   }
function X2(jmeno,prijmeni,datum,...,status) {
// ...
   alert(prijmeni);
   }

Podle potřeby bych si to mohl upravit do nějakých sofistikovanějších tvarů, ale princip zůstává. Ve finále by to mohlo vypadat například takhle:

function X(arr) {
   (function (jmeno,prijmeni,datum,...,status) {
//    ...
      alert(prijmeni);
      }).apply(null,arr);
   }

Má to jen malou mušku v tom, že to funguje až v Javascriptu 1.3, resp. JScriptu 5.5, tedy od IE 5.5 (v 5.0× to chodit nebude), ale to už by dnes nemělo skoro nikoho trápit. Díky moc za supr nápad.

19 komentářů u „Javascriptový problém: pojmenování obsahu pole“

  1. Lámal jsem si hlavu, ale nakonec mi vždy vypadlo řešení podobné některému z těch, co jsi už napsal. Opatrně bych si troufal tvrdit, že tohle současný JavaScript lépe nezvládne (opatrně pro případ, že to někdo v dalším komentáři vyvrátí). Varianta addKeys je zaručeně cool a trendy.

    Co tak brousím připravovaným ECMA Script 4 (JavaScript 2) href=„http://­www.ecmascrip­t.org/es4/spec/o­verview.pdf“ rel=„nofollow ugc“>http://ww­w.ecmascript.or­g/es4/spec/over­view.pdf tam už by to mělo jít některou z jeho konstrukcí, ale tě bude zajímat tak za 3–5 let.

  2. [1] Chtěl jsem zrovna napsat komentář podobného znění, takže jen přitakám. Jen s výhradou vůči přidávání funkce přímo do prototypu Array a se zpochybněním termínu použitelnosti ES4 :(. Ještě mě napadlo využití objektu arguments uvnitř funkce a předávat klíče přímo jako parametry, ale na principu to nic nemění.

  3. Nevim jestli jsem spravne pochopil zadani ale od verze JS 1.7 by melo fungovat neco takoveho:

    var inArray = [‚jmeno‘,‚prij­meni‘,‚datum‘,‚a­dresa‘];

    var [jmeno,prijme­ni,datum,adre­sa] = inArray;

    alert (jmeno + " " + prijmeni + " " + datum + " " +adresa);

  4. [3] Ano, to je přesně ono, co by se mi líbilo. Otázka ale zní: je JS 1.7 implementován taky jinde než ve FF2? Obávám se, že v IE to fungovat nebude (ale pro jistotu vyzkouším).

  5. Bohuzel co vim tak v IE6 to nefunguje, coz je dnes jeste problem.

  6. Pak bych mel jeste jedno reseni, ale je to trochu pres koleno – protoze IE a Opera nepodporuje forEach tak je nutne upravit prototype pro Array (je to z mozilla.org a jak sem to prolit tak to funguje).
    Cele to reseni neni uz tak pekne ale urcite by se dalo pouzit:

    <script type="text/javascript" language="JavaScript1.7">
    //  pro IE a Opera uprava prototype - bohuzel nutnost
    
    if (!Array.prototype.forEach)
    {
      Array.prototype.forEach = function(fun /*, thisp*/)
      {
        var len = this.length;
        if (typeof fun != "function")
          throw new TypeError();
        var thisp = arguments[1];
        for (var i = 0; i < len; i++)
        {
          if (i in this)
            fun.call(thisp, this[i], i, this);
        }
      };
    }
    
    // pole hodnot ktere prijde
    
    var inArray = ['jmeno - hodnota','prijmeni - hodnota','datum - hodnota','adresa - hodnota'];
    
    // pole nazvu indexu pro pojmenovani
    
    var reqList = ['jmeno','prijmeni','datum','adresa'];
    
    // vysledne asoc. pole
    
    var outArray = [];
    
    reqList.forEach( function(element, index, array){
    
        outArray[element] = inArray[index];
    
        }
    
        )
    
    // testik
    
    alert (outArray.prijmeni);
    
    </script>
  7. [6] … když už bych kvůli té jedné malé funkci měl opravovat prototyp Array, tak tam radši rovnou udělám tu metodu addKeys() a je to.

  8. V JavaScriptu zadna podobna konstrukce v soucasnosti neni (beru v potaz implementace bezici alespon od IE6). Osobne bych to resil nejak takhle:

    <html>
    <body>
       <script type="text/javascript">
    
    
          function map(arr, mapping)
          {
             var my = {}; var i=0; var n=arr.length; while (i<n) my[mapping[i]] = arr[i++];
             return my;
          }
    
          function X(arr)
          {
             var person = map(arr, ['first_name', 'last_name', 'email', 'phone']);
             alert(person.first_name + ' ' + person.last_name + ', E: ' + person.email + ', T: ' + person.phone);
          }
    
          X(['Petr', 'Krontorad', 'petr@krontroad.com', '800 123456']);
    
    
          // hromadny mapping - pokud je v aplikaci vic definic, rozsiruju `map' o 2 pomocne funkce.
    
          var maps = {
             person: ['first_name', 'last_name', 'email', 'phone'],
             contact: ['fax', 'email', 'mobile']
          }
    
          function umap(arr, map_id)
          {
             return map(arr, maps[map_id]);
          }
    
          // vrati konretni polozku podle identifikatoru {mapa.nazev_polozky}
          function smap(arr, map_ident)
          {
             map_ident = map_ident.split('.')
             return map(arr, maps[map_ident[0]])[map_ident[1]]
          }
    
    
          // pouziti u(niversal)map
          var external_data = ['Petr', 'Stanicek', 'pixy@ixpy.cz', '800 098765'];
    
          person = umap(external_data, 'person');
          alert('Email is: ' + person.email);
          // pouziti s(ingle)map
          alert('First name: ' + smap(external_data, 'person.first_name'));
    
          // dalsi priklad
          external_data = ['+420 545 654534', 'info@spam-me.com', '800 098765'];
          contact = umap(external_data, 'contact');
          alert('Spam me at: ' + contact.email + ' or fax to: ' + contact.fax);
    
    
       </script>
    </body>
    </html>
  9. ad [6] – trochu buldozer na mravence a hlavně viz [7] – když už se rozhodnu jít do vlastních rozšíření std. objektů, udělám to jednodušeji a efektivněji.

    ad [8] – ať koukám, jak koukám, žádný rozdíl oproti tomu namapování, co uvádím v článku, nevidím. Akorát to rozšiřujete o další složitější funkce. To už je málem framework… ;-)

    Jde fakt o malou jednoduchou funkci a co nejefektivnější pojmenování položek pole, abych při jejich zpracování nemusel pracovat s nicneříkajícími indexy, jako

    if (d[11]) x = d[7] + (d[2]?d[3]:d[4]); atd… Udělat si na to nějaký framework je v tomhle případě to nejsnazší a nejpohodlnější – a nejméně vhodné řešení :-D

  10. Možná je to nehodné moderního dynamicky typovaného jazyka, ale pro úplnost je potřeba zmínit klasické, historií prověřené řešení:

    var P_JMENO = 1;
    var P_PRIJMENI = 2;
    var P_DATUM=3;

    if (data[P_JMENO] == data[P_PRIJMENI]) alert(„Jmenujete se divne“)

  11. Ta PHP funkce se dá přibližně imitovat následujícím kódem:

    function List(arr) {
       for (var i = 1; i<arguments.length; i++) {
          this[arguments[i]] = arr[i-1];
       }
    }
    
    //test
    var arr = new Array("abc", "def");
    var obj = new List(arr, "a", "b", "c");
    alert(obj.a);
    alert(obj.b);
    alert(obj.c);
  12. [11] Jo, to je pěkné! Dík. ;)

    Jen bych ještě doplnil do té funkce nějaké testy, protože podle zadání to pole nemusí obsahovat všechny položky a arr může být kratší než počet argumentů.

  13. Doplnil jsem do článku jedno moc pěkné řešení, podle mě zatím nejlepší.

  14. [12] Pokud bude pole kratší, zbývající vlastnosti budou mít hodnotu undefined, takže všechny běžné testy se k tomu zachovají správně:

    alert(obj.c == undefined);
    alert(obj.c == null);
    alert(!obj.c);
    alert(typeof(obj.c) == „undefined“);

    Jediné, co se bude chovat jinak, je test na samotnou existenci vlastnosti:

    alert(„c“ in obj);

    Pokud vlastnost, která nemá odpovídající element, nemá vzniknou ani s hodnotou undefined, měla by stačit následující úprava:

    function List(arr) {
    for (var i = 0; i < Math.min(argu­ments.length-1, arr.length); i++) {
    this[arguments[i+1]] = arr[i];
     }
    }

  15. To reseni – apply(null,arr); je rozhodne nejlepsi, bezvadnej napad

  16. [15] Bohužel jsem ho před chvílí zkusil použít v jedné aplikaci a v IE6 to nefunguje. Nemám čas teď zkoumat, co je špatně, ale určitě to potřebuje nějaké důkladnější otestování. Podle specifikace by function.apply() mělo fungovat od IE 5.5, otázkou je, co s tím udělá ta varianta s null.

  17. var def = ['patrik','ovx'];
    Array.prototype.list = function() {
       for(var i=0; i<arguments.length; i++){
          eval('Object.prototype.'+arguments[i]+'="'+this[i]+'"');
       }
    }
    
    def.list('a','b');
    alert(a);
  18. [17] To je ovšem pro pořádně drsné jinochy. Narvat do všech objektů v JS natvrdo property „patrik“ a „ovx“… A co kdyby definice vypadala třeba takhle:

    def = ['name','id','style','constructor','__proto__']

    to by teprve začala správná zábava… Ne, se vší úctou, tohle fakt nepovažuju za dobré řešení, ani kdyby v tom eval() místo toho Object.prototype bylo (imho správněji) Array.prototype – i tak je to příliš drsné a extrémně nebezpečné. ;-)

  19. [18] předpokládám, že šlo o to ukázat cestu. vstup by se měl ošetřit a místo Object by asi šlo napsat this, ale pak jsme zase u řešení p. Jirsáka :)
    Předpokládám, že zdatný programátor si to už upraví sám ;-)
    Nevím o jaké nebezpečnosti u javascriptu mluvíte? ;-)

Napsat komentář