Webprogramozás

További JavaScript nyelvi elemek, kódszervezés

Horváth Győző
Egyetemi docens
1117 Budapest, Pázmány Péter sétány 1/c., 2.408-as szoba
Tel: (1) 372-2500/8469
horvath.gyozo@inf.elte.hu

Ismétlés

Ismétlés – nyelvi elemek

  • Dinamikusan típusos
  • Interpretált nyelv
  • Szintaxis C++-hoz hasonló
    • vezérlési szerkezetek
    • operátorok
  • Adatszerkezetek
    • elemi típusok
    • összetett típusok: tömb és objektum
  • Funkcionális aspektus
  • OOP-s aspektus

Ismétlés – DOM

  • HTML elemek belső ábrázolása
  • programozási interfész (API)
  • bemeneti-kimeneti interfész

Ismétlés – DOM

  • Elemek kiválasztása
    • document.querySelector('css selector')
    • document.querySelectorAll('css selector')
  • Elem (JavaScript objektum) tulajdonságai
    • Analógia: Webfejlesztés → Webprogramozás
    • tulajdonságok, írás/olvasás
    • stílusok
    • faműveletek

Ismétlés – Eseménykezelés

  • Eseménykezelés
    • elem.addEventListener(típus, fgv)
    • Interakció eszköze
    • mini-programok
    • Eseményobjektum
    • Alapértelmezett műveletek megakadályozása
    • Buborékolás
    • Delegálás

További nyelvi elemek

typeof operátor

Adott érték típusa szövegesen

typeof undefined      === "undefined"
typeof null           === "object"
typeof true           === 'boolean'
typeof 37             === 'number'
typeof 42n            === 'bigint'
typeof NaN            === 'number'
typeof 'bla'          === 'string'
typeof {a: 1}         === 'object'
typeof [1, 2, 4]      === 'object' // Array.isArray()
typeof new Date()     === 'object'
typeof function() {}  === 'function'

Referencia

Number

  • Konstans
    • Number.NaN (NaN)
    • Number.POSITIVE_INFINITY (Infinity)
    • Number.NEGATIVE_INFINITY (-Infinity)
  • Statikus metódusok
    • Number.isNaN(value)
    • Number.isFinite(value)
    • Number.isInteger(value)
    • Number.parseInt(str[, radix])
    • Number.parseFloat(str)

Referencia

Number

// NaN
const a = 'alma' / 2         // NaN
Number.isNaN(a)              // true
3 + Number.NaN               // NaN

// Infinity
const a = 10/0
isFinite(a)                  //false

// parseInt, parseFloat
Number.parseInt('123')       //123
Number.parseInt('123', 10)   //123
Number.parseInt('0101', 2)   //5
Number.parseInt('alma', 10)  //NaN
Number.parseInt('5alma', 10) //5
Number.parseFloat('4.54')    //4.54

Number

  • Példánymetódusok
    • numObj.toExponential([fractionDigits])
    • numObj.toFixed([digits]): tizedespont utáni számjegyek száma
    • numObj.toPrecision([precision]): számjegyek száma
    • numObj.toString([radix])
// boxing: literal to numObj
3.toString()                  // error
(3).toString()                // "3"
Number.parseInt(3).toString() // "3"
const n = 3
n.toString()                  // "3"

// példánymetódusok
(123.456).toExponential(2)    // "1.23e+2"
(123.456).toFixed(2)          // "123.46"
(123.456).toPrecision(2)      // "1.2e+2"
(123.456).toPrecision(4)      // "123.5"
(123).toString(2)             // "1111011"

BigInt

253-1-nél nagyobb egész számok ábrázolására

Operátorok: +, -, *, /, **

const bigint1 = 42n
const bigint2 = BigInt(42)
const bigint3 = BigInt("42")
const bigint4 = BigInt(Number.MAX_SAFE_INTEGER) // 9007199254740991n
const bigint5 = bigint4 + 1n                    // 9007199254740992n 

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Calculating_Primes
function isPrime(p) {
  for (let i = 2n; i * i <= p; i++) {
    if (p % i === 0n) return false;
  }
  return true;
}

Date

  • Date objektum
  • Aktuális időpont
    • Date.now()
  • Megadott időpont
    • new Date(year, month, day [, hour, minute, second, millisecond]);
    • Date.parse(dateString): ms
  • Műveletcsoportok
    • getterek: getMonth(), getDay(), stb
    • setterek: setMonth(), setDay(), stb.
    • toString metódusok: pl. toLocaleString(), toGMTString()

Referencia

Date példa

// Létrehozás
const d1 = new Date()
const d2 = new Date(2019, 0, 31, 3, 42, 0);
const d3 = new Date("2019-01-31T03:42:00");
const ms1 = Date.now();
const ms2 = Date.parse("2019-01-31T03:42:00");

// Műveletek
d2.getFullYear()            // 2019
d3.setFullYear(2020)
d2.toLocaleString('hu-HU')  // "2019. 01. 31. 3:42:00"
d2.toLocaleString('en-US')  // "1/31/2019, 3:42:00 AM"

// Eltelt idő (ms-okban)
const delta = ms2 - ms1

String műveletek

  • egy karakter vagy kódja
    • charAt(i), charCodeAt(i)
  • keresés elölről, hátulról, indexet ad vissza
    • indexOf(mit, honnan), lastIndexOf(mit, honnan)
  • összehasonlítás, -1, 0, 1
    • localeCompare(mivel[, locale])
  • keresés, csere, reguláris kifejezésekkel is
    • search(regexp), replace(mit, mivel), match(regexp), matchAll(regexp)
  • részszöveg
    • substr(honnan, hossz), substring(mettől, meddig), slice(mettől, meddig)

String műveletek

  • szöveg felbontása → tömb
    • split(szeparátor)
  • kezdődik, végződik
    • startsWith(str), endsWith(str)
  • kitöltés
    • padStart(length[, str]), padEnd(length[, str])
  • ismétel
    • repeat(num)
  • fehérszóköz eltávolítás
    • trim(), trimStart(), trimEnd()
  • kisbetű, nagybetű
    • toLowerCase(), toUpperCase(), toLocaleLowerCase(locale), toLocaleUpperCase(locale)

String példa

'piros alma'.charAt(2)                    // "r"
'piros alma'.charCodeAt(2)                // 114
'piros alma'.indexOf('alma')              // 6
'piros alma'.localeCompare('piros körte') // -1
'piros alma'.replace('piros', 'sárga')    // "sárga alma"
'piros alma'.substr(2, 3)                 // "ros"
'piros alma'.split(' ')                   // ["piros","alma"]
'piros alma'.startsWith('piros')          // true
'piros alma'.padStart(15)                 // "     piros alma"
'     piros alma'.trim()                  // "piros alma"
'piros alma'.toUpperCase()                // "PIROS ALMA"

Szövegkódolás

  • Speciális karakterek
    • tárolása
    • küldése
  • Teljes URL kódolása
    • encodeURI(), decodeURI()
  • URL-beli érték kódolása
    • encodeURIComponent(), decodeURIComponent()
const kod = encodeURIComponent('árvíztűrőtükörfúrógép');
//"%C3%A1rv%C3%ADzt%C5%B1r%C5%91t%C3%BCk%C3%B6rf%C3%BAr%C3%B3g%C3%A9p"
const dekod = decodeURIComponent(kod);
//"árvíztűrőtükörfúrógép"
const url = encodeURI('http://server.hu/útvonal szóközökkel');
//"http://server.hu/%C3%BAtvonal%20sz%C3%B3k%C3%B6z%C3%B6kkel"

RegExp

  • Reguláris kifejezés
  • Olyan minta, amely szövegekre illeszthető
  • Literálforma: /minta/flags
    • minta: normál szöveg + speciális jelölők
    • flag: viselkedést szabályozó jelölők
  • Metódusok
    • test(str) → logika érték
    • exec(str) → tömb
  • String függvények használják intenzíven
    • search(regexp)
    • replace(regexp, mire)
    • match(regexp), matchAll(regexp)
    • split(regexp)

RegExp példa

/\d+/.test('alma')    //false
/\d+/.test('alma123') //true
/alma/.test('piros alma kukacos');  // true
/alma$/.test('piros alma kukacos')  //false
/^\w+/.test('piros alma')           //true
/alma/i.test('Piros Alma')          //true
"piros alma kukacos".replace(/os/, 'itott')  //"piritott alma kukacos"
"piros alma kukacos".replace(/os/g, 'itott') //"piritott alma kukacitott"

"1a234  5b678 90"
  .replace(/\D/g, '')       // "1234567890"
  .replace(/.{4}/g, '$& ')  // "1234 5678 90"

"Horvath Gyozo".replace(/(\w+)\s(\w+)/i, "$2, $1") // "Gyozo, Horvath"
'Hófehérke és a 7 törpe meg az 1 herceg'.match(/\d+/g) // ["7", "1"]

/(\w+)\s(\w+)/ig.exec("Horvath Gyozo Horvath Emese")
// [
//   'Horvath Gyozo',
//   'Horvath',
//   'Gyozo',
//   index: 0,
//   input: 'Horvath Gyozo Horvath Emese',
//   groups: undefined
// ]
Array.from("Horvath Gyozo Horvath Emese".matchAll(/(\w+)\s(\w+)/ig))
// [
//   [
//     'Horvath Gyozo',
//     'Horvath',
//     'Gyozo',
//     index: 0,
//     input: 'Horvath Gyozo Horvath Emese',
//     groups: undefined
//   ],
//   [
//     'Horvath Emese',
//     'Horvath',
//     'Emese',
//     index: 14,
//     input: 'Horvath Gyozo Horvath Emese',
//     groups: undefined
//   ]
// ]

Math

  • Matematikai műveletek
    • sin(rad), asin(sz), cos(rad), acos(sz), tan(rad), atan(sz)
    • pow(alap, kit), exp(n), log(n), stb.
    • random()
    • round(n), floor(n), ceil()
  • Konstansok
    • PI
    • E
    • SQRT2
    • LN2, stb.

Math példa

Math.PI                       //3.141592653589793
Math.sin(90)                  //0.8939966636005579
Math.sin(90 * Math.PI / 180)  //1
Math.random()                 //pl. 0.47057095554085615
Math.random()                 //pl. 0.5286946792885748
Math.round(1.6)               //2
Math.floor(1.6)               //1

Array

  • pop(), push(e), shift(e), unshift()
    • végéről, végére, elejére,elejéről
  • reverse()
    • megfordít
  • splice(honnan, mennyit)
    • kivág
  • join(szeparátor)
    • szöveggé fűz
  • indexOf(elem)
    • keresés
  • includes(elem)
    • eldöntés

Array példa

const t = [1, 2, 3, 4, 5]
t.push(6)           // [1, 2, 3, 4, 5, 6]
t.pop()             // 6, [1, 2, 3, 4, 5]
t.unshift(0)        // [0, 1, 2, 3, 4, 5]
t.shift()           // 0, [1, 2, 3, 4, 5]
t.reverse()         // [5, 4, 3, 2, 1]
t.sort()            // [1, 2, 3, 4, 5]
t.splice(2, 1)      // [5, 4, 2, 1]
t.join('###')       // "5###4###2###1"

[1, 2].concat([3, 4])               // [1, 2, 3, 4]
[1, 2, 3, 4, 5].copyWithin(2, 0, 2) // [1, 2, 1, 2, 5]
[1, 2, 3].fill(4)                   // [4, 4, 4]
[1, 2, [3, 4]].flat()               // [1, 2, 3, 4]
[1, 2, 3, 4].flatMap(x => [x * 2])  // [2, 4, 6, 8]
[1, 2, 3, 4, 5].slice(2, 4)         // [ 3, 4 ]

const items = ['réservé', 'premier', 'cliché', 'communiqué', 'café', 'adieu']
items.sort(function (a, b) {
  return a.localeCompare(b)
})

Map

  • set(key, value)
  • get(key)
  • has(key)
  • delete(key)
  • size
  • keys(), values(), entries()
  • clear()
const map = new Map()
map.set('some', 'value')
map.get('some')             // 'value'
map.has('some')             // true
map.delete('some')

const map2 = new Map([['first', 1], ['second', 2]]);
for (const [key, value] of map2.entries()) {
  console.log(key + ' = ' + value);
}
// 'first' = 1
// 'second' = 2

Set

  • add(value)
  • has(value)
  • delete(value)
  • size
  • values()
  • clear()
const set = new Set()
set.add(1)
set.has(1)             // true
set.delete(1)

const set2 = new Set([1, 2, 3, 4, 1, 2, 3]);
for (const value of set2) {
  console.log(value);
}
// 1, 2, 3, 4

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Implementing_basic_set_operations
function isSuperset(set, subset) {
    for (var elem of subset) {
        if (!set.has(elem)) {
            return false;
        }
    }
    return true;
}

Időzítő

Böngésző API

setTimeout

  • timerId = setTimeout(fn, ms)
    • Egy adott függvény futtatása ms ms múlva
  • clearTimeout(timerId)
    • A timerId-jú időzítő leállítása
// Külön függvény
function tick() {
  console.log('tick')
}
setTimeout(tick, 1000)

// Helyben függvény
setTimeout(function () {
  console.log('tick')
}, 1000)

// Időzítő törlése
const timer = setTimeout(() => {}, 1000)
// do something, or even in an event:
clearTimeout(timer)

setInterval

  • timerId = setInterval(fn, ms)
    • Egy adott függvény futtatása ms ms-onként
  • clearInterval(timerId)
    • A timerId-jú időzítő leállítása
// Külön függvény
function tick() {
  console.log('tick')
}
setInterval(tick, 1000)

// Helyben függvény
setInterval(function () {
  console.log('tick')
}, 1000)

// Időzítő törlése
const timer = setInterval(() => {}, 1000)
// do something, or even in an event:
clearInterval(timer)

Időzítő használata

  • Késleltetett végrehajtás
  • Újrarajzolás megvárása (pl. animáció)
  • Emberi léptékű műveletvégrehajtás (pl. animáció)
  • Hosszú műveletek felosztása

Kódszervezés

Fizikai és logikai egységek

Kódszervezés

  • Fizikai
    • külön fájl
    • modul
  • Logikai
    • függvény
    • osztály (egységbe zárás)
    • modul

Fizikai csoportosítás

  • külön fájl
  • függőségek kézi kezelése
// math.js
const add = (a, b) => a + b
const multiply = (a, b) => a * b
// app.js
console.log(add(40, 2))
<body>
  <!-- ... -->
  <script src="math.js"></script>
  <script src="app.js"></script>
</body>

Fizikai csoportosítás

  • külön fájl: modul
  • függőségek automatikus kezelése
// math.js
const add = (a, b) => a + b
const multiply = (a, b) => a * b
export { add, multiply }
// app.js
import { add } from "./math.js"
console.log(add(40, 2))
<body>
  <!-- ... -->
  <script type="module" src="app.js"></script>
</body>

Modulok – export

// in-place export
export const add = (a, b) => a + b
export const multiply = (a, b) => a * b

// separate export
const add = (a, b) => a + b
const multiply = (a, b) => a * b
export { add, multiply }

// default export
export default const add = (a, b) => a + b

// rename exports
const add = (a, b) => a + b
const multiply = (a, b) => a * b
export { add as customAdd, multiply as customMultiply }

// export from module
export * from "./other.js"

Referencia

Modulok – import

// import entities
import { add, multiply } from "./math.js" 

// import defaults
import add from "./math.js"

// rename imports
import { add as mathAdd } from "./math.js"

// import module object
import * as MyMath from "./math.js"

// import jus for side effects
import "./something.js"

// import URL
import * as MyMath from "http://some.where.hu/math.js"

Referencia

Modulok

  • strict mode
  • nem globális scope
    • nehezebb debugolás
  • file protocoll nem működik
    • Node.js és npm installálása
    • npx serve
    • http://localhost:5000

Logikai csoportosítás

  • Függvények
    • elemi egység
    • műveletek strukturálása
// helper function
function range(n) { return Array.from({length: n}, (e, i) => i+1); }

// HTML generator
function genList(list) {
    return `<ul>${list.map(e => `<li>${e}</li>`).join('')}</ul>`
}

// business logic
const add = (a, b) => a + b

// event handler
function onClick(e) {
  // ...
}

Logikai csoportosítás

  • Osztályok
    • magasabb szintű egység
    • műveletek és adatok egységbe zárása

Kódszervezés

Egységbe zárás

Egységbezárás 1.

Globális változók és metódusok

let szam = 0
function novel() {
  szam++
}
function init() {
  szam = 0
}

novel()
console.log(szam)

Egységbezárás 2.

Objektum, singleton

const jatek = {
  szam: 0,
  novel: function() {
    this.szam++
  },
  init: function () {
    this.szam = 0
  }
}

jatek.init()
jatek.novel()
console.log(jatek.szam)

Egységbezárás 3.

Osztály

class Jatek {
  constructor() {
    this.szam = 0
  }
  novel() {
    this.szam++
  }
}

const jatek = new Jatek()
jatek.novel()
console.log(jatek.szam)

Egységbezárás 4.

Függvény, saját hatókörrel (felfedő modul minta)

const jatek = (function () {
  let szam = 0
  function novel() {
    szam++
  }
  function init() {
    szam = 0
  }
  function getSzam() {
    return szam
  }
  return {
    novel: novel,
    getSzam: getSzam
  }
})()

jatek.novel()
console.log(jatek.getSzam())

Egységbezárás 5.

Modul, függvény

// game.js
let szam = 0
function novel() {
  szam++
}
function init() {
  szam = 0
}
function getSzam() {
  return szam
}
export { novel, init, getSzam }
// main.js
import { novel, init, getSzam } from "./game.js"

novel()
console.log(getSzam())

Egységbezárás 6.

Modul, osztály

// game.js
export class Jatek {
  constructor() {
    this.szam = 0;
  }
  novel() {
    this.counter += 1;
  }
}
import { Jatek } from './game.js';

const jatek = new Jatek();
jatek.novel();
console.log(jatek.szam);

Kódszervezés

Adattárolás

Todo lista

Tárolás a DOM-ban

Új elem:
<input>
<button>Hozzáad</button>
<ul></ul>
document.querySelector("button").addEventListener("click", ujElemKattintas);
function ujElemKattintas(e) {
    // beolvasás
    const ujelem = document.querySelector('input').value
    // kiírás
    document.querySelector('ul').innerHTML += `<li>${ujelem}</li>`
}

Tárolás a DOM-ban

Új elem:
<input>
<button>Hozzáad</button>
<ul></ul>
document.querySelector("button").addEventListener("click", ujElemKattintas);
function ujElemKattintas(e) {
    // beolvasás
    const ujelem = document.querySelector('input').value
    // kiírás
    const li = document.createElement('li')
    li.innerHTML = ujelem
    document.querySelector('ul').appendChild(li)
}

Tárolás a DOM-ban

  • Mindig onnan kell kiolvasni
  • Nem alap nyelvi elem
  • adat és feldolgozó függvény szétválik
  • probléma
    • egységbe zárás
    • tárolás, küldés

Tárolás a DOM-ban

Todo lista elemei

document.querySelector("button").addEventListener("click", ujElemKattintas);
function ujElemKattintas(e) {
    // beolvasás
    const ujelem = document.querySelector('input').value
    const lista = 
      Array.from(document.querySelectorAll('li'))
      .map(li => li.innerText)
    // feldolgozás
    lista.push(ujelem)
    console.log(lista)
    // kiírás
    const li = document.createElement('li')
    li.innerHTML = ujelem
    document.querySelector('ul').appendChild(li)
}

Adat és megjelenés szétválasztása

  • Alapelv
  • Könnyebb egységbe zárás
  • Nyelvi elemek
  • DOM lassú

Adat és megjelenés szétválasztása

const lista = []

document.querySelector("button").addEventListener("click", ujElemKattintas);
function ujElemKattintas(e) {
    // beolvasás
    const ujelem = document.querySelector('input').value
    // feldolgozás
    lista.push(ujelem)
    console.log(lista)
    // kiírás
    const li = document.createElement('li')
    li.innerHTML = ujelem
    document.querySelector('ul').appendChild(li)
}

Adat és megjelenés szétválasztása

A felület mint az adat leképezése (függvény)

const lista = []

document.querySelector("button").addEventListener("click", ujElemKattintas);
function ujElemKattintas(e) {
    // beolvasás
    const ujelem = document.querySelector('input').value
    // feldolgozás
    lista.push(ujelem)
    // kiírás
    document.querySelector('ul').innerHTML = genLista(lista)
}
function genLista(lista) {
    return `${lista.map(e => `<li>${e}</li>`).join('')}`
}

Megoldás részei

Kódszervezés

Felület kezelése

Kiírás a DOM-ba

  1. Imperatív megközelítés
    • direkt manipuláció
  2. Deklaratív megközelítés
    • adat leképezése
    • felület = fn(adat)
    • HTML generálók

Kártyák

Alap funkcionalitás, kattintás

Kártyák

Tábla generálása

<table>
  <tr>
    <td>
      <div class="card">
        <div class="front">1</div>
        <div class="back"></div>
      </div>
    </td>
  </tr>
</table>
const table = document.querySelector('table')
table.addEventListener('click', onClick)
function onClick(e) {
  const card = e.target.closest('.card')
  if (this.contains(card)) {
    card.classList.toggle('flipped')
  }
}

document.querySelector('button').addEventListener('click', onButtonClick)
function onButtonClick(e) {
  const n = parseInt(document.querySelector('input[type=number]').value)
  m = genMatrix(n)
  table.innerHTML = genTable(m);
}

function random(a, b) {
  return Math.floor(Math.random() * (b - a + 1)) + a
}
function genMatrix(n) {
  return (new Array(n)).fill(0).map(() => (new Array(n)).fill(0).map(() => random(1, 99)))
}
function genTable(m) {
  return `${m.map(row => `
    <tr>
      ${row.map(cell => `
        <td>
          <div class="card">
            <div class="front">${cell}</div>
            <div class="back"></div>
          </div>
        </td>
      `).join('')}
    </tr>
  `).join('')}`
}

Kártyák

1 kattintásra több is változik → újrarajzolás

let m;
const selected = [];

const table = document.querySelector('table')
table.addEventListener('click', onClick)
function onClick(e) {
  const card = e.target.closest('.card')
  if (this.contains(card)) {
    const {x, y} = xyCoord(card)
    m[y][x].flipped = !m[y][x].flipped
    selected.push({x, y})
    if (selected.length > 2) {
      const [{x:x1, y:y1}, {x:x2, y:y2}] = selected.splice(0, 2)
      m[y1][x1].flipped = !m[y1][x1].flipped
      m[y2][x2].flipped = !m[y2][x2].flipped
    }

    table.innerHTML = genTable(m);  
  }
}

document.querySelector('button').addEventListener('click', onButtonClick)
function onButtonClick(e) {
  const n = parseInt(document.querySelector('input[type=number]').value)
  m = genMatrix(n)
  table.innerHTML = genTable(m);
}

function xyCoord(card) {
  const td = card.closest('td')
  const x = td.cellIndex
  const tr = td.parentNode
  const y = tr. sectionRowIndex
  return {x, y}
}
function random(a, b) {
  return Math.floor(Math.random() * (b - a + 1)) + a
}
function genMatrix(n) {
  return (new Array(n)).fill(0).map(() => (new Array(n)).fill(0).map(() => ({
    value: random(1, 99),
    flipped: false
  })))
}
function genTable(m) {
  return `${m.map(row => `
    <tr>
      ${row.map(cell => `
        <td>
          <div class="card ${cell.flipped ? 'flipped' : ''}">
            <div class="front">${cell.value}</div>
            <div class="back"></div>
          </div>
        </td>
      `).join('')}
    </tr>
  `).join('')}`
}

Kártyák

Animáció, újrarajzolás, hibás

.card.flipped {
  animation: flip 1s forwards;
}
@keyframes flip {
  100% {
    transform: rotateY(0deg);
  }
}
function onClick(e) {
  const card = e.target.closest('.card')
  if (this.contains(card)) {
    const {x, y} = xyCoord(card)
    m[y][x].flipped = !m[y][x].flipped
    selected.push({x, y})
    if (selected.length > 2) {
      const [{x:x1, y:y1}, {x:x2, y:y2}] = selected.splice(0, 2)
      m[y1][x1].flipped = !m[y1][x1].flipped
      m[y2][x2].flipped = !m[y2][x2].flipped
    }

    table.innerHTML = genTable(m);  
  }
}

Kártyák

Animáció, imperatív

function onClick(e) {
  const card = e.target.closest('.card')
  if (this.contains(card)) {
    const { x, y } = xyCoord(card)
    m[y][x].flipped = !m[y][x].flipped
    selected.push({ x, y })
    if (selected.length > 2) {
      const [{ x: x1, y: y1 }, { x: x2, y: y2 }] = selected.splice(0, 2)
      m[y1][x1].flipped = !m[y1][x1].flipped
      m[y2][x2].flipped = !m[y2][x2].flipped
      document.querySelector(`table tr:nth-child(${y1+1}) td:nth-child(${x1+1}) .card`).classList.toggle('flipped', m[y1][x1].flipped)
      document.querySelector(`table tr:nth-child(${y2+1}) td:nth-child(${x2+1}) .card`).classList.toggle('flipped', m[y2][x2].flipped)
    }

    selected.forEach(s => {
      document.querySelector(`table tr:nth-child(${s.y+1}) td:nth-child(${s.x+1}) .card`).classList.toggle('flipped', m[s.y][s.x].flipped)
    })
  }
}

Kártyák

Animáció, imperatív

function onClick(e) {
  const card = e.target.closest('.card')
  if (this.contains(card)) {
    const { x, y } = xyCoord(card)
    m[y][x].flipped = !m[y][x].flipped
    selected.push(card)
    if (selected.length > 2) {
      const [card1, card2] = selected.splice(0, 2)
      const {x:x1, y:y1} = xyCoord(card1)
      const {x:x2, y:y2} = xyCoord(card2)
      m[y1][x1].flipped = !m[y1][x1].flipped
      m[y2][x2].flipped = !m[y2][x2].flipped
      card1.classList.toggle('flipped', m[y1][x1].flipped)
      card2.classList.toggle('flipped', m[y2][x2].flipped)
    }

    selected.forEach(s => {
      const {x, y} = xyCoord(s)
      s.classList.toggle('flipped', m[y][x].flipped)
    })
  }
}

Összefoglalás

  • Nyelvi elemek
    • JavaScript beépített objektumai
  • Kódszervezés
    • fizikai, logikai
    • adattárolás helye
    • imperatív, deklaratív UI kezelés