ugrás a tartalomhoz

Creating A Client-Side Shopping Cart

janez · 2014. Feb. 15. (Szo), 23.18
Kliens oldali bevásárló kosár elkészítése
 
1

Hogyan ne

Hidvégi Gábor · 2014. Feb. 16. (V), 16.36
Azt hiszem, a cím végéről hiányzik a következő: "- worst practices", a szerző minden tipikus hibát elkövetett, amit csak lehetett. Valamelyik nap olvastam az egyik sztárblogger egy bejegyzésében, hogy amit JS-ben meg lehet csinálni, azt meg is fogják, csak azt a kérdést felejtette el feltenni, hogy "Oké, hogy lehet, de érdemes-e?".

  • Mivel tisztán kliensoldali megoldás, ez több problémát is hoz magával:
    1, A látogatók átlagosan 1-2%-ának különböző okok miatt nincs bekapcsolva a JS, azaz ők nem fognak tudni vásárolni, ez a boltnak éves szinten 1-2%-os bevételkiesést jelent, ami a fejlesztő felelőssége.

    2, Ha a böngészőt véletlenül bezárják vagy összeomlik, a kosár tartalma elveszhet (utóbbi esetben a böngészőtől függ); szerveroldali technológiákkal ez részben kivédhető (ha a felhasználó bejelentkezik, hozzárendelhetjük a kosarat).
  • A jQuery használata akár többszázszor lassabb futást eredményez, mint a natív megoldás; már magának a jQuerynek a betöltése is tizedmásodperceket vesz igénybe egy PC-n. Animációkat is gyorsabb és hatékonyabb css-ből megoldani.
  • Az adatok tárolása a data-* attribútumokban nemcsak sérti a szétválasztás elvét (separation of concerns), hisz a HTML kódba olyan dolgokat keverünk, aminek semmi köze a megjelenéshez (csak a működéshez), másrészt az adatok tárolása és kinyerése HTML elemekből lassú (Firefoxban egy <div>-nek például 244 tulajdonsága és metódusa van, ekkora tömbön kell végigiterálni, amikor adatok után hlászunk).

    A data-* attribútumokban tárolt adatokat beolvasás után konvertálgatni kell, ez is lassítja a futást és bonyolítja a programot.
  • Bár a szerző célja, hogy OOP elvek szerint építse fel a programot, ez csak részben sikerült, például a tárhely kezelése nem elég absztrakt, így, ha alternatív módon szeretnénk az adatokat tárolni, több helyen kell módosítani a kódot.
  • A HTML kód előállítása két helyen történik, egyrészt a nyers HTML-ben, másrészt pedig karakterláncok összeragasztgatásából JS-ben. Ez mindenképp megnehezíti a karbantartást.



Igazából semmi sem indokolja a JS megvalósítást, hagyományos szerveroldali programozással ugyanezt az eredményt lehet elérni, ami ráadásul kevésbé terheli a klienst (például AJAX hívásokkal csak az oldal megfelelő részeit frissítjük, így minimális adatforgalomra van szükség). Ez az egész épp annyira abszurd, mint például alkoholt hirdetni fiatal, fogamzóképes nőkkel, hisz az alkohol köztudottan tompítja a szexuális működést, impotenciára és depresszióra hajlamosít, az ember tántorog tőle és gyengül az ítélőképessége, károsítja a belső szerveket és függőséget okoz.

3

Kísérlet

Hidvégi Gábor · 2014. Feb. 16. (V), 16.41
Kíváncsiságból össze szerettem volna hasonlítani pár megoldást ugyanarra a problémára: az adatok lekérésének sebességére. Letöltöttem a legfrissebb jQueryt, és az első két függvényben ezt használtam, a harmadikban DOM 1 függvényekkel értem el az elemeket és az adatokat, míg a negyedik függvényben egyszerűen egy tömbön iterálok végig. Ezeknek az eredményét a konzolba logolom (öt érték jelenik meg, az első a jQuery betöltésének sebessége).

Firefoxban következők lettek a függvények sebességei:
~1200ms
~820ms
~31ms
~1-2ms

A negyedik megoldás nemcsak több mint ezerszer gyorsabb az elsőnél, hanem elkülönülnek benne az adatok a kódtól, ezért ezt ajánlom mindenkinek, továbbá senkinek sem javaslom, hogy DOM-ban tároljon bármilyen adatot.

Hogyan lehet ezt megvalósítani? Úgy, hogy nem a HTML-be közvetlenül írjuk az adatokat, hanem például az oldal végén létrehozunk egy script blokkot, és abban egyenesen a JS tömböket generáljuk le szerveroldalon.
2

Kód

Hidvégi Gábor · 2014. Feb. 16. (V), 16.39
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script>var ido1 = new Date().getTime();</script>
<script src="jquery-1.11.0.min.js"></script>
<script>console.log(new Date().getTime() - ido1);</script>
<body>
  <form action="order.html" method="post" id="checkout-order-form">
    <h2>Your Details</h2>
    <fieldset id="fieldset-billing">
      <legend>Billing</legend>
        <!-- Name, Email, City, Address, ZIP Code, Country (select box) -->

      <div>
        <label for="name">Name</label>
        <input type="text" name="name" id="name" data-type="string" data-message="This field may not be empty" />
      </div>

      <div>
        <label for="email">Email</label>
        <input type="text" name="email" id="email" data-type="expression" data-message="Not a valid email address" />
      </div>

      <div>
        <label for="city">City</label>
        <input type="text" name="city" id="city" data-type="string" data-message="This field may not be empty" />
      </div>

      <div>
        <label for="address">Address</label>
          <input type="text" name="address" id="address" data-type="string" data-message="This field may not be empty" />
      </div>

      <div>
        <label for="zip">ZIP Code</label>
        <input type="text" name="zip" id="zip" data-type="string" data-message="This field may not be empty" />
      </div>

      <div>
        <label for="country">Country</label>
          <select name="country" id="country" data-type="string" data-message="This field may not be empty">
            <option value="">Select</option>
            <option value="US">USA</option>
            <option value="IT">Italy</option>
          </select>
      </div>
    </fieldset>

    <div id="shipping-same">Same as Billing <input type="checkbox" id="same-as-billing" value=""/></div>

    <fieldset id="fieldset-shipping">
      <legend>Shipping</legend>
      <!-- Same fields as billing -->
    </fieldset>

    <p><input type="submit" id="submit-order" value="Submit" class="btn" /></p>

  </form>

<script>
  var form, i, l, tipus, uzenet, elem, j, l2;
  function jq_data() {
    var ido1 = new Date().getTime(), form, l, elem, tipus, uzenet;
    form = jQuery(jQuery.find('#checkout-order-form')[0]);
    for (i = 0; i < 1000; i++) {
      l = form.find(':input');
      l.each(function() {
        elem = $(this);
        tipus = elem.data('type');
        uzenet = elem.data('message');
      });
    }
    return new Date().getTime() - ido1;
  }
  function jq_kozvetlen() {
    var ido1 = new Date().getTime(), form, l, elem, tipus, uzenet, j, l2;
    form = jQuery(jQuery.find('#checkout-order-form')[0]);
    for (i = 0; i < 1000; i++) {
      l = form.find(':input');
      for (j = 0, l2 = l.length; j < l2; j++) {
        elem = l[j];
        tipus = elem.dataset['type'];
        uzenet = elem.dataset['message'];
        //tipus = elem['data-type'];
        //uzenet = elem['data-message'];
      }
    }
    return new Date().getTime() - ido1;
  }

  function dom_attributum() {
    var ido1 = new Date().getTime(), form, l, elem, tipus, uzenet, j, l2;
    form = document.getElementById('checkout-order-form');
    for (i = 0; i < 1000; i++) {
      l = form.getElementsByTagName('input');
      l = [].concat(Array.prototype.slice.call(l), Array.prototype.slice.call(form.getElementsByTagName('select')));
      for (j = 0, l2 = l.length; j < l2; j++) {
        tipus = l[j]['data-type'];
        uzenet = l[j]['data-message'];
      }
    }
    return new Date().getTime() - ido1;
  }
  function tomb() {
    var ido1 = new Date().getTime(), tipus, uzenet;
    var elemek = [
      {azonosito: 'name', tipus: 'string', uzenet: 'This field may not be empty'},
      {azonosito: 'email', tipus: 'expression', uzenet: 'This field may not be empty'},
      {azonosito: 'city', tipus: 'string', uzenet: 'This field may not be empty'},
      {azonosito: 'address', tipus: 'string', uzenet: 'This field may not be empty'},
      {azonosito: 'zip', tipus: 'string', uzenet: 'This field may not be empty'},
      {azonosito: 'country', tipus: 'string', uzenet: 'This field may not be empty'},
    ];
    for (i = 0; i < 1000; i++) {
      for (var j = 0, l2 = elemek.length; j < l2; j++) {
        tipus = elemek[j].tipus;
        uzenet = elemek[j].uzenet;
      }
    }
    return new Date().getTime() - ido1;
  }
  
  console.log(jq_data());
  console.log(jq_kozvetlen());
  console.log(dom_attributum());
  console.log(tomb());
</script>
</body>
4

Ha mar a worst practiceknel

blacksonic · 2014. Feb. 16. (V), 20.02
Ha mar a worst practiceknel jarunk, a globalis valtozok es a szamozott es egybetus valtozonevek is ide tartoznak
Igen lassabb jQuery plusz mas frameworkokkel viszont prototipust gyorsan konnyebb vele osszerakni, aztan ha kell a teljesitmeny optimalizalsz, nem kell ezt a teljesitmeny dolgot tullihegni
5

Ha mar a worst practiceknel

Hidvégi Gábor · 2014. Feb. 16. (V), 21.38
Ha mar a worst practiceknel jarunk, a globalis valtozok es a szamozott es egybetus valtozonevek is ide tartoznak
Ha a globális változók népszerűsítése lett volna a célom, akkor mindent így használtam volna. Mivel nem ez a helyzet, nyilvánvaló, hogy a végső függvények elkészítésekor figyelmetlen voltam, és valami kimaradt a var-okból. Innentől kezdve a hozzászólásod első része inkább rosszindulatról, mint konstruktivitásról szól.

ha kell a teljesitmeny optimalizalsz
A teljesítmény az egész mondanivalómnak csak egy része volt, de nem a legfontosabb.
7

Arra akartam csak ramutatni

blacksonic · 2014. Feb. 16. (V), 22.35
Arra akartam csak ramutatni vele, hogyha mar porig lehordasz egy teljesen elfogadhato, karbantarthato, szepen megtervezett megoldast es nem bad hanem egyenesen worst practicekrol beszelsz, mikozben a te peldad is hemzseg a bad practicektol.
Nem ertem miert faj neked az ha valaki absztraktabb szep megoldast valaszt, ugy hogy az adott esetben egyaltalan nem bantja a felhasznaloi elmenyt.
Igen nem mindig kell behuzni tizen X libraryt, de nem is errol szol a blogposzt.
8

Tegyünk különbséget a

Hidvégi Gábor · 2014. Feb. 16. (V), 23.30
Tegyünk különbséget a szándékos tervezés és a figyelmetlenség között! Az ellenérveimet pedig leírtam az 1-es hozzászólásban. Ha valamivel nem értesz egyet, ne fogd vissza magad.

miert faj neked az ha valaki absztraktabb szep megoldast valaszt
Mert szerintem se nem elég absztrakt, se nem szép. A 3-as hozzászólásom negyedik függvényében felvázolt megoldás nemcsak egyszerűbb, átláthatóbb (és emiatt karbantarthatóbb), hanem három nagyságrenddel gyorsabb is a cikkben vázolt lehetőségnél.
6

form =

bamegakapa · 2014. Feb. 16. (V), 22.25
form = jQuery(jQuery.find('#checkout-order-form')[0]);
helyett
form = jQuery('#checkout-order-form');
bőven elég lenne, nem?
9

Részletkérdés

Hidvégi Gábor · 2014. Feb. 17. (H), 12.16
Biztosan.