24 Dec

Javascript pakk No. 1 - ECMAScript 6


A frontend fejlesztők élete nem csak játék és mese. Nem elég hogy a javascript prototype object modelje sokakban a hányingerre kisértetiesen emlékeztető érzéseket kelt, mindezt megfejelik aszinkron funkcionalitással és callback hegyekkel, a dinamikus típusosságról nem is beszélve.

Persze a nyelv fejlesztői mindezzel tökéletesen tisztában vannak, ezért kifejlesztették egymás közt a csuklás legjobb gyógymódját, az ECMAScript 6-os szabványt!

Ez sok újdonságot hoz a nyelvbe, viszont a böngészők egy része még nem támogatja vagy nem teljesen, viszont van rá mód, hogy azok számára is emészthetővé tegyük. A későbbiekben erről is írok.

Menjünk hát végig, hogy miben változik a szabvány az eddigiekhez képest!

Konstansok


A nyelv eddig nem támogatta a konstansokat, vagyis olyan "változókat", amiknek nem lehet megváltoztatni a tartalmát a definiálást követően.
Megjegyzés: A változó maga nem változhat, viszont az ahhoz rendelt tartalom igen. Tehát egy objectre mutató pointer ugyanarra az objectre fog mutatni, viszont az objektum maga változhat.

// ECMAScript 6

const PI = 3.141593

// ES5-ben az object helperekkel lehetett megvalósítani

// és azt is csak global scope-ba

Object.defineProperty(typeof global === "object" ? global : window, "PI", { 
  value: 3.141593, 
  enumerable: true, 
  writable: false, 
  configurable: false 
});

Scope-ok


A nyelvben eddig nem volt lehetőség ún. block-scoped változók deklarálására. Két opció volt eddig, mikor globális változót hoztunk létre, sima értékadással:
pi = 3.14; // globális, a definiálás helyétől függetlenül

A másik opció, mikor a var kulccsó használatával lokális változót hozunk létre.
function scoping() {
   var teszt = 5; // a létrehozó function-ön belül elérhető

   console.log(teszt); // 5

}

function scoping2() {
   for (var x = 0; x < 10; x++) {
      var teszt2 = 36;
   }
   console.log(teszt2);  // 36 még itt is elérhető, hiszen a létrehozó function ugyanaz

}
console.log(teszt); // undefined

console.log(teszt2); // undefined

Let it be!


Na és akkor jöjjön az újdonság az ES6 oldalról. A let kulcsszó segítségével ún. block scoped változókat tudunk létrehozni, amik nem lesznek az egész tartalmazó function-ön belül elérhetőek (ahogy azt minden normális nyelvben is lehet):
function teszt() {
   let teszt = 36
   for (var x = 0; x < 10; x++) {
      let teszt = 5; // csak az adott blokkon belül (jelen esetben a for ciklus) érhető el

   }
   console.log(teszt); // 36

}

Na most akkor ismét idézném a kedves orosz kollégát.. How cool is that?java4eveer-540x312

 

.NET betyárok, szevasztok!


Aki foglalkozott már valaha C#-al, az már bizonyára belefutott az ún. lambda kifejezésekbe. Hasonló (de működését tekintve más) szintax érkezett most az ES6-al. Akinek új: röviden egy egyszerűbb és átláthatóbb szintaxis a closure-ök létrehozására:
// ES5 módi


valamilyen.metodus(function(x) { return x + 1; }); // ez eddig is ment, nincs ebben semmi új, igaz?

valamilyen.metodus(function(x,y) { return x + y;}); // ez se új

// ES6

valamilyen.metodus(x => x + 1); // he? várjunk csak.. ez annyira nem bonyolult.. sőt, egész jó, nem?

// akkor most bonyolítsuk kicsit

valamilyen.metodus((x,y) => x + y); // hoppá, megy ez több paraméterrel is? Hol volt az az orosz idézet?

Nézzük meg mindezt pl egy forEach-nek átadott functionben!
// (Good) Plain old ES5

tomb.forEach(function(v) {
     if (v % 5 === 0) {
         fives.push(v);
     }
}); 

// ES6 style

tomb.forEach(v => {
    if (v % 5 === 0) {
         fives.push(v);
    }
});

THIS, you are here!


Emlékeztek még arra, amikor javascriptben nem kellett újraassign-olni az aktuális objektumra mutató pointer (this) értékét egy lokális változóba, vagy éppen bindolni azt (ES 5.1 után) a meghívott függvényben? Nem? Én se. Viszont ezeknek az időknek vége! Mostantól a this, az ahogy nevéből is adódik. Ez lesz. Nem pedig valami más.

Maradjunk az előbbi forEach példánál:
// ES5 style

var self = this;
this.nums.forEach(function(v) {
    if (v % 5 === 0) {
       self.fives.push(v);
    }
}) 

// ES5.1+ style


this.nums.forEach(function(v) {
    if (v % 5 === 0) {
       this.fives.push(v);
    }
}.bind(this)); // itt bekötjük a this context-et a functionbe

// ES6 style

this.nums.forEach(function(v) {
     if (v % 5 === 0) {
         this.fives.push(v); // se this újraassign-olás, se bind, csak gyönyörű haj!

     }
 });

Default parameter value


Akik PHP-vel foglalatoskodnak, azoknak nem lesz újdonság, hogy ún. alapértelmezett értékekkel adjuk át a függvényeinknek a paramétereket. Tehát ha az adott paraméter nem kerül átadásra, akkor is hozzárendel valami értéket. Persze jól szituált hákolással ez is megvalósítható volt eddig, nézzük hogy is zajlott mindez:
function f (x, y, z) {
 if (y === undefined)
 y = 7;
 if (z === undefined)
 z = 42;
 return x + y + z;
};
f(1) === 50; // jóféle hákolás, mi?

Akkor nézzük meg mennyivel egyszerűsödik le az életünk most az ES6-al:
function f (x, y = 7, z = 42) {
 return x + y + z
}
f(1) === 50

Hát komolyan, szóhoz se lehet jutni, már kezd olyan lenni az egész, mintha valami programnyelv lenne, nem? De a java még hátravan!

Rest parameter


A napfényes polimorfizmus egyik formája az ún. method overload. Sajnos ezen nyelvben erre nincs lehetőség olyan formában, mint pl. Javaban vagy C#-ben, viszont amit pluszban odapasszolunk a függvényünknek, azt be tudjuk csomagolni egy tömbbe:
// ES5 módi

function f (x, y) {
 var a = Array.prototype.slice.call(arguments, 2); // fogjuk és levágjuk az első két elemét az átadott paraméterek alkotta tömbnek

 return (x + y) * a.length;
};
f(1, 2, "hello", true, 7) === 9;

Akkor nézzük mennyivel közelebb áll ez a világunkhoz az ES6:
function f (x, y, ...a) { // a ...a jelenti az összes többi argumentumot tömbbé alakítva, amiket esetleg megkap a függvényünk

 return (x + y) * a.length
}
f(1, 2, "hello", true, 7) === 9

Ha már ennyire szétbontunk mindent tömbökre, akkor nézzük hol lehet még a spreading syntaxot használni?
// ES5 style

var params = [ "hello", true, 7 ]; // alap tömbünk

var other = [ 1, 2 ].concat(params); // [ 1, 2, "hello", true, 7 ] // régen ezt csak concattal lehetett beleoktrojálni a másikba

f.apply(undefined, [ 1, 2 ].concat(params)) === 9; // az előző függvényünket használva kipróbáljuk azt

var str = "foo";
var chars = str.split(""); // [ "f", "o", "o" ] // stringet csak splittel tudunk az egyes karakterek alkotta tömbbé alakítani


// ES6

var params = [ "hello", true, 7 ]
var other = [ 1, 2, ...params ] // [ 1, 2, "hello", true, 7 ] // spreadelve adjuk át az elemeket, mintha concat lenne

f(1, 2, ...params) === 9
var str = "foo"
var chars = [ ...str ] // [ "f", "o", "o" ] spread a stringet is :O

Template literals (template strings)


PHP-ben már korábban is jelen volt (és egyes esetekben okozhatott meglepetéseket) a következő feature. Javascriptben eddig, ha változókat akartunk behelyettesíteni stringbe, akkor a string replace-el vagy épp egyesével összerakosgatva tudtuk megtenni azt. PHP-ben a ""-ök közötti stringekben elhelyezett változók értékét automatikusan behelyettesítette a rendszer, hasonló került most be az ES6-al, de nézzük az eddigi hákolásos megoldásokat:
// ES5 : Based on a true story 

var customer = { name: "Foo" };
var card = { amount: 7, product: "Bar", unitprice: 42 };
message = "Hello " + customer.name + ",\n" + // a jó öreg összeollózott karakterliterál

"want to buy " + card.amount + " " + card.product + " for\n" +
"a total of " + (card.amount * card.unitprice) + " bucks?";

ES6-ban is jelölnünk kell, hogy a következő string bizony template, amibe változókat szeretnénk behelyettesíteni. Ehhez a szokásos " helyett ` karakterek közé kell azt tennünk, az alábbi módon:
var customer = { name: "Foo" }
var card = { amount: 7, product: "Bar", unitprice: 42 }
message = `Hello ${customer.name},
want to buy ${card.amount} ${card.product} for
a total of ${card.amount * card.unitprice} bucks?` // és bumm, így lett az XSS!

OO újítások


Gondolom akárkit kérdeznék, aki foglalkozik más komolyabb objektumorientált paradigmákat alkalmazó nyelvvel, az nem igen szívleli a prototype object modeljét a javascriptnek. Körülményes, a szemnek idegen szavak és kód. Na, ennek vége!

Nézzük csak az osztálydefiníciót:
// ES5 - From hell

var Shape = function (id, x, y) { // őő.. igen, ez egy konstruktor, a Shape meg egy osztály, fúj.

 this.id = id;
 this.move(x, y);
};
Shape.prototype.move = function (x, y) { // Ennek nem az osztálydefiníción belül kéne lennie? Meg miért kell a prototype, miért?

 this.x = x;
 this.y = y;
};

Akkor most vegyünk egy mély lélegzetet, számoljunk el tízig és nézzük meg a következőt:
class Shape { // osztálydefiníció?

 constructor (id, x, y) { // konstruktor

 this.id = id
 this.move(x, y)
 }
 move (x, y) { // instance method? 

 this.x = x
 this.y = y
 }
} // és mindez JS? Bizony, nem szellemeket látsz!

Öröklődés


Ha ez nem lett volna elég, hogy instant nekiess a specifikációnak, akkor jöjjön a következő lépés. Mi a helyzet, ha öröklődést akarsz megvalósítani?
// Kérem felkészülni, felkavaró ES5 öröklődés következik:

var Rectangle = function (id, x, y, width, height) { // still szép konstruktor

 Shape.call(this, id, x, y); // az a bizonyos "super"

 this.width = width;
 this.height = height;
};
Rectangle.prototype = Object.create(Shape.prototype); // átpasszoljuk a prototípust

Rectangle.prototype.constructor = Rectangle; // assignoljuk a konstruktort

var Circle = function (id, x, y, radius) { // megint egy "konstruktor"

 Shape.call(this, id, x, y); // super

 this.radius = radius;
};
Circle.prototype = Object.create(Shape.prototype); // és így tovább

Circle.prototype.constructor = Circle;

Akkor jöjjön mindez ES6-ban:
class Rectangle extends Shape { // extends? :O

 constructor (id, x, y, width, height) { 
 super(id, x, y) // super??

 this.width = width
 this.height = height
 }
}
class Circle extends Shape {
 constructor (id, x, y, radius) {
 super(id, x, y) // ez nem csak konstruktorra működik, bizony.. base class elérés a super kulcsszóval.. F-yeah!

 this.radius = radius
 }
}

Kérem tegye fel a kezét, aki szerint ez utóbbi sokkal inkább OO-style!

Static members


Akár hiszitek, akár nem, ezzel még mindig nincs vége. Lassan kukát fejelek, ahogy írom, mert inkább JS-eznék (na jó, ez hazugság, inkább valami erősen típusos nyelv, de psszt! ):
// ES5

var Rectangle = function (id, x, y, width, height) {
 // "konstruktor"

};
Rectangle.defaultRectangle = function () { // ez lenne a statikus metódus, jelen esetben egy factory method

 return new Rectangle("default", 0, 0, 100, 100);
};
var Circle = function (id, x, y, width, height) {
 …
};
Circle.defaultCircle = function () {
 return new Circle("default", 0, 0, 100);
};
var defRectangle = Rectangle.defaultRectangle();
var defCircle = Circle.defaultCircle();

Ezt mondjuk kitaláltuk volna, de nézzük már meg, hogy mi a változás, hé!
class Rectangle extends Shape { 
 …
 static defaultRectangle () { // na ne.. tényleg képesek voltak beletenni végre egy static kulcsszót?

 return new Rectangle("default", 0, 0, 100, 100)
 }
}
class Circle extends Shape {
 …
 static defaultCircle () { // bizonyám!

 return new Circle("default", 0, 0, 100)
 }
}
var defRectangle = Rectangle.defaultRectangle()
var defCircle = Circle.defaultCircle()

settheworld


Set the world on fire!


Újabb adatszerkezetek érkeznek a nyelvbe, hogy a gyakran használt struktúrák helyét átvegyék és mindeközben frissebb-lágyabb-jobb érzéssel töltsenek el minden Coccolino macit. Ezek egyike lett a set ojjektum.
// ES5 

var s = {}; // sima ojjektum

s["hello"] = true; s["goodbye"] = true; s["hello"] = true; // feltöltjük elemekkel, a hello lévén ismétlődik, felülcsapja az előzőt

Object.keys(s).length === 2; // 2 elem van benne

s["hello"] === true; // bizony, still true

for (var key in s) // arbitrary order

 if (s.hasOwnProperty(key)) // fincsi, mi?

 console.log(s[key]);

// ES6

let s = new Set() // hmm, Set ojjektum?

s.add("hello").add("goodbye").add("hello") // az add felülcsapja, ha már van ilyen kulcs

s.size === 2 // size property length helyett

s.has("hello") === true
for (let key of s.values()) // values-al szedjük ki a cuccot

 console.log(key)

mapsEz gondolom még senkit sem vág a falhoz, szóval akkor jöjjön a következő... Goole Maps! Javascriptben, eddig ha ún. HashMap vagy PHP-s körökben asszociatív tömb kellett, akkor a nyelv ezt egy sima object kulcsaiban tárolta, ez szép és jó, csak semmiféle plusz, Mapre jellemző funkcionalitással nem bírtak a plain objecten felül:
var m = {}; // sima object

m["hello"] = 42; // beleoktrojáljuk "kulcsként"

for (key in m) {
 if (m.hasOwnProperty(key)) { 
 var val = m[key]; 
 console.log(key + " = " + val); // majd a kulcs -> érték párokat kiszedjük belőle

 }
}
// ES6

let m = new Map() // Map ojjektum

m.set("hello", 42) // beletesszük a kulcsot

m.size === 1
for (let [ key, val ] of m.entries()) // az entries()-el tudjuk kinyerni a benne elhelyezett párokat

 console.log(key + " = " + val)

Fasza, ugye? Akkor jöjjön az amitől a Java fejlesztők elalélnak majd!

WeakSet / WeakMap


Ha valaki belefutott már egy autentikus memory leakbe, akkor annak nem kell mondanom mennyire kardinális kérdés ez. Amikor csak 1 lekérés erejéig él az alkalmazás, akkor még annyira nem kardinális a dolog, viszont ha hosszú időn át fut, akkor jön elő mennyire durva a helyzet. Frontendnél még annyira nem szoktak ilyenek előjönni, ahhoz nagyon nagy baklövés kell, de backenden egy kellően szarul megírt node tud finomságokat produkálni. Persze a rendszer nem úgy működik, mintha C-ben írnánk, azért tesz értünk és fut az a bizonyos GC, de ha referenciák beragadnak, akkor bizony az óhatatlanul ottmarad és csámcsog a heap tetején. Hogy megkönnyítsék az életünket, itt is megjelentek az ún. weak reference-ek, illetve azoknak két konkrét "megvalósítása". Ez esetünkben nem a konkrét Mapra és Setre, hanem a benne tárolt kulcsokra vonatkozik, tehát ha valahol az adott kulcson csücsülő ojjektum eredeti referenciáját kitakarítjuk, akkor nem marad benne ezekben az adatszerkezetekben. Lévén ilyet nem lehetett ES5-ben csinálni, ezért csak az ES6 példa jöjjön:
let isMarked = new WeakSet() // az a bizonyos weak referenciákkal vértezett set

let attachedData = new WeakMap() // és map

export class Node { // csinálunk egy ojjektumot

 constructor (id) { this.id = id }
 mark () { isMarked.add(this) } // betesszük a set-be

 unmark () { isMarked.delete(this) } // kiszedjük a set-ből

 marked () { return isMarked.has(this) } // megnézzük, hogy a set-ben van-e az adott elem

 set data (data) { attachedData.set(this, data) } // betesszük a map-be

 get data () { return attachedData.get(this) } // és kikapjuk onnan

}
let foo = new Node("foo") // példányosítjuk az objektumot

JSON.stringify(foo) === '{"id":"foo"}'
foo.mark() // betesszük a set-be

foo.data = "bar" // a setteren keresztül betesszük a map-be

foo.data === "bar"
JSON.stringify(foo) === '{"id":"foo"}'
isMarked.has(foo) === true // megnézzük, hogy bent van-e a set-ben

attachedData.has(foo) === true // megnézzük, hogy az objektum bent van-e a mapben

foo = null /* kitakarítjuk a referenciát */
attachedData.has(foo) === false // hopp.. már nincs bent 

isMarked.has(foo) === false // a set-be se

Nos, remélem ez a kis adag kedvet hozott arra, hogy Ti magatok is beleássátok magatokat a specifikáció részleteibe vagy elkezdjétek próbálgatni. Persze még nem érdemes csak úgy ES6 kódot hányni, legalábbis kliensoldalon, lévén még a kompatibilitás hagy némi kívánnivalót maga után, viszont akadnak fordítók, amik ES5-öt varázsolnak belőle. Ilyen pl. a babel, amiről majd szintén írok az ünnepek alatt!

Apropó ünnepek...

Minden kedves olvasómnak Boldog Karácsonyt és kellemes húsvéti ünnepeket kívánok!

pope-christmas-gays

Hozzászólások betöltése
2014-2018 © Letscode.hu. Minden jog fenntartva. Build verzió: 1.2.11