Tacsiazuma
Tacsiazuma A letscode.hu alapitója, több, mint egy évtized fejlesztői tapasztalattal. Neovim függő hobbi pilóta.

Javascript pakk No. 1 – ECMAScript 6

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.

1
2
// ECMAScript 6
const PI = 3.141593
1
2
3
4
5
6
7
8
// 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:

1
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
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):

1
2
3
4
5
6
7
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:

1
2
3
4
// 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
1
2
3
4
// 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!

1
2
3
4
5
6
7
8
9
10
11
12
13
// (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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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:

1
2
3
4
5
6
7
8
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:

1
2
3
4
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:

1
2
3
4
5
6
// 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:

1
2
3
4
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?

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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:

1
2
3
4
5
6
// 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:

1
2
3
4
5
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:

1
2
3
4
5
6
7
8
9
// 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:

1
2
3
4
5
6
7
8
9
10
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?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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! ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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é!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

comments powered by Disqus