[[ maas / 2021-05-19 ]]
519
JS alapok

Az alábbiakban rengeteg jegyzetet felhalmoztam a témában, amiket szándékomban áll jelentősen rendszerezni a közeljövőben.

Bevezetés

A JavaScript 1995-ben, az akkor a Netscape-nél mérnökként dolgozó Brendan Eich alkotásaként indult hosszú, hódító útjára. Ennek megfelelően elsőként a korszak meghatározó jelentőségű webböngészőjében, a Netscape Navigatorban jelent meg a nyelv támogatása, méghozzá rögtön 1996 elején, a program 2.0-s verziójában. Eredetileg Mocha-nak, kicsit később LiveScript-nek nevezték volna, de mivel akkoriban a Netscape épp szoros üzleti kapcsolatban állt a Java-t birtokló Sunnal, egy meglehetősen elhibázott marketing-döntés nyomán úgy vélték, hogy az akkor már egészen sikeres Java népszerűségét meglovagolják, és végül a nyelv a JavaScript nevet kapta. Neveik alapján arra számíthatnánk, hogy a két nyelvben számos párhuzamra kellene akadnunk, ám valójában azok csak kis mértékben hasonlítanak. Valóban voltak a fejlesztés első, sürgetett kezdeti szakaszában a Netscape-nél olyan felsőbb szintű igények, hogy a nyelv hasonlítson a Java-hoz, de ez ezen a szinten meg is állt. A programozási nyelv neve még napjainkban is rengeteg félreértést szül, így aztán a kissé fura névválasztás tekintetében a rövid és tömör összefoglaló:

A két programozási nyelvnek semmi köze sincs egymáshoz.

Az erről szóló legelterjedtebb mondás a "Java is to JavaScript what Car is to Carpet.", amelynek magyar megfelelője akár így is szólhatna: "A Java-nak annyi köze van a JavaScript-hez, mint a rókának a parókához" (eredetileg autónak a szőnyeghez).

Megjelenését követően néhány hónappal később a Microsoft is beépítette saját JavaScript megvalósítását, a JScript-et az Internet Explorer 3-ba, majd újabb hónapok elteltével a Netscape a JavaScript szabványosítását elősegítendő, elküldte azt az ECMA International-nak (European Computer Manufacturers Association) és még 1997 júniusában megjelent az ECMAScript szabvány első kiadása. A szabványon tovább dolgoztak, és az első kiadást pontosan egy évvel követte a második, az ISO/IEC 16262 nemzetközi szabvánnyal való összhang megerősítése érdekében.

A szabvány 1999 decemberében, a 3. kiadásában érte el első igazán jelentős mérföldkövét, és attól kezdve tekinthetjük a nyelvet igazán stabilnak, megbízhatónak. A 4. verzió soha sem érte meg a végleges kiadását számos eltúlzott újítása, összetettsége miatt, így 2008-ban végleg elhagyatottnak (abandoned) nyilvánították. Az 5. verzió 2009 decemberében jelent meg olyan fontos újításokkal, mint a foreach, a map és a filter. 2015 júniusában jelent meg az azóta is leginkább meghatározó ECMAScript 6. Újítása azóta is meghatározóak és széles körben alkalmazottak. A legismertebbek ezek közül a let, a const, a for...of ciklus, a Python-stílusú generátorok, a függvények innentől kezdve alapértelmezett paramétereket is kaphatnak és egy egészen új módon, az arrow function expression módszerrel (() => {...}) is definiálhatóak. A 2016-os 7. kiadás nem hozott jelentős újdonságokat, ám a 2017-ben megjelent ECMAScript 8-ban a nyelv újabb fejlesztéseket kapott elsősorban a párhuzamosan futtatható programszálak területén az Async függvények és a Shared memory and atomics bevezetésével.

A fentiek alapján kijelenthető-e akár az is, hogy a továbbiakban ECMAScript programozásról fogunk olvasni, abban fogunk programozni? A kérdésnek van alapja, hiszen eredetileg az ECMAScript főleg a JavaScript-ből vált szabvánnyá, majd onnantól kezdve a JavaScript az ECMAScript szabványa szerint fejlődik, mégis az egyik csak egy szabvány, míg a másik a szabványból származó programozási nyelvi megvalósítás. Ugyanez az ECMAScript a közös szabványa a JScript, ActionScript, TypeScript stb. nyelveknek is, azonban a JavaScript az ECMAScript kétségkívül legnépszerűbb implementációja. A helyes megfogalmazás tehát valahogy így hangzik: a továbbiakban az ECMAScript 8 alapú JavaScript programozással fogunk foglalkozni.

Néhány hasznos olvasmány a JavaScript történetét illetően:

Mint látható, a JavaScript folyamatosan fejlődik, de még nagyobb változás (és őszintén szólva valódi káosz) figyelhető meg a hozzá kapcsolódó kiegészítők, modulok tekintetében. Ami ma még a legjobb módszer egy webes technika megvalósítására, könnyen előfordulhat, hogy 2-3 év múlva már annak megemlítése is ciki az igazán keményvonalas fejlesztők körében. Íme egy kis olvasnivaló ebben a témában is:

A fejlesztési környezet kialakítása, alapfogalmak

A tanulás során, illetve féldinamikus weboldalak készítésekor a legjobb fejlesztési környezetet maga a webböngésző nyújtja. Ennek igénybevételéhez használhatjuk a webböngésző Developer Tools (Firefox-ban: [F12]) → Console funkcióját (ugyanennek elérése Firefox-ban rögtön a [Ctrl]+[Shift]+[K] billetyűkombinációval is lehetséges).

A fejlesztéshez a VSCode a leginkább ajánlott szerkesztő, amelynek rövid bemutatása itt található.

2.1 Alapfogalmak

Mindenek előtt meg kell ismerkednünk annak a módjával, ahogy a tanulás során a programjainkat működésre bírjuk. Ez lehetőleg kényelmes, gyors és áttekinthető kell legyen, minimális kattintgatással.

2.1.1 Variációk a "Hello világ!" programra

Az elmaradhatatlan fejezet következik rögtön négy megoldással, amelyek közül egyelőre maradjunk a legutolsónál, de mindenképpen próbáljuk ki mindegyiket legalább egyszer.

A lenti programok alapja a program futásakor mindenképpen létrejövő console objeketum log() metódusának használata, amely az objektumorientált programozási paradigmának megfelelően a legelterjedtebb módon, egy pont karakterrel illeszthető egybe:

console.log()

2.1.1.1 A webböngésző konzoljával, HTML kódba illesztve

Készítsünk egy index.html nevű állományt az alábbi tartalommal:

<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <script>
      console.log('Hello világ!')
    </script>
  </body>
</html>

Ha kész, nyissuk meg egy webböngészőben, és az előző fejezetben már említett módon tekintsük meg az oldal által generált konzolkimenetet.

2.1.1.2 A webböngésző konzoljával, külön .js fájlban elhelyezve

Az előzőhöz nagyon hasonló index.html állomány csak hivatkozást tartalmazzon a beillesztett JavaScript fájlra:

<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <script src="test.js"></script>
  </body>
</html>

A hivatkozott test.js állomány tartalma (ugyanabban a könyvtárban, ahol az index.html is található):

console.log("Hello világ!");

2.2 Programírási szabályok

  1. A JavaScript kis-/nagybetű-érzékeny ("case sensitive"), tehát a futtat, a Futtat és a FUTTAT három különböző dolgot jelenthet. Erre különösen figyeljünk, éppen ezért

  2. Használjunk a camelCase névadási módszert:

    • a változók neve kezdődjön kisbetűvel (pl. greenDuck)
    • az objektumok és osztályok nevei nagybetűvel induljanak (pl. Duck())
    • a konstansok neve lehetőleg csupa nagybetűs legyen (pl. DUCKCOLOR)
  3. A szóközök, behúzások, sorközök rengeteget számítanak! A JavaScript kód sorközök és további szóközök nélkül is működőképes, de az olvashatóság miatt rendkívül fontos a szabályos tagoltság.

  4. Az összetartozó kódsorok végén pontosvesszőt kell tennünk. Általában szükségtelenek a kód futtathatóságának szempontjából, de ismét csak az áttekinthetőség miatt erősen ajánlottak.

  5. Ahol csak szükségét érezzük, alkalmazzunk kommenteket:

// egy sornyi komment
 
/_ több sorból álló,
hosszabb megjegyzés _/

Deklaráció, adattípusok, struktúrák és kifejezések

A JavaScript-ben a konstansok és változók típusa dinamikus, azaz létrejöttükkor kerül megállapításra a típusuk.

3.1 Konstansok

Nevükből is kikövetkeztethető: a konstansok értéke a program futása során nem változhat. Definiálása a const utasítás után történik const KONSTANSNÉV = tartalom; szintaktikával. Két ugyanolyan nevű konstanst vagy változót nem adhatunk meg, hiszen akkor nem tudnánk a megfelelőre hivatkozni. Miután definiáltunk egy konstanst, a fordító a továbbiakban ezzel a konstansnévvel találkozva már a fordítás alatt annak értékét írja a helyére.

const HELLO = 'Hello világ!'
 
console.log(HELLO)

Ennek az eredménye a böngésző Web Console ablakában:

Hello világ!

Akár a konstansok, akár a változók esetében a .log mellett a .dir metódus is használható, amely igazán látványos kimenetet majd az objektumok esetében fog megjeleníteni. Példaprogramunkat egy további hasznos függvénnyel, a typeof-al is kiegészíthetjük, amely a konstans vagy változó típusával tér vissza.

const HELLO = "Hello világ!";
 
console.log(HELLO);
console.dir(HELLO);
console.log(typeof(HELLO));
Hello világ!
Hello világ!
string

3.2 Változók

A var kifejezéssel egy változót deklarálhatunk, illetve opcionálisan rögtön hozzárendelhetjük azt egy értékhez.

A let kifejezéssel egy block scope-on belüli, helyi változót deklarálhatunk, amely szintén akár már létrehozáskor megkaphat egy értéket is.

E kettő közül az utóbbi években egyértelműen a let vált a gyakoribbá, mivel logikusabb.

Az alábbi két program közül az első esetben még az x változó deklarálása előtti próbálkozás is ad valamiféle eredményt ("undefined"), míg a másodiknál erre esélyt sem kapunk a fordítótól. Ezt nevezzük "Hoisting"-nek.

  • Hoisting var-al:
console.log(x);
var x = "Hello";
console.log(x);
undefined
Hello
  • Hoisting-el való próbálkozás let-el:
console.log(x);
let x = "Hello";
console.log(x);
ReferenceError: can't access lexical declaration `x' before initialization

3.3 Tömbök

3.4 Kifejezések

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/ExpressionsandOperators

A kifejezések kétféle összetevőből állnak: operátorokból és azok operandusaiból. Az operátorok tulajdonképpen a műveletek, az operandusok, pedig a számok, értékek, melyekkel a műveleteket végezzük.

Nézzünk egy tipikus példát, ahol az egyik értéket összehasonlítjuk a másik értékkel:

a > b

Ebben a kifejezésben azt vizsgáljuk, hogy az a nagyobb-e b operandusnál. A nagyobb jelet ábrázoló kacsacsőr egy relációs típusú operátor. A bináris operátorok közé is csoportosíthatjuk, ami azt jelenti, hogy két operandus tartozik hozzá. A legtöbb operátor bináris.

3.4.1 Értékadó operátorok

Erre való a = karakter. Pl.:

x = 5
console.log(x)

3.4.2 Aritmetikai operátorok

Alapvető számolási műveleteket végezhetünk velük egész vagy valós típusú számokkal. Ezek a következők lehetnek:

  • + összeadás
  • - kivonás
  • * szorzás
  • / osztás
  • % osztás maradékképzése
  • ++ növelés
  • -- csökkentés
  • ** hatványozás

Az aritmetikai operátorokhoz tartozik még továbbá két "unáris" operátor a + és a - előjel.

3.4.3 Logikai operátorok

Logikai (boolean) műveleteket végeznek el logikai típusú operandusok között logikai típusú eredményt hozva. Egyszerű logikai kifejezések, valamint több más típusú kifejezés összekapcsolásával összetett logikai kifejezések alakíthatók ki velük.

Az alábbiak tartoznak ebbe a csoportba:

  • && logikai és
  • || logikai vagy
  • ! logikai tagadás

3.4.4 Relációs operátorok

Egész és valós típusú operandusok között végezhetünk velük vizsgálatokat:

  • == egyenlő
  • != nem egyenlő
  • === szigorúan egyenlő (tartalomra és típusra egyaránt)
  • !== szigorúan nem egyenlő (tartalomra és típusra egyaránt)
  • < kisebb, mint…
  • > nagyobb, mint…
  • <= kisebb-­egyenlő, mint…
  • >= nagyobb-­egyenlő, mint…
  • in egyik eleme­e a másiknak

Figyelem: a => nem operátor, hanem az újabb típusú függvényjelölés (Arrow function notation).

3.4.5 Szövegösszefűző operátor

Egymástól elkülönített szövegeket, vagy egy változó­ ill. konstansnév utáni szöveget a + operátorral tudunk kiíratáskor összefüggővé tenni az alábbi módokon:

console.log('Addig jár a korsó a kútra,' + ' amíg be nem vezetik a vizet.')
console.log(konyvtarnev + '/')

Sőt, akár még így is használható:

var x = 'kocka';
x += 'has'; // x értéke így: 'kockahas'

További operátorok és részletes ismertető: developer.mozilla.org/…/Expressions_and_Operators

(+1: A Math.floor() függvény egy decimális értéket generál.)

Vezérlési szerkezetek

4.1 Elágazások

4.1.1 Egy és kétirányú elágazás

if

if...else

4.1.2 Többirányú elágazás

switch

4.2 Ciklusok

4.2.1 A while

A JavaScript és sok más programozási nyelv esetében ez a tipikus elöltesztelős ciklus. A programblokk futásának előfeltétele, hogy kifejezés igaz értéket vegyen fel. Amennyiben ez már induláskor sem teljesül, a ciklus egyszer sem fut le. Amennyiben teljesül, a ciklusmag újra és újra futtatásra kerül egészen addig, amíg még igaz a bemeneti vizsgálat eredménye:

let n = 0
 
while (n < 3) {
  console.log(n)
  n++
}
 
console.log('A végén: ' + n)
0
1
2
A végén: 3

A következő programblokkban arra láthatunk példát, hogy a ciklus egyszer sem fut le, hiszen már az előzetes vizsgálat során is hamis eredményt kapunk (n kisebb 2-nél):

let n = 2
 
while (n < 2) {
  n++
}
 
console.log(n)
2

4.2.2 A do...while

A JavaScript a do...while módszerrel valósítja meg a hátultesztelős ciklust. Mint látható, a programblokk legalább egyszer lefuttatásra kerül, majd csak ezután vizsgálja meg az összefüggést. Más szavakkal: addig futtatja a programblokkot, amíg az összefüggés hamis értéket nem kap:

let n = 2
 
do {
  n++
} while (n < 2)
 
console.log(n)
3

Jól megfigyelhető, ahogy a korábbi while ciklussal teljesen megegyező do...while-os átirata egyszer megnövelte az n változó értékét.

4.2.3 A for

Minden programozási nyelvben a for-nak valamilyen változatával alkothatunk meg növekményes ciklusokat. Ennél a típusnál elsősorban nem összefüggések kiértékelésétől függ a ciklusmag futása, hanem nagyon leegyszerűsítve, tulajdonképpen saját magunkat kíméljük meg felesleges kódsorok bepötyögésétől.

A for alapmodelljében három paraméter, a bemeneti átmeneti változó (amelyet az "index" mivolta miatt gyakran i-nek nevezünk), az előtesztelés és az átmeneti változó növelése segíti a növekményes ciklust:

let n = 0
 
for (let i = 0; i < 3; i++) {
  n++
  console.log(n)
}
1
2
3

4.2.4 A for...in

A for...in használata az alapmodellnél már sokkal életszerűbb, ugyanis előszeretettel használjuk egy szöveges beérkező adatfolyam "körbejárásához". Íme egy példa erre:

const GYUMIK = { alma: 'Jonatán', korte: 'Vilmos', barack: 'Kajszi' }
 
for (let i in GYUMIK) {
  console.log(GYUMIK[i])
}
Jonatán
Vilmos
Kajszi

4.2.5 A for...of

A for...of az előbbi továbbgondolt változata, amely a későbbiek során megismert JSON adathalmazok körbejárását segíti elő. Olyan adattípusokat támogat csak, amik "iterálhatóak". A tömbbe illesztett objektumok ilyenek:

const GYUMIK = [
  {
    nev: 'alma',
    fajta: 'Jonatán',
  },
  {
    nev: 'körte',
    fajta: 'Vilmos',
  },
  {
    nev: 'barack',
    fajta: 'Kajszi',
  },
]
 
for (let i of GYUMIK) {
  console.log(i.fajta)
}
Jonatán
Vilmos
Kajszi

4.2.6 A for await...of

Async és sync iterálható objektumok körbejárásához alkalmazható növekményes ciklusokat írhatunk a segítségével.

A szintaxisa:

for await (variable of iterable) {
  statement
}

Használatára majd csak a későbbiekben, a Promise-ok és az async/await függvények alkalmazásakor térünk ki.

4.3 Megszakítások

4.3.1 A break

4.3.2 A continue

4.3.3 A try...catch

További vezérlési szerkezetek és részletes ismertető: developer.mozilla.org/…/Statements

Függvények

Az összetartozó, különösképpen a többször is felhasználni kívánt kódsorokat egy külön kódblokkba érdemes helyeznünk.

A függvények elnevezésének szabályai azonosak a változónevek szabályaival:

  • akár a $, _, a..z, 0..9 karakterek bármelyikét, sőt, még különféle speciális karaktereket is tartalmazhat
  • számmal nem kezdődhet
  • nem lehetnek már létező parancsszavak (pl. for, while stb.)

Függvénynevek megalkotására a https://mothereff.in/js-variables weboldal is segítségünkre lehet.

A függvények meghívásának a JavaScript-ben több módja is lehetséges:

  • Hagyományos (Named) függvénydeklaráció
  • Névtelen (Anonymous) függvénydeklaráció
  • Azonnal meghívott függvénykifejezés (Immediately invoked function expression)
  • Magasabb rendű (Callback) függvénydeklaráció

A this attribútum az objektum adott instance-ának értékét kapja meg (arra mutat).

5.1 Hagyományos függvénydeklaráció

function plus(a, b) {
  return a + b
}
 
console.log(plus(2, 2))

5.2 Anonymous függvénydeklaráció

var plus = function (a, b) {
  return a + b
}
 
console.log(plus(2, 2))

5.3 Azonnal meghívott függvénykifejezés

var plus = (function (a, b) {
  return console.log(a + b)
})(2, 2)

5.4 A callback függvény

function plus(a, b, callback) {
  console.log(`Adding ${a} to ${b} equals with ${a + b}.`)
  callback()
}
 
plus(2, 2, function () {
  console.log('Calculation finished.')
})

5.5 A "call-and-apply" függvénymeghívás

var speak = function (what) {
  console.log(this.love)
  console.log(what)
}
 
var saySomething = { normal: 'meow', love: 'purr' }
 
speak.call(saySomething)
speak.apply(saySomething, ['meouff'])

A "return" function statement nagyjából opcionális. Visszatérési értéke egy kifejezés. Ha nincs ott, hogy mi lesz a visszatérési érték, akkor is lesz legfeljebb egy "undefined".

function myFunc() {
  console.log('Hello')
}
 
myFunc()
function myFunc(myVar) {
  console.log('Hello, a myVar értéke: ' + myVar)
}
 
myFunc(2)

Anonymous függvénynek tekintjük ezt azért, mert a function kulcsó után nem határozunk meg nevet.

var myFunc = function () {
  console.log('Hello')
}
 
myFunc()

Függvénydeklaráció függvény-kifejezéssé alakítására is gyors megoldást kapunk. Magától meghívhatóvá tehetjük ezzel a függvényt:

var myFunc = (function () {
  console.log('Hello, a myVar értéke: ' + myVar)
})()

Ez így nem hívódik meg:

function myFunc(myVar) {
  console.log('Hello, a myVar értéke: ' + myVar)
}
2

Így viszont már igen, ezt hívjuk "closure"-nek:

;(function () {
  console.log('Hello')
})()

Ugyanez argumentum megadásával:

var myFunc = (function (myVar) {
  console.log('Hello, a myVar értéke: ' + myVar)
})(2)

A "scope" (hatókör) egy változó életét és halálát jelenti.

Itt a "dogName" változó a meghívásakor a "Morzsi" értéket kapja:

function myDog() {
  let dogName = 'Buksi'
  function otherDog() {
    let dogName = 'Morzsi'
    console.log(dogName + ' mondja: Vaú!')
  }
  otherDog()
}
 
myDog()

A globális változó definiálása erősen kerülendő dolog:

function myDog() {
  dogName = 'Buksi'
}
 
myDog()
console.log(dogName + ' says woof')

A hoisting az az, amikor előbb van a változóra hivatkozás, mint a változó definiálása, de a JS mégis tudja, hogy van valami, így "undefined" értéket kap:

function myDog() {
  console.log(dogName + ' mondja: Vaú!')
  var dogName = 'Buksi'
}
 
myDog()

A modulok készítése esetén érdemes a namespacing-el is törődni: "Namespacing allows you to protect any variables that you have in your modules from any global scoped variables."

A "return" statement nagyon hasznos akár objektumok visszatérési értékként való elküldésére.

Itt, ebben a nagyon egyszerű modulban gondosan elszeparáltuk (megvédtük) a változót a külső scope-tól:

var myFunc = (function () {
  return {
    speak: function () {
      console.log('Hello')
    },
  }
})()

…másik <script> tag-en belül meghívva:

myFunc.speak()

Argumentum küldése egy függvénybe (itt csak az a veszély, ha nincs argumentum):

var myFunc = (function () {
  return {
    speak: function () {
      console.log(arguments[0].say)
    },
  }
})()
 
myFunc.speak({ say: 'Szervusz!' })

Az előző továbbfejlesztése, hogy legyen egy default érték:

var myFunc = (function () {
  return {
    speak: function () {
      var myArgs = arguments[0] || ''
      var statement = myArgs.say || 'Hello!'
      console.log(statement)
    },
  }
})()
 
myFunc.speak()
myFunc.speak({ say: 'Szervusz!' })

Az előzőnél elegánsabb megoldás, ha a függvény elején a DEFAULTS értékeket definiáljuk:

var myFunc = (function () {
  const DEFAULTS = {
    say: 'Hello!',
  }
  return {
    speak: function () {
      var myArgs = arguments[0] || ''
      var statement = myArgs.say || DEFAULTS.say
      console.log(statement)
    },
  }
})()
 
myFunc.speak()
myFunc.speak({ say: 'Szervusz!' })

A "Chaining" azt használja ki, amikor a "return this;" sor az objektum adott instance-ával tér vissza.

var ray = (function () {
  var DEFAULTS = {
    say: 'hello',
    speed: 'normal',
  }
 
  return {
    speak: function () {
      var myArguments = arguments[0] || ''
      var statement = myArguments.say || DEFAULTS.say
      console.log(statement)
      return this
    },
    run: function () {
      var myArguments = arguments[0] || ''
      var running = myArguments.speed || DEFAULTS.speed
      console.log('running...' + running)
      return this
    },
  }
})()

A HTML kódban az előzőt így hívjuk meg:

<script src="script.js"></script>
<script>
  ray.speak({ say: 'howdy' }).run().speak({ say: 'run faster' }).run({ speed: 'faster' })
</script>

Osztályok

6.1 A függvények és osztályok közötti különbségek

A függvények hoist-elhetőek, az osztályok nem, tehát mindenképpen előbb kell deklarálni az osztályt, mielőtt használnánk. A függvényeket felül lehet írni, az osztályokat nem. Az osztályoknak is lehet metódusokat adni, melyeket ugyanúgy újra fel lehet használni.

6.2 Strict mode

ES5-ben lett bemutatva. Olyan error-okat is kaphatunk a használatával, amikhez régen nem férhettünk hozzá. A még nem széles körben elfogadott szintaxisok használatát szándékosan tiltja. A MDN-en többet is meg lehet tudni róla.

6.3 Statikus metódusok

Static methods are methods that aren't accessible through an instance of a class, but only available through the class itself. They are usually created for utility functions that don't relate to the instance of the class. I rarely need to use static methods, but when I do, it's always good to be aware they exist.

class Car {
  constructor(doors, engine, color) {
    this.doors = doors
    this.engine = engine
    this.color = color
  }
  carStats() {
    return `This car has ${this.doors} doors, a ${this.engine} engine and a beatiful ${this.color} color!`
  }
 
  static totalDoors(car1, car2) {
    const doors1 = car1.doors
    const doors2 = car2.doors
    return doors1 + doors2
  }
}
 
const CX5 = new Car(4, 'V6', 'red')
const CIVIC = new Car(3, 'V4', 'blue')
 
console.log(CX5)
console.log(CX5.carStats())
console.log(CIVIC)
console.log(CIVIC.carStats())
 
console.log('Total doors: ' + Car.totalDoors(CX5, CIVIC))

Az eredmény a böngésző konzoljában:

Object { doors: 4, engine: "V6", color: "red" }
app.js:22:9
This car has 4 doors, a V6 engine and a beatiful red color! app.js:23:9
Object { doors: 3, engine: "V4", color: "blue" }
app.js:24:9
This car has 3 doors, a V4 engine and a beatiful blue color! app.js:25:9
Total doors: 7

6.4 Prototípus metódusok

A konzolban található <prototype>: Object { … } fát kibontva láthatjuk az objektum konstruktorát, a constructor: function Car()-t. Itt megláthatjuk mindazt a konstruktort, amit használhatunk a Car osztályú objektumhoz, pl. a Car.doors-t. Ugyanitt a prototype részben az alkalmazható metódusokat is listázva láthatjuk:

console.log(CX5.doors.toString())

6.5 Konstruktorok

Constructors are part of the syntax of a class. If you don't include one, one will be generated for you. Also you can only do one constructor per class, otherwise there will be a syntax error thrown at you. A konstruktor tehát automatikusan készít egy objektumot számunkra. A super kulcsszó lehetővé teszi a szülő osztály metódumainak meghívását.

class Car {
  constructor(doors, engine, color) {
    this.doors = doors
    this.engine = engine
    this.color = color
  }
  carStats() {
    return `This car has ${this.doors} doors, a ${this.engine} engine and a beatiful ${this.color} color!`
  }
 
  static totalDoors(car1, car2) {
    const doors1 = car1.doors
    const doors2 = car2.doors
    return doors1 + doors2
  }
}
 
class SUV extends Car {
  constructor(doors, engine, color, brand, carStats) {
    super(doors, engine, color, carStats)
    this.brand = brand
    this.wheels = 4
    this.ac = true
  }
 
  myBrand() {
    return console.log(`This SUV is a ${this.brand}`)
  }
}
 
const CIVIC = new Car(3, 'V4', 'red')
const CX5 = new SUV(4, 'V6', 'black', 'Mazda')
 
console.log(CX5)
console.log(CX5.carStats())
console.log(CX5.myBrand())

Az extends kulcsszó tehát kiterjeszti a már meglévő osztályt, amiből öröklődik.

Amennyiben egynél több osztályt szeretnénk kiterjeszteni egy újabb osztályhoz, a mixin a megoldás. Ilyenkor nem öröklődés történik, hanem összeállítás. Csak nagy óvatosággal használjuk!

let mixin = {
madeIn() {
console.log('This car was made in year 2019');
}
};
 
let carMixin = {
**proto**: mixin,
madeIn() {
super.madeIn();
}
};
 
class Car {
constructor(doors, engine, color) {
this.doors = doors;
this.engine = engine;
this.color = color;
}
carStats() {
return `This car has ${this.doors} doors, a ${this.engine} engine and a beatiful ${this.color} color!`;
}
 
static totalDoors(car1, car2) {
const doors1 = car1.doors;
const doors2 = car2.doors;
return doors1 + doors2;
}
}
 
class SUV extends Car {
constructor(doors, engine, color, brand, carStats) {
super(doors, engine, color, carStats);
this.brand = brand;
this.wheels = 4;
this.ac = true;
Object.assign(this, carMixin);
}
 
myBrand() {
return console.log(`This SUV is a ${this.brand}`);
}
}
 
const CIVIC = new Car(3,'V4','red');
const CX5 = new SUV(4,'V6','black','Mazda');
 
console.log(CX5);
console.log(CX5.carStats());
console.log(CX5.myBrand());
 
console.log(CX5.madeIn())

Aszinkron programozási alapok

Amikor egymás után állunk sorban mozijegyet venni, az egy szinkron folyamat. Valakinek tovább tart, valakinek rövidebb időt vesz igénybe, de szépen sorban egymás után kivárjuk. Miközben várunk, a mi folyamatunk szempontjából blokkolás zajlik. Az időigényesebb kiszolgálási idővel járó feladatok teljesítése méghosszabb blokkolási idővel jár, így ekkor már valóban szükség van aszinkron kiszolgálásra. Egy másik, mindennapi életből merített példával élve így történik egy étteremben, több pincérrel.

Amikor csak lehet, tehát amikor az egyik folyamat végrehajtása nem függ egy másik, hosszabb időt igénybe vevő folyamat eredményétől, aszinkron programfolyamot érdemes használnunk.

Három eszköz is rendelkezésünkre áll aszinkron programrészek futtatására, amennyiben annak eredménye szükséges más programrészek futásához:

  • callback függvény
  • promise
  • async/await alkalmazása

7.1 Callback

Időnként szükségünk lehet arra, hogy további függvényeket vagy metódusokat futtassunk, miután egy aszinkron függvény végrehajtódott. Hagyományosan ezt "callback" igénybevételével tudjuk elérni, ami lehetővé teszi, hogy egy aszinkron függvény meghívása egyszerre (párhuzamosan) úgy mehessen végbe, hogy a főprogram folyamata sem áll le, addig sem blokkolódik. Ez az aszinkron függvény argumentumként kell, hogy használjon egy másik függvényt. Miután az eredeti függvény összefüggései végrehajtódtak, a függvény maga adódik át egy meghívott argumentumként. Az így meghívott függvényt hívjuk callback függvénynek. A callback lehetővé teszi számunkra, hogy mi történjen egy aszinkron módon futó függvény lefutását követően.

A setTimeout függvény például két argumentummal kell rendelkezzen: egy callback függvénnyel és egy időintervallummal:

setTimeout(callback, delay)
Egyszerű megvalósítása:
 
setTimeout(function() {
console.log('Szia!');
}, 3000);

Az alábbi kódban tehát a szinkron kód eredménye előbb kiíródik, hiába található meg a kódban később:

console.log('Szia!')
 
setTimeout(function () {
  console.log('Aszinkron eredmény')
}, 3000)
 
console.log('Szinkron eredmény')

Az aszinkron függvény végzi tehát a várhatóan időbe telő kérést és egy callback-et az eredményül kapott adattal…

A success is egy ilyen callback. Ha pedig arra is kell számítanunk, hogy az időbe telő függvény hibára fut, akkor "fail callback"-et is be kell építenünk.

Amikor többszörösen láncolt callback-eket alkalmazunk, könnyen köthetünk ki egy "callback hell" nevű kódsornál, ami nehezen áttekinthető. Erre nyújt megoldást a promise.

7.2 Promise

A promise (ahogy az elnevezése is sugallja: ígéret) információt ad egy műveletről és nyomon követi annak állapotát.

Rendelkezik egy state property-vel, ami lehet pending, fulfilled vagy rejected, valamint egy result property-vel, ami induláskor még undefined értéket kap, de siker esetén végül megkapja a művelet eredményét. Ez alapján tehát a pending állapotban (state) a result értéke undefined, a fulfilled állapotban az érték a feldolgozás eredményeként született érték, a rejected állapotban pedig egy hibaleírás (error).

Először egy új promise-t hozunk létre a Promise konstruktorral…

let promise = new Promise(function (resolve, reject) {
  // do something
  if (1 > 2) {
    resolve(result)
  } else {
    reject(error)
  }
})
 
promise
  .then(function (result) {
    // do something with result
    return newResult
  })
  .then(function (newResult) {
    // do something with new result
    return funalResult
  })
  .catch(function (error) {
    // deal with error
  })
  .finally(function () {
    // do something that runs anyway at the end
  })

Mivel a JS kódjában a szóközök nem számítanak, új sorokban felsorolhatjuk egymás után a .then (és egyéb) blokkokat.

A .catch metódussal az error-t tudjuk lekezelni, amit az eredeti promise generált hiba esetén.

A .finally metódussal azokat az utasításokat hajthatjuk végre, amik függetlenek attól, hogy a promise eredménye sikeres vagy elutasított lett.

A Promise.all metódussal minden promise-t összevonhatunk egy tömbbe.

7.3 Async/Await

Bár már a promise-ek is nagy előrelépést jelentenek a callback-ekhez képest, még mindig nehezen követhetőek. Emiatt az async/await a legolvashatóbb módszer aszinkron függvényekhez. Az async/await kód a háttérben promise-ek használatával füt le egyébként, tehát ez csak minket, programozókat segít. A kód a Babel igénybevételével akár ES6 előtti kódsorrá is képes alakítani kodunkat, vagy annak egy részét.

async function readyData() {
  let data = await getData()
  let formattedData = await formatData(data)
  return formattedData
}

Kicsit valósabb példa try és catch blokkokkal kiegészítve:

async function readyData() {
  try {
    let data = await getdata()
    let formattedData = await formattedData(data)
    return formattedData
  } catch (error) {
    // handle error
  }
}

Példaprogram egy Promise-t, async-et és await-et felhasznáva:

var a = 2,
  b = 3,
  c
 
function getData() {
  return new Promise(function (resolve) {
    setTimeout(function () {
      c = a + b
      resolve(c)
    }, 1000)
  })
}
 
async function msg() {
  console.log(`Számolás indul... (c értéke ekkor: ${c})`)
  const msg = await getData()
  console.log('Az eredmény:', msg)
}
 
msg()

Itt is ugyanúgy használható a végén mindenképpen végrehajtódó finally blokk is.

Csak async és await felhasználásával és egy időigényesebb számítást beépítve szemléletesebb példaprogramot hozhatunk létre:

let calc = () => {
  var i, j, s
  for (i = 1; i < 10000; i++) {
    s = i * i * i
    for (j = 1; j < 10000; j++) {
      s = j / j / j
    }
  }
  return s
}
 
msg = async () => {
  console.log(`Számolás indul...`)
  const msg = await calc()
  console.log('Az eredmény:', msg)
  console.log('Számolás vége.')
}
 
msg()

...bár ez még így is megfelelő sorrendben írja az üzeneteket, úgyhogy ezen még dolgozom:

let calc = () => {
  var i, j, s
  for (i = 1; i < 10000; i++) {
    s = i * i * i
    for (j = 1; j < 10000; j++) {
      s = j / j / j
    }
  }
  return s
}
 
console.log(`Számolás indul...`)
console.log('Az eredmény:', calc())
console.log('Számolás vége.')

JSON

8.1 Általános tudnivalók a JSON-ról

A JavaScript Object Notation rövidítése.

A JS objektumkezelési szintaktikáján alapul (de nem pontosan ugyanaz!), így sokkal hatékonyabb JavaScript app-okkal, mint a nehezebben kezelhető XML. Mostanra már számos más nyelv kiválóan kezeli a JSON-t (Python, C++ stb.).

{
  "key": "value",
  "another_key": "another value"
}

A különbségek és szabályok a JS objektumleírásokhoz képest:

  • a kulcsok és értékek egyaránt dupla ("rendes") idézőjelek között kell, hogy legyenek. Az aposztróf sem elegendő, a kulcsoknál az idézőjelek elhagyása szintén nem elfogadható.
  • a kulcsokban is lehet szóköz és mindenféle karakter, de ezek nagyban nehezíthetik a kezelést. Az aláhúzás OK.
  • csak e 6 adattípus valamelyike lehet az érték: string, number, object, array, boolean vagy "null"
  • a JS objektumban egy kulcs értéke akármilyen típus lehet, akár egy függvény is (és ez gyakori is egyébként)
  • a JSON megetetése a JS-el csak "parse"-olást követően történhet (eval vagy .parse). A folyamat ellenkezője a stringify metódussal történik. (JS objektum konvertálása JSON adattá)

Az utolsó érték után nem kell vessző:

var info = {
  full_name: 'Jakab Gipsz',
  title: 'főnök',
  company: 'Retek Kft.',
  qty: 3,
}

Lehet szépíteni is a jobb olvashatóság kedvéért:

var info = {
  full_name: 'Jakab Gipsz',
  title: 'főnök',
  company: 'Retek Kft.',
  qty: 3,
}

Hozzáférés egy értékhez (dot notation):

console.log(info.full_name)

Ugyanez bracket notation-el (ha a kulcs érvénytelen lenne JS esetében, pl. ha a kulcsban szóköz van):

console.log(info['full_name'])

Objektum értékként való használata:

var info = {
  full_name: 'Jakab Gipsz',
  title: 'író',
  links: {
    blog: 'http://gipszjakab.blog.hu',
    facebook: 'http://facebook.com/gipszjakab',
    youtube: 'http://youtube.com/gipszjakab',
  },
}
 
// Az utolsó érték egyikének elérése így:
 
console.log(info.links.facebook)

Bár az elérés egyszerűbb, nincs rá garancia, hogy az objektum elemei az eredeti sorrendben tárolódnak a memóriában is. Emiatt a tömb használata esetenként ésszerűbb.

Tömb értékként való használata: ennek a megadási módja szintén szögletes-zárójelek között történik:

var info = {
  full_name: 'Jakab Gipsz',
  title: 'főnök',
  courses: ['JavaScript & JSON', 'Facebook Apps', 'jQuery Mobile Web Apps'],
}

Az utolsó érték egyikének elérése így:

console.log(info.courses[1])

A JSON-t érdemes validáltatni valamilyen tool-al, amilyen pl. a JS Hint (JS validálás) vagy JSONLint (csak JSON validálás). Még ezeknél is látványosabb a JSON Editor Online (jsoneditoronline.org). A böngésző is tudja a JSON-t megfelelően megjeleníteni.

A módosítás is a sima dot notation-el hozzáférhetően módosítható, vagy akár törölhető, pl. delete(info.title).

A tömbből elem törlése, hozzáadása viszont nem ilyen egyszerű, tehát pl. a delete(info.courses[1]) módszerrel nem lehet törölhetjük ki az 1. sorszámú elemet. Helyette a splice és push metódusokat kell használnunk:

// Odamegy az 1. elemhez ("Facebook Apps") és onnan 1 elemet töröl:
info.courses.splice(1, 1)
 
console.log(info.courses)
 
// A lista végéhez hozzáadja az új elemet:
info.courses.push('JavaScript - Classes')
 
console.log(info.courses)

Ha a tömbön belül objektumokat használunk, akkor azon is működnek a splice és push metódusok:

Tömbön belüli objektumos verizó (ami még mindig nem teljesen oké):

var info = {
  full_name: 'Jakab Gipsz',
  title: 'író',
  links: [{ blog: 'http://gipszjakab.blog.hu' }, { facebook: 'http://facebook.com/gipszjakab' }, { youtube: 'http://youtube.com/gipszjakab' }],
}
 
info.links.push({ twitter: 'http://twitter.com/gipszjakab' })

8.2 Objektum elemeinek ciklusba helyezése

var info = {
  full_name: 'Jakab Gipsz',
  title: 'író',
  links: {
    blog: 'http://gipszjakab.blog.hu',
    facebook: 'http://facebook.com/gipszjakab',
    youtube: 'http://youtube.com/gipszjakab',
  },
}
 
var out = ''
 
for (key in info.links) {
  if (info.links.hasOwnProperty(key)) {
    out += '<li>' + '<a href="' + info.links[key] + '">' + key + '</a></li>\n'
  }
}
 
console.log(out)
 
var update = document.getElementById('links')
console.log(update)
update.innerHTML = out

8.2.1 Tömbön belüli objektumok elérése, ciklusba helyezése

var info = {
  full_name: 'Jakab Gipsz',
  title: 'író',
  links: [
    { blog: 'http://gipszjakab.blog.hu' },
    { facebook: 'http://facebook.com/gipszjakab' },
    { youtube: 'http://youtube.com/gipszjakab' },
    { twitter: 'http://twitter.com/gipszjakab' },
  ],
}
 
console.log(info.links)
 
var out = ''
for (var i = 0; i <= info.links.length; i++) {
  for (key in info.links[i]) {
    if (info.links[i].hasOwnProperty(key)) {
      out += '<li>' + '<a href="' + info.links[i][key] + '">' + key + '</a></li>\n'
    } // hasOwnProperty check
  } // for each object
} // for each array element
 
console.log(out)

A JSONP egy másik technika, hogy rendezett JSON adatokat töltsünk le külső weboldalakról. Pl.:

JSONP Request:

<script src="https://api.example.com/data? callback=parseData"></script>

JSONP Response:

parseData({ champion: 'mango', runnerUp: 'banana', secondRunnerUp: 'apple' })

A jQuery is hasznos segédeszköz, ha .json adatfájlokat kell betöltenünk.

Egy letöltött JSON fájl általában emberi olvashatóságra nem alkalmas állapotban érkezik, azaz egyben van az egész sortörések és behúzások nélkül. Ezen segít a JSON.stringify metódus:

JSON.stringify(value, replacer, spacer)

In a modern browser, we can test if an object is empty using the Object.keys.length property.

JSON Schema-t is definiálhatunk, amivel aztán validáltathatjuk a betöltött JSON adatot. Arra is jó, ha required property-ket akarunk vele követeltetni.

8.3 Konverziók

8.3.1 XML - JSON konverzió

Az XML esetében minden tag-nek bezárva kell lennie (akár /> végződéssel), az egésznek egy tag-ben kell lennie ("single root element"), az egymásba helyezett tag-eknek megfelelő sorrendben kell lenniük és még a kis-/nagybetűknek is stimmelniük kell.

A json2xml.js és a xml2json.js egyszerű library-k a kettő közötti konverzióra.

8.3.2 YAML - JSON konverzió

A YAML-t mégegyszerűbb ember számára olvasni még a JSON-nál is ("YAML is superset of JSON"), mert sortöréseken és behúzásokon alapul. A szövegekhez még idézőjelet sem kell alkalmazni. JS-ben nem is használható közvetlenül. A fájlok kiterjesztése a .yml.

A yaml.js nyújt konverziót oda-vissza.

8.4 Függvény értékként való beszúrása

Ez csak egy próba, majd még lehet, hogy módosítom: (???)

let myVar = []
 
let myFunc = () => {
  myVar.push({
    name: 'Ede',
    weight: (() => {
      return 40 + 30
    })(),
  })
  console.log(myVar)
}
 
myFunc()
on this page
    back to top