Javascriptový problém: pojmenování obsahu pole [příspěvek v archivu]

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.

Petr Staníček, 4. 12. 2007 000 20.44 • Rubrika: IT, Všeobecné, Webdesign19 komentářů u textu s názvem Javascriptový problém: pojmenování obsahu pole

19 komentářů k článku »Javascriptový problém: pojmenování obsahu pole«

[1] Vložil(a): Martin Hassman 4. 12. 2007 v 23.06

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] Vložil(a): karf 4. 12. 2007 v 23.25

[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] Vložil(a): Willik 4. 12. 2007 v 23.57

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] Vložil(a): Pixy 5. 12. 2007 v 1.23

[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] Vložil(a): Willik 5. 12. 2007 v 1.41

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

[6] Vložil(a): Willik 5. 12. 2007 v 2.09

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] Vložil(a): Pixy 5. 12. 2007 v 2.28

[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] Vložil(a): aprilchild 5. 12. 2007 v 4.52

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] Vložil(a): Pixy 5. 12. 2007 v 9.58

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] Vložil(a): Radek 5. 12. 2007 v 10.56

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] Vložil(a): Filip Jirsák 5. 12. 2007 v 11.21

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] Vložil(a): Pixy 5. 12. 2007 v 11.29

[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] Vložil(a): Pixy 5. 12. 2007 v 11.44

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

[14] Vložil(a): Filip Jirsák 5. 12. 2007 v 11.49

[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] Vložil(a): Willik 5. 12. 2007 v 14.37

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

[16] Vložil(a): Pixy 5. 12. 2007 v 15.11

[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] Vložil(a): patrik.ovx 6. 12. 2007 v 10.06

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] Vložil(a): Pixy 6. 12. 2007 v 11.14

[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] Vložil(a): patrik.ovx 6. 12. 2007 v 13.40

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

Váš komentář

Upozornění: Pokud vás téma tohoto příspěvku nezajímá, nebaví, dotýká se vás či vás dokonce uráží, tak prosím odejděte a pokud možno se nadále ve vlastním zájmu dalším podobným vyhýbejte. Hlavně se to prosím nesnažte autorovi sdělovat v komentářích, takové příspěvky nikoho nezajímají a budou nejspíš vymazány. Totéž platí pro vaše názory na osobu autora, na jiné přispěvatele, mluvení z cesty, ze spaní či pod vlivem omamných látek a další podobné výlevy nesouvisející s tématem článku. Jinými slovy, toto je prostor soukromého blogu určený pro komentování příspěvku publikovaného výše, nikoli k chatování a volné diskusi. Děkuji za pochopení.

Abyste mohli komentovat, musíte se přihlásit.