Novinky v JavaScripte
vo verziách ES2015, ES2016, ES2017 a ES2018
ES2015
ES?
ECMAScript
ECMAScript
Oficiálny názov JavaScriptu
Java® je ochranná známka
ECMA
European Computer Manufacturers Association
ES2015 vs. ES6
HarmonyES6- ES2015
Novinky ES2015
Literály pre binárne a oktálové čísla
var x = 0b1011;
var y = 0o715;
Deklarácia premenných
-
let
-
const
Dva nové spôsoby deklarácie
Blokový scope
let foo = 'foo';
foo = 'foo 2';
// ----
const bar = ['pivo'];
bar.push('vino');
bar = ['borovicka']; // TypeError: invalid assignment
// to const `bar`
Arrow funkcie
- kratší zápis
- uchovávanie kontextu (this, arguments)
Arrow funkcie - kratší zápis
var pow = function (arg) {
return arg * arg;
};
const pow = (arg) => {
return arg * arg;
};
const pow = (arg) => arg * arg;
// Pozor, zátvorky okolo argumentov sa dajú odobrať
// iba v prípade jednoargumentovej funkcie
// Funkcia bez argumentov musí mať zátvorky uvedené!
const getName = () => 'zelenina';
const pow = arg => arg * arg;
Arrow funkcie - zdieľanie kontextu
const tyrion = {
name: 'tyrion',
tags: ['piť', 'nadávať', 'veci'],
printKnowledge: function () {
this.tags.forEach(function (tag) {
console.log(this.name + ' vie ' + tag);
});
}
};
tyrion.printKnowledge();
Skúste tento kód opraviť v starom JS (bez arrow funkcie)
Arrow funkcie - zdieľanie kontextu
const tyrion = {
name: 'tyrion',
tags: ['piť', 'nadávať', 'veci'],
printKnowledge: function () {
const that = this;
this.tags.forEach(function (tag) {
console.log(that.name + ' vie ' + tag);
});
}
};
tyrion.printKnowledge();
Starý JS: pomocná premenná "that"
Arrow funkcie - zdieľanie kontextu
const tyrion = {
name: 'tyrion',
tags: ['piť', 'nadávať', 'veci'],
printKnowledge: function () {
this.tags.forEach((function (tag) {
console.log(this.name + ' vie ' + tag);
}).bind(this));
}
};
tyrion.printKnowledge();
Starý JS: bindovanie
Arrow funkcie - zdieľanie kontextu
const tyrion = {
name: 'tyrion',
tags: ['piť', 'nadávať', 'veci'],
printKnowledge: function () {
this.tags.forEach((tag) => {
console.log(this.name + ' vie ' + tag);
});
}
};
tyrion.printKnowledge();
Nový JS: array funkcia
Rozšírené objektové literály
const name = 'Ferko';
const lastName = 'Mrkvička';
const x = 'abc';
const y = 'def';
const fero = {};
/* Pre objekt fero:
* - vytvorte property name a lastName,
* ktoré budú mať hodnoty z premenných vyššie
* - vytvorte metódu foo, ktorá vráti reťazec 'metóda foo'
* - vytvorte property, ktorej názov je poskladaný z x a y.
*/
console.log(fero.name);
console.log(fero.lastName);
console.log(fero.foo());
console.log(fero.abcdef);
Rozšírené objektové literály
const name = 'Ferko';
const lastName = 'Mrkvička';
const x = 'abc';
const y = 'def';
const fero = {
name: name,
lastName: lastName,
// vytvorte metódu foo, ktorá vráti reťazec 'metóda foo'
// vytvorte property, ktorej názov je poskladaný z x a y.
};
console.log(fero.name);
console.log(fero.lastName);
console.log(fero.foo());
console.log(fero.abcdef);
V starom JS
Rozšírené objektové literály
const name = 'Ferko';
const lastName = 'Mrkvička';
const x = 'abc';
const y = 'def';
const fero = {
name: name,
lastName: lastName,
foo: function () {
return 'metóda foo';
},
// vytvorte property, ktorej názov je poskladaný z x a y.
};
console.log(fero.name);
console.log(fero.lastName);
console.log(fero.foo());
console.log(fero.abcdef);
V starom JS
Rozšírené objektové literály
const name = 'Ferko';
const lastName = 'Mrkvička';
const x = 'abc';
const y = 'def';
const fero = {
name: name,
lastName: lastName,
foo: function () {
return 'metóda foo';
},
};
fero[x + y] = 'počítaná property';
console.log(fero.name);
console.log(fero.lastName);
console.log(fero.foo());
console.log(fero.abcdef);
V starom JS
Rozšírené objektové literály
const name = 'Ferko';
const lastName = 'Mrkvička';
const x = 'abc';
const y = 'def';
const fero = {
name,
lastName,
foo() {
return 'metóda foo';
},
[x + y]: 'počítaná property',
};
console.log(fero.name);
console.log(fero.lastName);
console.log(fero.foo());
console.log(fero.abcdef);
V novom JS
const name = 'Ferko';
const lastName = 'Mrkvička';
const x = 'abc';
const y = 'def';
const fero = {
name,
lastName,
foo() {
return 'metóda foo';
},
[x + y]: 'počítaná property',
};
console.log(fero.name);
console.log(fero.lastName);
console.log(fero.foo());
console.log(fero.abcdef);
const name = 'Ferko';
const lastName = 'Mrkvička';
const x = 'abc';
const y = 'def';
const fero = {
name: name,
lastName: lastName,
foo: function () {
return 'metóda foo';
},
};
fero[x + y] = 'počítaná property';
console.log(fero.name);
console.log(fero.lastName);
console.log(fero.foo());
console.log(fero.abcdef);
Destructuring - polia
const [x, y] = [1, 2];
const pole = ['agát', 'blýskavica', 'cieľovníci'];
const [a, , c] = pole;
const [foo, bar, baz = 33] = [11, 22];
// výmena hodnôt dvoch prvkov
let [var1, var2] = ['value 1', 'value 2'];
[var2, var1] = [var1, var2];
Destructuring - objekty
const obj = {
foo: 1,
bar: 2,
baz: 3,
};
const { foo, baz } = obj;
// prípadne pod iným názvom
const { foo: aliasFoo, baz } = obj;
// default hodnoty
const { foo, xyz = 42 } = obj;
Destructuring - objekty
// argumenty funkcie
const params = {
logger: 'console',
isDev: true,
};
foo(params);
const foo = function ({ isDev, logger }) {
console.log(logger);
console.log(isDev);
};
Default hodnoty argumentov funkcie
const foo = function (x, y = 5) {
// ...
};
const bar = (x, y = 5) => {
// ...
};
Rest operátor
const foo = (x, ...y) => {
console.log(y);
};
foo(1, 2, 3, 4, 5);
Spread operátor
const foo = (x, y, z) => {
console.log(x, y, z);
};
const arr = [1, 2, 3];
foo(...arr);
const foo = (x, y, ...z) => {
console.log(z);
};
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// čo bude vypísané v konzole?
foo(...arr1, ...arr2, ...arr3);
Chvíľka na zamyslenie
const foo = (x, y, ...z) => {
console.log(z);
};
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// čo bude vypísané v konzole?
foo(...arr1, ...arr2, ...arr3);
// [3, 4, 5, 6, 7, 8, 9]
Chvíľka na zamyslenie
const foo = (x, y, ...z) => {
console.log(z);
};
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// čo bude vypísané v konzole?
foo(...arr1, ...arr2, ...arr3);
// [3, 4, 5, 6, 7, 8, 9]
foo(arr1, arr2, ...arr3);
Chvíľka na zamyslenie
const foo = (x, y, ...z) => {
console.log(z);
};
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// čo bude vypísané v konzole?
foo(...arr1, ...arr2, ...arr3);
// [3, 4, 5, 6, 7, 8, 9]
foo(arr1, arr2, ...arr3);
// [7, 8, 9]
Chvíľka na zamyslenie
const foo = (x, y, ...z) => {
console.log(z);
};
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// čo bude vypísané v konzole?
foo(...arr1, ...arr2, ...arr3);
// [3, 4, 5, 6, 7, 8, 9]
foo(arr1, arr2, ...arr3);
// [7, 8, 9]
foo(...arr1, arr2, ...arr3);
Chvíľka na zamyslenie
const foo = (x, y, ...z) => {
console.log(z);
};
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [7, 8, 9];
// čo bude vypísané v konzole?
foo(...arr1, ...arr2, ...arr3);
// [3, 4, 5, 6, 7, 8, 9]
foo(arr1, arr2, ...arr3);
// [7, 8, 9]
foo(...arr1, arr2, ...arr3);
// [3, [4, 5, 6], 7, 8, 9]
Chvíľka na zamyslenie
Template string
const multiline = `toto je viacriadkový
reťazec
`;
console.log(multiline);
const str = 'reťazec';
const val = 'hodnotami';
const replaced = `toto je ${str} s nahradenými ${val}`;
console.log(replaced);
Generátory
const gen = function* () {
let n = 1;
while (true) {
yield n;
n++;
}
}
const generator = gen();
console.log(generator.next());
for-of cyklus
const arr = [1, 2, 3, 4, 5];
for (const i of arr) {
console.log(i);
}
const foo = function* () {
yield 1;
yield 2;
}
for (const i of foo()) {
console.log(i);
}
const str = 'hello world';
for (const c of str) {
console.log(c);
}
Iterátory
const cart = {
price: 23.5,
items: ['jablko', 'hruska', 'banan'],
[Symbol.iterator]: function* () {
let i = 0;
while (i < this.items.length) {
yield this.items[i];
i++;
}
}
};
for (const i of cart) {
console.log(i);
}
Unicode
Plná podpora Unicode v reťazcoch a regulárnych výrazoch
Rozšírenia pre Number
Number.EPSILON
Number.isInteger(Infinity)
Number.isNaN(NaN)
String.prototype.includes, String.prototype.repeat
"ferko mrkvička".includes("mrk");
"abc".repeat(3);
Object.assign
let person = {id: 42};
person = Object.assign(
person,
{
name: 'fero',
},
{
name: 'jozo',
surname: 'mrkvicka',
},
{
name: 'ferko',
}
);
Rozšírenia pre Array
Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
Array.from([1, 2, 3]); // [1, 2, 3]
Array.from([1, 2, 3], x => x + 10); // [11, 12, 13]
Array.from({ length: 5 }, (v, i) => i); // [0, 1, 2, 3, 4]
// -----
Array.of(1, 2, 3); // [1, 2, 3]
Rozšírenia pre Array.prototype
const arr = [0, 0, 0, 0]
arr.fill(7, 1, 2);
console.log(arr); // [0, 7, 0, 0]
// ---
const idx = arr.findIndex(x => x == 7);
console.log(idx); // 1
Rozšírenia pre Array.prototype
const arr = ['a', 'b', 'c'];
for (const i of arr.entries()) {
console.log(i);
}
for (const i of arr.keys()) {
console.log(i);
}
for (const i of arr.values()) {
console.log(i);
}
Nové metódy entries, keys a values, ktoré vracajú iterátor.
Ďalšie novinky:
- Promise
- Triedy
- Moduly
ES2016
const pole = [1, 2, 3];
pole.includes(5);
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
pole.includes(2);
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
pole.includes(2); // true
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
pole.includes(2); // true
pole.includes('2');
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
pole.includes(2); // true
pole.includes('2'); // false
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
pole.includes(2); // true
pole.includes('2'); // false
pole.includes(undefined);
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
pole.includes(2); // true
pole.includes('2'); // false
pole.includes(undefined); // false
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
pole.includes(2); // true
pole.includes('2'); // false
pole.includes(undefined); // false
delete pole[1];
pole.includes(undefined);
Zisťuje, či v poli existuje hodnota
Array.prototype.includes
Array.prototype.includes
const pole = [1, 2, 3];
pole.includes(5); // false
pole.includes(2); // true
pole.includes('2'); // false
pole.includes(undefined); // false
delete pole[1];
pole.includes(undefined); // true
Zisťuje, či v poli existuje hodnota
Umocňovací operátor **
console.log(
4 ** 2,
);
// 16
Výsledok je totožný s Math.pow(), ale funguje aj pre BigInt (ES2020)
ES2017
Zarovnávanie stringov
'ahoj'.padStart(4);
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
'ahoj'.padEnd(3);
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
'ahoj'.padEnd(3);
// 'ahoj'
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
'ahoj'.padEnd(3);
// 'ahoj'
'ahoj'.padEnd(7);
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
'ahoj'.padEnd(3);
// 'ahoj'
'ahoj'.padEnd(7);
// 'ahoj '
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
'ahoj'.padEnd(3);
// 'ahoj'
'ahoj'.padEnd(7);
// 'ahoj '
'ahoj'.padEnd(7, 'abcdefghi');
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
'ahoj'.padEnd(3);
// 'ahoj'
'ahoj'.padEnd(7);
// 'ahoj '
'ahoj'.padEnd(7, 'abcdefghi');
// 'ahojabc'
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
'ahoj'.padEnd(3);
// 'ahoj'
'ahoj'.padEnd(7);
// 'ahoj '
'ahoj'.padEnd(7, 'abcdefghi');
// 'ahojabc'
'7'.padEnd(3, '0');
Nové metódy padStart() a padEnd()
Zarovnávanie stringov
'ahoj'.padStart(4);
// 'ahoj'
'ahoj'.padStart(5);
// ' ahoj'
'ahoj'.padStart(7);
// ' ahoj'
'ahoj'.padStart(7, 'abcdefghi');
// 'abcahoj'
'7'.padStart(3, '0');
// '007'
'ahoj'.padEnd(4);
// 'ahoj'
'ahoj'.padEnd(3);
// 'ahoj'
'ahoj'.padEnd(7);
// 'ahoj '
'ahoj'.padEnd(7, 'abcdefghi');
// 'ahojabc'
'7'.padEnd(3, '0');
// '700'
Nové metódy padStart() a padEnd()
Object.values()
const osoba = {
meno: 'fero',
vek: 23,
};
Object.values(osoba);
// ['fero', 23]
Vráti pole obsahujúce vlastné property objektu
Object.entries()
const osoba = {
meno: 'fero',
vek: 23,
};
Object.entries(osoba);
// [['meno', 'fero'], ['vek', 23]]
Vráti pole obsahujúce polia kľúčov a hodnôt vlastných properties
Object.getOwnPropertyDescriptors()
Vráti objekt so všetkými descriptormi všetkých vlastných properties objektu
Descriptory sú sadou informácií o property, ktoré ju plne definujú.
- value: hodnota
- writable: vieme zapisovať?
- get: getter funkcia, ktorá sa volá pri čítaní
- set: setter funkcia, ktorá sa volá pri zápise
- configurable: ak je false, tak nevieme zmeniť nič okrem value
- enumerable: je vymenovateľná?
Object.getOwnPropertyDescriptors()
const osoba = {
meno: 'fero',
vek: 23,
};
Object.getOwnPropertyDescriptors(osoba);
Vráti objekt so všetkými descriptormi všetkých vlastných properties objektu
K čomu to je?
Object.assign ignoruje non-enumerable property.
Pomocou tejto metódy a Object.defineProperties() to vieme obísť
Trailing commas
function foo(
abc,
def,
ghi,
) {
// ...
}
foo(
123,
456,
789,
);
Môžeme mať nadbytočné čiarky v definícii aj volaní funkcie
Asynchrónne funkcie
function foo() {
return new Promise((resolve) => {
setTimeout(
() => resolve('fooooo'),
3000
)
});
}
function bar() {
console.log('pred foo');
foo().then((value) => {
console.log('po foo', value);
});
}
async / await
Doplnok k promisom a generátorom
Asynchrónne funkcie
function foo() {
return new Promise((resolve) => {
setTimeout(
() => resolve('fooooo'),
3000
)
});
}
async function bar() {
console.log('pred foo');
const value = await foo();
console.log('po foo', value);
}
async / await
Doplnok k promisom a generátorom
Asynchrónne funkcie
async / await
- doplnok k promisom a generátorom
- nemáme callback hell, čo poriešili Promises
- a nemáme ani promise-syntax hell
- pozor: await vieme použiť iba vo vnútri async funkcie
Zdieľaná pamäť medzi webworkerom a jeho tvorcom
SharedArrayBuffer
ES2018
Rest/Spread pre objekty
const person = {
name: 'Ferko',
surname: 'Mrkvička',
email: 'ferko.mrkvicka@example.com',
phone: '+111222333444'
};
//rest
const { name, surname, ...others} = person;
// spread
const personWithAge = {
...person,
age: 23,
}
for-await-of
async function* getData() {
yield 'mrkva';
yield 'jablko';
yield 'pomaranc';
}
// ...
for await (const i of getData()) {
// do something
}
Môžeme iterovať nad asynchrónnym iterovateľným "objektom"
Promise.prototype.finally
fetch()
.then(() => { /* ... */ })
.then(() => { /* ... */ })
.catch(() => { /* ... */ })
.finally(() => { /* ... */ })
Promisy dostali metódu finally, ktorá sa vykoná bez ohľadu na to, či došlo alebo nedošlo k chybe.
Vylepšenia pre RegExp
- look-behind ?<=, ?<!
- pomenované capturing groupy ?<meno>
- nový flag s pre single line porovnávanie (do bodky sa zahrnie aj znak nového riadku)
Ďakujem za pozornosť
Novinky v JS, 2015 - 2018
By Milan Herda
Novinky v JS, 2015 - 2018
- 324