by lkokoszka@future-processing.com aka "Max"
var text = "𠮷"; // CJK UNIFIED IDEOGRAPH console.log(text.length); // 2 (surrogate pair) console.log(/^.$/.test(text)); // singe character test; false console.log(text.charAt(0)); // "" console.log(text.charAt(1)); // "" console.log(text.charCodeAt(0)); // 55362 console.log(text.charCodeAt(1)); // 57271
var text = "𠮷a"; console.log(text.charCodeAt(0)); // 55362 console.log(text.charCodeAt(1)); // 57271 console.log(text.charCodeAt(2)); // 97 console.log(text.codePointAt(0)); // 134071 console.log(text.codePointAt(1)); // 57271 console.log(text.codePointAt(2)); // 97
console.log("\u0061"); // "a" - aktualny standard console.log("\u20BB7"); // "₻7" - próba użycia znaku Non-BMP console.log("\u{20BB7}"); // " " - ECMAScript 6
/u - wymusza prace silnika wyrażeń regularnych w oparciu o znaki, nie "code units"
* wszystkie przyjmują parametr location, czyli punkt początkowy i zwracają wartość bool'owską
let oneLiner = `it is a one line`; // znak delimitacji ` let multiLiner_maybe_bad = `Multi // zachowuje się jak <pre> line text`; let multiLiner_maybe_good = ` // prosty trick Multi line text `.trim();
let what = 'dog', with = 'a long tail', // używamy zmiennych "osiągalnych" w scope'ie msg1 = `it is a ${what}`, msg2 = `it is a ${what} with ${with}`; console.log(msg1); // "it is a dog" console.log(msg2); // "it is a dog with a long tail"
let howMuch = 20, exchangeRate = 4, convert = function (eur, exRate) { return (eur * exRate).toFixed(2); }, euro2pln = `it is ${(howMuch * exchangeRate).toFixed(2)} PLN`; euro2plnFn = `it is ${convert(howMuch,exchangeRate)} PLN`; console.log(euro2pln); // "it is 80.00 PLN" console.log(euro2plnFn); // "it is 80.00 PLN"
tag`Hello ${name}`
function extRangeTrans(literals, ...substitutions) { let result = "", i; for (i = 0; i < substitutions.length; i++) { result += literals[i]; result += substitutions[i]; } result += literals[literals.length - 1]; return result; } let pln = 20, exRate = 4.2, euro2pln = extRangeTrans` ${eur} EUR (@ ${exRate}) is ${(eur * exRate).toFixed(2)} PLN `.trim(); console.log(euro2pln); //20 EUR (@ ${exRate}) is 84.00 PLN
`a\nb` // a // b String.raw`a\nb` //a\nb
var text = 'First line\nsecond line'; var regex = /(\S+) line\n?/y; var match = regex.exec(text); match[1] // 'First' regex.lastIndex // '11' var match2 = regex.exec(text); match2[1] // 'Second' regex.lastIndex // '22' var match3 = regex.exec(text); match3 === null // 'true'
var re1 = /ab/i, re2 = new RegExp(re1, "g");
var value1 = 0o71; // 57 var value2 = 0b101; // 5 parseInt("0o71"); // 0 parseInt("0b101"); // 0 Number("0o71"); // 57 Number("0b101"); // 5
isFinite(25) // true isFinite("25") // true Number.isFinite(25) // true Number.isFinite("25") // false isNaN(NaN) // true isNaN("NaN") // true Number.isNaN(NaN) // true Number.isNaN("NaN") // false
Number.isInteger(25) // true Number.isInteger(25.0) // true Number.isInteger(25.1) // false Number.isSafeInteger(9007199254740991) // true Number.isSafeInteger(9007199254740992) // false
let color = 'red'; if (true) { console.log(color) // ReferenceError let color = 'white'; console.log(color) // "white" } console.log(color) // "red"
użycie var
var funcs = []; for (var i = 0; i < 10; i++) { funcs.push( function () { console.log(i); }); } funcs.forEach(function (func) { func(); // wypluje liczbę "10" 10x });
verte ↓
użycie let
var funcs = []; for (let i = 0; i < 10; i++) { funcs.push( function () { console.log(i); }); } funcs.forEach(function (func) { func(); // wypluje liczbę "0", potem "1" itd. });
const MAX_SIZE = 'red';
var sourceObject = {from: 1, to: 2} function ECMAScript5 (sourceObject) { var localFrom = sourceObject.from, localTo = sourceObject.to; } function ECMAScript6 (sourceObject) { let { from: localFrom, to: localTo } = sourceObject; console.log(localFrom, localTo); // :) // lub w przypadku mapowania nazwenictwa 1:1 let { from, to } = sourceObject; console.log(from, to); // :D }{ wlasciwoscObiektuZrodlowego: zmienneLokalna} = obiektZrodlowy
var sourceObject = { from: 1, to: 2, more: { max: 3 } }; // później gdzieś w kodziwie... let { from, to, more: { max }} = sourceObject; console.log(from, to, max); // :D - ! `more` nie zostanie zadeklarowane
var kolory = ['red', 'green', 'blue']; // później gdzieś w kodziwie... let [firstColor, secondColor] = kolory; console.log(firstColor, secondColor) // :D[ pierwszyElement, drugiElement...] = tablicaZrodlowa
var sourceObject = { from: 1, to: 2, colors: [ 'red', 'green', 'blue' ]}; // później gdzieś w kodziwie... let {from, to, colors: [firstColor, secondColor]} = kolory; console.log(from, to, firstColor, secondColor) // :D let {from, to, colors} = kolory; console.log(from, to, colors) // :D - colors będzie referencją do sourceObject.colors
function reload (what, in = 2000, then = function () {} ) { } function defaultAddWhatValue () { return 0; } function add (addTo, addWhat = defaultAddWhatValue() + 0) { }
function sampleRest (param1, param2, ...options) { ... console.log(options.length); // ^ == przekazane - zadeklarowane nazwane }
function destParams (param1, {setting1, setting2}, {setting3} = {}) { console.log(param1, setting1, setting2, setting3); } destParams(1); // error! destParams(1, {setting1: 'a', setting2: 'b'});
var params = ['a', 'b']; function yYoOeE (param1, param2) { } // zamiast yYoOeE.apply(yYoOeE, params); // szkrótem! yYoOeE(...params);
var doSomething = function doSomethingElse() { }; // doSomething.name == "doSomethingElse" function doSomething () { } // doSomething.name == "doSomething" var doAnotherThing = function () { }; // doAnotherThing.name == "doAnotherThing" var person = { get firstName () { }, sayName: function () { }}; // person.firstName.name == "get firstName" // person.sayName.name == "sayName" var test = doSomething.bind(null); // test.name == "bound doSomething" // (function () {}).name == "anonymous"
"use strict"; if ( true ) { console.log( typeof doSomething); // "function" function doSomething() { } } console.log( typeof doSomething); // "undefined"
var justReturn = value => value; // jest równoznaczne z: var justReturn = function (value) { return value; }; [0,1,2].forEach(v => { results.push(v * 2); });
var test = () => 'test'; var add = (param1, param2) => { return param1 + param2 };
var addFn = add() => { return add() };* wciąż dyskutowane
let local = 1, example = { local }; // example == { local: 1 };
let example = { do() { //... } } // typeof example.local == 'function'
let errorSuffix = 'Error', errorSuffixProvider = () => 'Error', example = { ['profile' + 'Error']: false, ['user' + errorSuffix]: false, ['network' + errorSuffixProvider()]: false }; // example = { // profileError: false, userError: false, networkError: false // }
let person = { getGreeting() { return "Hello"; } }; let dog = { getGreeting() { return "Woof"; } }; let friend = Object.create(person); friend.getGreeting() // "Hello" Object.getPrototypeOf(friend) === person // true Object.setPrototypeOf(friend, dog); friend.getGreeting() // "Woof" Object.getPrototypeOf(friend) === dog // true
// metoda getEmployer na instancji Developer // którego prototypem jest Employee getEmployer() { Developer.prototype.getEmployer.call(this); // lub this.__proto__.getEmployer.call(this) // lub Object.getPrototypeOf( this ).getEmployer.call( this ); // lub w ECMAScript 6 super.getEmployer(); // === super.getEmployer.call(this) // lub super() }
super referencja wprowadza konieczność zdefiniowana nowej ukrytej/wew. właściwiość funkcji/metody oznaczonej w dokumentacji jako [[HomeObject]]. Ta wskazuje na obiekt do którego dana funkcja/metoda należy i jest ustawiana w chwili definiowania funkcji.
To zmienia definicję metody w JS.
"ECMAScript 6 formally defines a method as a function that has an internal [[HomeObject]] property containing the object to which the method belongs."" zatem w każdym inny przypadku mamy doczynienia z funkcją!ponieważ [[HomeObject]] jest stricte związany z procesem definiowana funkcji, aby móc korzystać z dobordziejstw super referencji w przypadku nadpisywania metod obiektu w czasie jego życia, należy użyć nowej metody dostępnej dla każdej funkcji .toMethod(), która skonwertuje funkcję na metodę.
let friend = { sayHi() { return "hi!"; } }; function sayHiAndYo () { return super.sayHi() + ", yo!"; } friend.sayHi = sayHiAndYo; friend.sayHi(); // error! friend.sayHi = sayHiAndYo.toMethod(friend); // :)
var sym1 = Symbol(), sym2 = Symbol("foo"), sym3 = Symbol("foo"), sym4 = new Symbol(), // TypeError sym5 = Symbol.for('meta_data_storage'); // pierwszy lookup utworzy nowy symbol o danym opisie/kluczu // typeof sym1 == "symbol" // typeof sym3 == "symbol" // typeof sym5 == "symbol"- symbol może zawierać opis/klucz (przechowywany wew.), który: w przypadku symboli globalnych może posłużyć do jego odszukania w rejestrze, w przypadku lokalnych może przydać się w procesie debuggowania - ponieważ Symbol nie jest konstruktorem nie można używać go z operatorem `new`
var sym = Symbol.for('meta_data_storage'); // pierwszy lookup utworzy nowy symbol o danym opisie/kluczu console.log(Symbol.keyFor(sym)); // "meta_data_storage"w przypadku użycia keyFor z symbolem lokalnym metoda zwróci undefined
to lista symboli wykorzystywanych w silniku JS i opisanych w specyfikacji jako dostępnych dla programisty w celu umożliwenia zmian w część wewnętrznych zachowań języka. Spec. posługuje się notacją "@@nazwaSymbolu" dla odróżnienia symboli od innych danych
function Person(name) { this.name = name; } Person.prototype[Symbol.toStringTag] = "Person"; Person.prototype.toString = function () { return this .name; }; var me = new Person("Nicholas"); console.log(me.toString()); // "Nicholas" console.log(Object.prototype.toString.call(me)); // "[object Person]
ponieważ Object.getOwnPropertyNames nie zwróci właściwości zdefniowanych za pomocą symboli, aby uzyskać do nich dostęp należy użyć Object.getOwnPropertySymbols, który zwróci array symboli
function createIterator(items) { var i = 0; return { next: function () { var done = (i >= items.length), value = !done ? items[i++] : undefined; return { done: done, value: value }; } }; } var iterator = createIterator([1, 2, 3]); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: 3, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }definicja takiego iteratora jest możliwa już w ECMAScript 5, jedynie ostatni przypadek jest szczególny i związany z oczekiwaniami ECMAScript 6
// generator function *createIterator() { yield 1; yield 2; yield 3; }
var obj = { es5: function * () {}, *es6() {} }; class A { *giveMe () {} }
// generator function *createIterator() { yield 1; yield 2; yield 3; } let iterator = createIterator(); for ( let i of iterator ) { console.log(i); }
// generator function *createIterator() { yield 1; return 100; yield 2; yield 3; }to jednak zadziała tylko przy manualnej iteracji - for..of ignoruje zwróconą wartość
function *createNumberIterator() { yield 1; yield 2; } function *createCombinedIterator() { yield *createNumberIterator(); yield true ; } var iterator = createCombinedIterator(); console.log(iterator.next()); // "{ value: 1, done: false }" console.log(iterator.next()); // "{ value: 2, done: false }" console.log(iterator.next()); // "{ value: true, done: false }" console.log(iterator.next()); // "{ value: undefined, done: true }"
function *createIterator() { yield * "test"; }
class Example extends AnotherExample { constructor () { super(); } methodA () {} static methodB () {} *[Symbol.iterator]() {} }
var c = class C {};
class Example { constructor () { this.property = true; } get property () {} set property () {} }ES6 definuje domyślny konstruktor
class Example { static test () { return 'test'; } } Example.test();
class Example extends AnotherExample {} class Example extends combine(AnotherExample, BaseExample) {}można dziedziczyć po null jak i dowolnym obiekcie (nie koniecznie zdefiniowany z użyciem class; np. Array)
ES6 wraz z obsługą klas wprowadza bardzo ważną zmianę związaną z tworzeniem ich instancji:instancja jest tworzona w najgłębiej położonym konstruktorze całego łańcucha dziedziczenia podczas gdy ES5 tworzył instancje zaraz po wywołaniu operatora new
verte ↓
to wymóg w przypadku wykorzystywania dziedziczenia
class Example extends AnotherExample { constructor () { this.something = true; // ReferenceError super() // WYMAGANE WYWOŁANIE konstruktora klasy nadrzędnej // za pomocą "super referencji" this.something = true; } }
nowy "niejawny parametr" dostępna we wszystkich funkcjach. Dla konstruktorów klas jest ref. na funkcję wywołującą.`new.target` jest dla konstruktorów tym czym`this` jest dla funkcji
class Example extends AnotherExample { constructor () { super() // `new.target` w AnotherExample wskazuje // na konstruktor Example } }- new.target.prototype staję prototype tworzonej instacji dzięki czemu wszystkie egzotyczne "właściwości" mogą zostać prawidołow zainicjalizowane - w przypadku normalnych funkcji new.target == undefined, dla "arrow functions" new.target == new.target otaczającego scope'a [1]
z zachowaniem ich "magicznych" właściwości jak np. Array
class MyArray extends Array { constructor () { super() } } (new MyArray()).length // będzie działać prawidłowo!ten feature wymaga jednak zmian w silnikach JS!
tj. nadpisanie konstruktora używanego przez metody zwracające nowe instancje, jak np. Array.prototype.map
class MyArray1 extends Array { } let result1 = new MyArray1().map(x => x); result1 instanceof MyArray1 // true class MyArray2 extends Array { static get [Symbol.species]() { return Array; } } let result2 = new MyArray2().map(x => x); result2 instanceof MyArray2 // false
var sample = new Set([1, "1", true, {}]); // iterable jako parametr sample.size // 4z użyciem for..of lub forEach
var obj = {}, sample = new WeakSet([obj]); // iterable jako parametr
var sample = new Map([["key1", "value1"], ["key2", "value2"]]); sample.set("key3", "value3"); sample.get("key3");z użyciem for..of lub forEach
var obj = {}, sample = new WeakMap([[obj, 'test']]); // iterable jako parametr
let target = {}, handler = { get (target, propKey, receiver) { console.log('get ' + propKey); return 123; }, ownKeys (target) { console.log('ownKeys'); return ['hello', 'world']; } }, proxy = new Proxy(target, handler); proxy.foo // "get foo" -> 123 Object.keys(proxy) // "ownKeys" -> "['hello', 'world']"
pułapka get jest na swój sposób szczególna, przez nią bowiem przechodzi większość akcji wykonywanych na obiektach zatem można jej używać do całościowego przeciążania obiektów
let proxy = new Proxy({ a: true, b: function () {} }, { get(target, propKey, receiver) { console.log('get ' + propKey); return target[propKey]; } }); proxy.a; // "get a" + true proxy.b(); // "get b" + 'call to b'
proxy może zostać użyte jako prototyp obiektu, dzięki czemu można wyłapywać operacje, które nie zostały zaspokojony przez obiekt lub wymusiły lookup po łańcuchu prototypów
let proto = new Proxy({}, { get(target, propertyKey, receiver) { console.log('GET ' + propertyKey); return target[propertyKey]; } }); let obj = Object.create(proto); obj.bla; // GET bla
let handler = { deleteProperty(target, propKey) { console.log('DELETE ' + propKey); return Reflect.deleteProperty(target, propKey); }, has(target, propKey) { console.log('HAS ' + propKey); return Reflect.has(target, propKey); } }; let proxy = new Proxy({}, handler); delete proxy.test; // DELETE test
var promise = new Promise(function (resolve, reject) { // tzw. "executor" resolve(); //jeśli poszło OK reject(); //jeśli coś poszło nie tak });
promise.then(r => 1).then(r => ({})).then(r => true);
promise.then(r => 1).catch(e => ({})).then(r => true);
Promise.all([promise1, promise2, promise3]); Promise.race([promiseDownload1, promiseDownload2, promiseDownload3]);
// modules/A.js export default function () {}; // modules/B.js export function print() {}; // modules/C.js import A from 'modules/A'; import {print} from 'modules/B'; export default function () {}; // modules/D.js import C from 'modules/C'; C();
// modules/A.js export default function () {}; export function test() {}; export class testClass() {}; // modules/B.js function doSomething() {} export doSomething; // modules/C.js const MY_CONST = ...; function myFunc() {} export { MY_CONST, myFunc };moduły mogą wykonywać wiele nazwanych exportów i nie więcej niż jeden domyślny (via default). można łączyć eksport domyślny z nazwanymi w jednym module (świetnym przykładem wykorzystania mieszanego exportu są underscore.js i lodash.js)
// modules/A.js export function doSomething() {} export {doSomething as justDo}; const MY_CONST = ...; export { MYCONST as THATCONST }; // lub export { MYCONST as default };
import {default as theDefault} from 'modules/B'; // == import theDefault from 'modules/B'; // pozostałe możliwości import theDefault, {named1, named2} from 'modules/B'; import { named1, named2 } from 'modules/B'; // aliasowanie / zmiana nazwy podczas importu import { named1 as moduleBFn }; // importowanie modułu do obiektu/tworzenie "namespace" import * as moduleB from 'modules/B'; // ładowanie bez importu import 'modules/B';
function add5(a) { return a + 5; } function add10 (a){ return add5(a); // tail call return 5 * add5(a); // a tutaj nie if (a > 5) { return add(10); // recursive tail call } }
function computeMaxCallStackSize() { try { return 1 + computeMaxCallStackSize(); } catch (e) { // Call stack overflow return 1; } } function computeMaxCallStackSize(size) { size = size || 1; return computeMaxCallStackSize(size + 1); }