Стандарт ECMAScript 6

Разработка динамических веб приложений

Гладкий Максим Валерьевич / github:MaksHladki

Содержание лекции

Долгие 10 лет

Как вдруг...

17 июня 2015 года генеральное собрание Ecma International официально одобрило стандарт ECMA-262 версии 6.0
  • Стандарт ECMA-262
  • ECMAScript 6
  • ECMAScript 2015
  • ECMAScript Harmony: улучшения после ECMAScript 5 (ES6 и ES7)
  • ECMAScript.next: кодовое название ECMAScript 6

Блочная область видимости

(let и const)

Оператор let

Позволяет объявить локальную переменную с областью видимости, ограниченной текущим блоком кода
                        
let var1 [= value1] [, var2 [= value2]] [, ..., varN [= valueN]];

//var1, var2, …, varN - имя переменной 
//(любой допустимый идентификатор)

//value1, value2, …, valueN - значение переменной 
//(любое допустимое выражение)
                        
                    

Блочная область видимости

ES5

                        
var apples = 5;
if (true) {
    var apples = 10;
    console.log(apples);//10 (внутри блока)
}

console.log(apples);//10 (снаружи блока то же самое)
                        
                    

ES6

                        
let apples = 5;
if (true) {
    let apples = 10;
    console.log(apples);//10 (внутри блока)
}

console.log(apples);//5 (снаружи блока значение не изменилось)
                        
                    

Видимость после объявления

                        
console.log(a);//undefined
var a = 5;
                        
                    
                        
console.log(a);//ReferenceError: a is not defined
let a = 5;
                        
                    
                    
let x;
let x;//SyntaxError: Identifier 'x' has already been declared
                    
                    
                        
if (true) {
    let apples = 10;
    console.log(apples);//10 (внутри блока)
}

console.log(apples);//ReferenceError: apples is not defined
                        
                    

Использование в цикле

ES5

                        
for(var i=0; i<10; i++) {
    console.log(i);//0, 1, 2, 3, 4 ... 9
}

console.log(i);//10
                        
                    

ES6

                        
for (let i = 0; i<10; i++) {
    console.log(i);//0, 1, 2, 3, 4 ... 9
}

console.log(i);//i is not defined	
                        
                    

Замыкания

                        
function shooterBuilder() {
    let shooters = [];

    for (let i = 0; i < 10; i++) {
        shooters.push(function() {
            console.log(i);
        });
    }

    return shooters;
}

var x = shooterBuilder();
x[0]();//0
x[5]();//5
//При использовании var было бы 10 10
                        
                    

Оператор const

Значение констант не может быть изменено новым присваиванием и не может быть переопределено. Константы подчиняются области видимости уровня блока
                        
const name1 = value1 [, name2 = value2 [, ... [, nameN = valueN]]];

//nameN - имя константы (те же правила, что и для переменных)
//valueN - значение константы
                        
                    

Пример 1

                        
const apple = 5;
apple = 10;//Uncaught TypeError: Assignment to constant variable
                        
                    
                        
const user = {
  name: "Alex"
};

user.name = "Maks";//допустимо
user = 5;//Uncaught TypeError: Assignment to constant variable
                        
                    

Пример 2

                        
const lit = 4;
lit = 5;//TypeError: Литерал изменить нельзя

const obj = { a: 1 };
obj.a = 2;//Значения внутри объекта изменить можно

console.log(obj);//{ a: 2 }
obj = { a: 3 };//TypeError: Ссылку менять нельзя

const arr = [1, 2, 3];
arr.push(4);//Значения внутри массива изменить можно

console.log(arr);//[1, 2, 3, 4]
arr = [4, 3, 2, 1];//TypeError: Ссылку менять нельзя
                        
                    

Деструктуризация

(destructuring assignment)

Понятие деструктуризации

Особый синтаксис присваивания, при котором можно присвоить массив или объект сразу нескольким переменным, разбив его на части
  • Деструктуризация позволяет привязывать данные при совпадении паттерна
  • Поддерживается для массивов и объектов
  • Деструктуризация устойчива к ошибкам и во многом похожа на поиск поля в объекте
  • Возвращает undefined, если что-то пошло не так

Деструктуризация массива

                        
let [firstName, lastName] = ['Иван', 'Иванов'];
console.log(firstName);//Иван
console.log(lastName);//Иванов
                        
                    

Пропуск элементов

                        
//первый и второй элементы не нужны
let [, , middleName] = "Иванов Иван Иванович (1971)".split(" ");
console.log(middleName);//Иванович
                        
                    

Первый и второй, а также все элементы после третьего никуда не записались, они были отброшены

Оператор spread

  • Значением rest будет массив из оставшихся элементов
  • Вместо rest можно использовать другое имя переменной
  • Оператор spread - троеточие
  • Spread должен стоять только перед последним элементом
                        
let [first, last, ...rest] = "Все ЛР должны быть сданы".split(" ");

console.log(first);//Все
console.log(last);//ЛР
console.log(rest);//["должны", "быть", "сданы"]
                        
                    

Значения по умолчанию

                        
let [firstName, lastName] = [];
console.log(firstName);//undefined
                        
                    
                        
let [firstName="Гость", lastName="Анонимный"] = [];
console.log(firstName);//Гость
console.log(lastName);  //Анонимный
                        
                    
                        
function defaultLastName() {
    return Date.now() + '-visitor';
}

//lastName получит значение, соответствующее текущей дате:
let [firstName, lastName=defaultLastName()] = ["Вася"];
console.log(firstName);//Вася
console.log(lastName);//1436...-visitor
                            
                        

Отделение объявления от присваивания

                        
let a, b;
[a, b] = [1, 2];

console.log(a);//1
console.log(b);//2
                        
                    

Swapping

                    
var a = 1;
var b = 3;
[a, b] = [b, a];

console.log(a);//3
console.log(b);//1
						
					

Использование возвращающей функции

                        
function f() {
    return [1, 2];
}

var a, b; 
[a, b] = f();

console.log(a);//1
console.log(b);//2
                        
                    

Работа c другими коллекциями

                        
//Строки
var [a, b, c] = 'xyz';
console.log(a, b, c);//x y z
                        
                    
                        
//Коллекции DOM элементов
var [link1, link2] = document.links;
console.log(link1.tagName);//A
console.log(link2.textContent);//Стандарт ECMAScript 6
                        
                    

Деструктуризация объектов

  • Указываем, какие свойства в какие переменные должны перейти
  • Объект справа – существующий объект
  • Список слева – список переменных, в которые записываются соответствующие свойства
                        
let {var1, var2} = {var1:…, var2…}
                        
                    

Пример

                        
let options = {
  title: "Меню",
  width: 100,
  height: 200
};

let {title, width, height} = options;

console.log(title);//Меню
console.log(width);//100
console.log(height);//200
                        
                    

Использование возвращающей функции

                        
var calc = function(num) {
    return Math.pow(num, 2);
};

var { prop: x, pow: y = calc(x)} = { prop: 4 };
console.log(x, y);//4 16

var { prop: x, pow: y = calc(x)} = { prop: 4, pow: 10 };
console.log(x, y);//4 10
                        
                    

Другое имя переменной

                    
let options = {
    title: "Меню",
    width: 100,
    height: 200
};

let {width: w, height: h, title} = options;

console.log(title);//Меню
console.log(w);//100
console.log(h);//200
                        
                    

Значение по умолчанию

                        
let options = {
    title: "Меню"
};

let {width=100, height=200, title} = options;

console.log(title);//Меню
console.log(width);//100
console.log(height);//200
                        
                    

Combo: новое имя + значение по умолчанию

                        
let options = {
  title: "Меню"
};

let {width:w=100, height:h=200, title} = options;

console.log(title);//Меню
console.log(w);//100
console.log(h);//200
                        
                    

Отделение объявления от присваивания

                        
var width, height;
({width, height} = {width: 100,height: 200});

console.log(width);//100
console.log(height);//200
                        
                    

Использование конкретного имени

                        
let key = "title";
let { [key]: t } = {title: "Меню"};

console.log(t);//Меню
                        
                    

Вложенные деструктуризации

                    
let options = {
    size: {
        width: 100,
        height: 200
    },
    items: ["Пончик", "Пирожное"]
};

let { title="Меню", 
      size: {width, height}, 
      items: [item1, item2] 
    } = options;

//Меню 100 200 Пончик Пирожное
console.log(title);//Меню
console.log(width);//100
console.log(height);//200
console.log(item1);//Пончик
console.log(item2);//Пирожное
                    
                

Функции

Параметры по умолчанию

                        
function [name]([param1[= defaultValue1][, ..., paramN[= dValueN]]])
{
    //function's body
}
                        
                    

Простой пример

                        
function multiply(a, b = 1) {
    return a*b;
}

console.log(multiply(5));//5
                        
                    

Пример с undefined

Даже при явном указании 'undefined' параметру присваивается значение по умолчанию
                        
function multiply(a, b = 1) {
    return a*b;
}

multiply(5, undefined);//5
                        
                    

Вычисление во время вызова

                        
function append(value, array = []) {
    array.push(value);
    return array;
}

append(1);//[1]
append(2);//[2]
                        
                    

Пример с функцией

                        
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

function print(number = getRandomInt(10, 100)){
    console.log(number);
}

print();//29
                        
                    

Оператор spread

В отличие от arguments, rest - это массив, а значит, можно использовать методы map, forEach и т.д.
                        
function printName(firstName, lastName, ...rest) {
    console.log(firstName + ' ' + lastName + ' - ' + rest);
    console.log(rest);
}

printName('Иван', 'Иванов', 'Иванович', '1959');
//Иван Иванов - Иванович,1959
//["Иванович", "1959"]
                        
                    

Свойство name

                        
function getRandomInt(min, max) 
{ 
    return Math.floor(Math.random() * (max - min)) + min; 
} 

console.log(getRandomInt.name);//getRandomInt
getRandomInt.name = 'f'; 

console.log(getRandomInt.name);//getRandomInt 
console.log(f.name);//ReferenceError: f is not defined
                        
                    

Блочная модель видимости

                        
var something = false;
if(something){
    console.log(getRandomInt(0, 10));
    function getRandomInt(min, max) {
       return Math.floor(Math.random() * (max - min)) + min;
    }
}

console.log(getRandomInt(0, 10));
//Uncaught TypeError: getRandomInt is not a function
                        
                    
                        
var something = true;
if(something){
    console.log(getRandomInt(0, 10));//2
    function getRandomInt(min, max) {
       return Math.floor(Math.random() * (max - min)) + min;
    }
}

console.log(getRandomInt(0, 10));//9
                        
                    

Стрелочные функции

Выражения имеют более короткий синтаксис, всегда анонимные и лексически привязанные к значению this
                        
(param1, param2, paramN) => expression
                        
                    

Особенности

  • Лексическое связывание. Значения this, super и arguments определяются не тем, как стрелочные функции были вызваны, а тем, как они были созданы
  • Неизменяемые this, super и arguments. Значения этих переменных внутри стрелочных функций остаются неизменными на протяжении всего жизненного цикла функции
  • Стрелочные функции не могут быть использованы как конструктор
  • Недоступность «собственного» значения переменной arguments, this ...

Короткая запись

                        
var faculties = [
  "ИТ",
  "ИЭФ",
  "ХТИТ"
];

var a = faculties.map(function(s){ return s.length });//[2,3,4]
var b = faculties.map( s => s.length);//[2,3,4]
                        
                    

Еще пример

                        
let square = x => x*x;
console.log(square(3));//9
                        
                    
                        
let sum = (x, y) => x + y;
console.log(sum(3,4));//7
                        
                    
                        
let getObject = () => ({ brand: 'BMW' });
console.log(getObject());//Object {brand: "BMW"}
                        
                    
                        
let func = () => 77;
console.log(func());//77
                        
                    

Отсутствие this

                    
var handler = {
    id: "123456",
    init: function() {
        document.addEventListener("click", ev => this.doSomething(ev.type));
        //this указывает на внешний контекст
    },
    doSomething: function(type) { 
        console.log("Handling " + type  + " for " + this.id);
    }
};
                        
                    

Отсутствие arguments

                        
function foo() {
    var f = (i) => arguments[0]+i;
    //возмет arguments из функции foo
    console.log(f(2));
}

foo(1);//3
                        
                    

Отсутствие запуска с new

                        
var a = new (function() {}); 
//переменной a будет присвоено значение экземпляра анонимной функции

var b = new (() => {}); 
//TypeError: (intermediate value) is not a constructor
                        
                    

Строки

Шаблонные строки

Выглядят как обычные строки, за исключением того, что обернуты символами обратных кавычек `
                        
`строка текста`
`строка текста 1
 строка текста 2`
`строка текста ${выражение} строка текста`
tag `строка текста ${выражение} строка текста`
                        
                    

Многострочные литералы

                        
console.log(`string text line 1
string text line 2`);
//"string text line 1
// string text line 2"
                        
                    

Интерполяция выражений

                        
let arg1 = 2;
let arg2 = 3;

console.log(`${arg1} + ${arg2} = ${arg1 + arg2}`);//2 + 3 = 5
                        
                    

Еще пример

                        
function authorize(user, action) {
    if (!user.hasPrivilege(action)) {
        throw new Error(
        `Пользователю ${user.name} не разрешено ${action}.`);
    }
}
                        
                    
                        
const up = (str) => str.toUpperCase();
let str = `this is ${ up('string') } in uppercase`;

console.log(str);//this is STRING in uppercase
                        
                    

Вложенная интерполяция

                        
const up = (str) => str.toUpperCase();
let user = 'user';
let str = `these ${up(`${user}s`)} are great`;
console.log(str);//these USERS are great
                        
                    

Улучшенная поддержка юникода

Используется кодировка UTF-16. На хранение одного символа необходимо 2 байта
                        
console.log( '我'.length );//1
console.log( '𩷶'.length );//2

//расширенная поддержка метасимволов, математических символов 
//и смайликов
console.log( '𝒳'.length );//2, MATHEMATICAL SCRIPT CAPITAL X
console.log( '😂'.length );//2, FACE WITH TEARS OF JOY
                        
                    

Новые методы

  • str.includes(s)
  • str.endsWith(s)
  • str.startsWith(s)
  • str.repeat(count)

Метод includes

Проверяет, включает ли одна строка str в себя другую строку searchString, возвращает true/false
                        
str.includes(searchString[, position])
                        
                    
                        
var str = 'To be, or not to be, that is the question.';
console.log(str.includes('To be'));       //true
console.log(str.includes('question'));    //true
console.log(str.includes('nonexistent')); //false
console.log(str.includes('To be', 1));    //false
console.log(str.includes('TO BE'));       //false
                        
                    

Метод endsWith

Возвращает true, если строка str заканчивается подстрокой searchString
                        
str.endsWith(searchString[, position])
                        
                    
                        
var str = 'To be, or not to be, that is the question.';

console.log(str.endsWith('question.'));//true
console.log(str.endsWith('to be'));    //false
console.log(str.endsWith('to be', 19));//true
                        
                    

Метод startsWith

Возвращает true, если строка str начинается со строки searchString
                        
str.startsWith(searchString[, position])
                        
                    
                        
var str = 'To be, or not to be, that is the question.';

console.log(str.startsWith('To be'));        //true
console.log(str.startsWith('not to be'));    //false
console.log(str.startsWith('not to be', 10));//true
                        
                    

Метод repeat

Повторяет строку str count раз
                        
str.repeat(count)
                        
                    
                        
'abc'.repeat(-1);   //RangeError
'abc'.repeat(0);    //''
'abc'.repeat(1);    //'abc'
'abc'.repeat(2);    //'abcabc'
'abc'.repeat(3.5);  //'abcabcabc' (count will be converted to integer)
'abc'.repeat(1/0);  //RangeError
                        
                    

Функции шаблонизации

Процесс использования своей функции шаблонизации для строк
                        
let str = func`моя строка`;
                        
                    

Пример

                        
function f(strings, ...values) {
    console.log(JSON.stringify(strings));//["Sum of "," + "," =\n ","!"]
    console.log(JSON.stringify(strings.raw));//["Sum of "," + "," =\\n ","!"]
    console.log(JSON.stringify(values));//[3,5,8]
}

let arg1 = 3;
let arg2 = 5;
let str = f`Sum of ${arg1} + ${arg2} =\n ${arg1 + arg2}!`;
                        
                    

Необрабатываемые строки

Статический метод Raw является теговой функцией для шаблонных строк и используется для получения необработанной строки из шаблона
                        
String.raw(callSite, ...substitutions)
String.raw`templateString`
                        
                    
                        
String.raw`Привет\n Ответ = ${2+3}!`;
//"Привет\n Ответ = 5!"

//Экранирующие символы не имеют особого значения и
//обратные слеши будут присутствовать в выходной строке.
//Можно убедиться в этом, проверив свойство .length
                        
                    
                        
function tag(strings, ...values) {
  return strings.raw[0];
}

tag`string text line 1 \n string text line 2`;
//"string text line 1 \n string text line 2"
//'\' и 'n' - это два символа
                        
                    

Symbol

Тип данных Symbol

Уникальный и неизменяемый тип данных, который может быть использован как идентификатор для свойств объектов
Символьный объект — это объект-обертка для примитивного символьного типа
                        
Symbol([описание])
//создается новый символ, 
//значение которого не равно любому другому объекту
                        
                    

Методы

Symbol.for(key) ищет существующие символы по заданному ключу и возвращает его, если он найден. В противном случае создается новый символ в глобальном реестре символов
Symbol.keyFor(sym) получает по разделяемому символу его ключ из глобального реестра символов

Пример 1

                        
var sym1 = Symbol();
var sym2 = Symbol("foo");
var sym3 = Symbol("foo");
                        
                    
                        
var sym = Symbol("foo");
console.log(typeof sym);//symbol

var symObj = Object(sym);
console.log(typeof symObj);//object

console.log(Symbol("name") == Symbol("name"));//false
                        
                    

Пример 2

                        
let isAdmin = Symbol("isAdmin");
let user = {
  name: "Вася",
  [isAdmin]: true
};

console.log(user[isAdmin]);//true
                        
                    

Пример 3

Свойство, объявленное через символ, не будет видно в for-in, Object.keys, Object.getOwnPropertyNames, также не будет добавлено при использовании JSON.stringify
                        
let user = {
  name: "Вася",
  age: 30,
  [Symbol.for("isAdmin")]: true
};

//в цикле for..in не будет символа
console.log(Object.keys(user));//["name", "age"]

//доступ к свойству через глобальный символ — работает
console.log(user[Symbol.for("isAdmin")]);//true
                        
                    

Использование для private-членов

                        
var role = Symbol();
var user = {
    id: 1001,
    name: 'Administrator',
    [role]: 'admin'
};

var userRole = user[role];//admin - работает
var userRole1 = user.role;//undefined - не работает
var userRole2 = user['role'];//undefined - не работает
                        
                    

Глобальные символы

Глобальный реестр символов позволяет иметь общие глобальные символы, которые можно получить из реестра по имени. Используется метод for
                        
//создание символа в реестре
let name = Symbol.for("name");

//символ уже есть, чтение из реестра
console.log(Symbol.for("name") == name);//true
                        
                    

Встроенные символы

Symbol.iterator возвращающий итератор для объекта
Symbol.match сопоставление объекта со строкой (String.prototype.match)
Symbol.replace заменяет совпавшие подстроки в строке (String.prototype.replace)
Symbol.search возвращает индекс вхождения подстроки, соответствующей регулярному выражению (String.prototype.search)
Symbol.split разбивает строку на части в местах, соответствующих регулярному выражению (String.prototype.split)
Symbol.for(key) ищет существующие символы по заданному ключу и возвращает его, если он найден
Symbol.species определяет конструктор для порожденных объектов

Symbol.iterator

                        
var str = "Hello";

let iterator = str[Symbol.iterator]();

while(true) {
  let result = iterator.next();
  if (result.done) break;
  console.log(result.value);//H e l l o
}
                        
                    
                        
var iterableType = {}
iterableType[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

console.log([...iterableType]);//[1, 2, 3]
                        
                    

Symbol.match

                        
"/bar/".startsWith(/bar/);
//Uncaught TypeError: First argument to String.prototype.startsWith 
//must not be a regular expression
                        
                    
                        
var reg = /foo/;
reg[Symbol.match] = false;

console.log("/foo/".startsWith(reg));//true
console.log("/baz/".endsWith(reg));//false
                        
                    

Symbol.species

                        
class Foo extends Array {
    static get [Symbol.species]() {
        return this;
    }
}

class Bar extends Array {
    static get [Symbol.species]() {
        return Array;
    }
}

//new Foo().map(function(){} -> Foo []
console.log(new Foo().map(function(){}) instanceof Foo);//true
console.log(new Bar().map(function(){}) instanceof Bar);//false
console.log(new Bar().map(function(){}) instanceof Array);//true
                        
                    

Объекты и прототипы

Короткое свойство

При объявлении свойства объекта достаточно указать только его имя, а значение будет взято из переменной с аналогичным именем
                        
let name = "Вася";
let isAdmin = true;

let user = {
  name,
  isAdmin
};

console.log(JSON.stringify(user));//{"name": "Вася", "isAdmin": true}
                        
                    

Вычисляемые свойства

                        
let propName = "firstName";
let user = {
    [propName]: "Вася"
};

console.log(user.firstName);//Вася
                        
                    

Пример с функцией

                        
let a = "Зелёный ";
let b = "Крокодил";
let user = {
    [(a + b).toLowerCase()]: "Петя"
};

console.log( user["зелёный крокодил"] );//Петя
                        
                    

Метод setPrototypeOf

Метод устанавливает прототип (внутреннее свойство [[Prototype]]) указанного объекта в другой объект или null
                        
Object.setPrototypeOf(obj, prototype);
                        
                    
                        
var dict = Object.setPrototypeOf({}, null);
                        
                    
                        
var person = {
    name: 'unknown'
};

var student = {
    group: 1
};

var p1 = Object.setPrototypeOf(student, person);
console.log(p1.group);//1
console.log(p1.name);//unknown
                        
                    

Object.assign

Метод используется для копирования значений всех собственных перечисляемых свойств из одного или более объектов в целевой объект
                        
Object.assign(target, src1, src2...)
                        
                    
                        
var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj);//{ a: 1, b: 2, c: 3 }
console.log(o1); //{ a: 1, b: 2, c: 3 }
//изменился и целевой объект
                            
                        

Object.is

Метод определяет, являются ли два значения одинаковыми
                        
var isSame = Object.is(value1, value2);
                        
                    
                        
Object.is('foo', 'foo');   //true
Object.is(window, window); //true
Object.is('foo', 'bar');   //false
Object.is([], []);         //false

var test = { a: 1 };
Object.is(test, test);     //true
Object.is(null, null);     //true

//Специальные случаи
Object.is(0, -0);          //false
Object.is(-0, -0);         //true
Object.is(NaN, 0/0);       //true
Object.is(NaN, NaN);       //true
                        
                    

Объявление метода

Более короткий и удобный синтаксис

                        
let name = "Вася";
let user = {
    name,
    //something: function()
    //{
    //console.log(this.name);
    //}
    something() {
        console.log(this.name);
    }
};

user.something();//Вася
                        
                    

Классы

Объявление класса

                        
class Название [extends Родитель]  {
    constructor
    методы
}
                        
                    
                        
class Polygon {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
}
                        
                    

Выражение класса

                        
//безымянный
var Polygon = class {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
};

//именованный
var Polygon = class Polygon {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
};
                        
                    

Создание объекта и прототип

Constructor запускается при создании new Object, остальные методы записываются в Object.prototype
                        
class User {
    constructor(name) {
        this.name = name;
    }
    sayHi() { console.log(this.name);}
}

let user = new User("Вася");
                        
                    
                        
function User(name) {
    this.name = name;
}

User.prototype.sayHi = function() {
    console.log(this.name);
};
                        
                    

Подъем (hoisting)

Разница между объявлением функции (function declaration) и объявлением класса (class declaration) в том, что объявление функции совершает подъём (hoisted), в то время как объявление класса — нет
                        
var p = new Polygon();
class Polygon {}
//Uncaught ReferenceError: Polygon is not defined
                        
                    

Статические методы

                        
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    static distance(a, b) {
        const dx = a.x - b.x;
        const dy = a.y - b.y;
        return Math.sqrt(dx*dx + dy*dy);
    }
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
console.log(Point.distance(p1, p2));//7.07....
                        
                    

Геттеры, сеттеры

                        
class User {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    get fullName() {
        return `${this.firstName} ${this.lastName}`;
    }
    
    set fullName(newValue) {
        [this.firstName, this.lastName] = newValue.split(' ');
    }
};

var user = new User('Maksim', 'Hladki');
console.log(user.fullName);//Maksim Hladki
user.fullName = "Ivan Ivanov";
console.log(user.fullName);//Ivan Ivanov
                        
                    

Пример

                        
class Rectangle {
    constructor (width, height) {
        this._width  = width
        this._height = height
    }
    set width  (width)  { this._width = width}
    get width  ()       { return this._width}
    set height (height) { this._height = height}
    get height ()       { return this._height}
    get area   ()       { return this._width * this._height }
}

var test = new Rectangle(50, 20);
console.log(test.area);//1000
                        
                    

Вычисляемые имена методов

                        
class Foo() {
    myMethod() {}
}
                        
                    
                        
class Foo() {
    ['my'+'Method']() {}
}
                        
                    
                        
const m = 'myMethod';
class Foo() {
    [m]() {}
}
                        
                    

Наследование

Только один конструктор, прототип, базовый класс!
                        
class Child extends Parent {
    //TODO logic
}
                        
                    

Пример

                        
class Point {
    constructor(x, y) {
        this.x = x; this.y = y;
    }
    toString() {
        return '(' + this.x + ', ' + this.y + ')';
    }
}

class ColorPoint extends Point {
    constructor(color) {
        super(0, 0);
        this.color = color;
    }
    toString() {
        return super.toString() + ' in ' + this.color;
    }
}
var cPoint = new ColorPoint('red');
console.log(cPoint.toString());//(0, 0) in red
                        
                    

Наследование статических методов

                        
class Foo {
    static classMethod() {
        return 'hello';
    }
}

class Bar extends Foo {
    //TODO
}

console.log(Bar.classMethod());//hello
                        
                    

Super

Используется для вызова функций, принадлежащих родителю объекта
                        
super([arguments]);//вызов родительского конструктора 
super.functionOnParent([arguments]);
                        
                    

Вызов конструктора

                        
class Polygon {
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
}

class Square extends Polygon {
    constructor(length) {
        super(length, length);
    }
    get area() {
        return this.height * this.width;
    }
}
                        
                    

Вызов метода

                        
class Foo {
    static classMethod() {
        return 'hello';
    }
}

class Bar extends Foo {
    static classMethod() {
        return super.classMethod() + ', too';
    }
}

Bar.classMethod();//hello, too
                        
                    

Mixins

Абстрактные подклассы (mix-ins) — это шаблоны для классов. У класса может быть только один родительский класс, поэтому множественное наследование невозможно. Функциональность должен предоставлять родительский класс
                        
class B extends A, M {}//Uncaught SyntaxError: Unexpected token ,
//множественного наследования нет

const mixin = base => class extends base {
    /* свойства и методы */
}
                        
                    

Пример 1

                        
class Person { ··· }

const Storage = Sup => class extends Sup {
    save(database) { ··· }
};

const Validation = Sup => class extends Sup {
    validate(schema) { ··· }
};

class Employee extends Storage(Validation(Person)) { ··· }
                        
                    

Пример 2

                        
let MyMixin = (superclass) => class extends superclass {  
    test() {
        console.log('test from MyMixin');
    }
};

class MyClass extends MyMixin(MyBaseClass) {  
    /* ... */
}

let c = new MyClass();  
c.test();//test from MyMixin
                        
                    

Итераторы

Всемогущественный for...of

Выполняет цикл обхода итерируемых объектов, вызывая на каждом шаге итерации операторы для каждого значения из различных свойств объекта
                        
for (переменная of объект)
  оператор
                        
                    
  • При итерациях используется значение, а не ключ
  • Можно использовать continue и break
  • Используется с Array, Map, Set, Object и т.д.

Пример

                        
let arr = [ 3, 5, 7 ];
arr.foo = "hello";

for (let i in arr) {
   console.log(i);//"0", "1", "2", "foo"
}

for (let i of arr) {
   console.log(i);//"3", "5", "7"
}
                        
                    

Различные типы данных

                        
//Строки
let result = '';
for (let chr of 'this is string') {
  result += chr.trim();
}
console.log(result);//thisisstring

//Коллекции DOM элементов
let result = [];
for (let link of document.links) {
  result.push(link.href);
}
console.log(result);
//['http://google.com', 'https://yandex.by/', ...]
                        
                    

Понятие итератора

Объект является итератором, если он умеет обращаться к элементам коллекции по одному за раз, при этом отслеживая свое текущее положение внутри этой последовательности
В JS итератор - это объект, который предоставляет метод next(), возвращающий следующий элемент последовательности. Этот метод возвращает объект с двумя свойствами: done и value

Спецификация

                        
interface Iterable {
    [Symbol.iterator]() : Iterator;
}

interface Iterator {
    next() : IteratorResult;
}

interface IteratorResult {
    value: any;
    done: boolean;
}
                        
                    

Итерируемые значения

  • Arrays
  • Strings
  • Maps
  • Sets
  • DOM data structures

Сфера применения

Пример

                        
function makeIterator(array){
    var nextIndex = 0;
    
    return {
       next: function(){
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    }
}

var it = makeIterator(['hello', 'world']);
var first = it.next();
console.log(first.value);//'hello'
console.log(first.done);//false
var second = it.next();
console.log(second.value);//'world'
console.log(second.done);//false
console.log(it.next().done);//true
                        
                    

Добавление к существующему объекту

                        
let range = {
  from: 1, to: 5
};

range[Symbol.iterator] = function() {
    let current = this.from;
    let last = this.to;
    return {
        next() {
            if (current <= last) {
                return {done: false, value: current++};
            } else {
                return {done: true};
            }
        }
    }
};
for (let num of range) 
    console.log(num)
//1 2 3 4 5
                        
                    

Бесконечный итератор

                        
function infinity(){
    var index = 0;
    
    return {
       next: function(){
           return {
               value: index++, 
               done: false
            };
       }
    }
}

var it = infinity();
console.log(it.next().value);//0
console.log(it.next().value);//1
console.log(it.next().value);//2
//.....
                        
                    

Встроенный итератор

                        
let str = "Hello";
let iterator = str[Symbol.iterator]();

while(true) {
  let result = iterator.next();
  if (result.done) break;
  console.log(result.value);//'H' 'e' 'l' 'l' 'o'
}
                        
                    

Еще пример

                        
var someString = "hi";
var iterator = someString[Symbol.iterator]();
console.log(iterator + "");    //"[object String Iterator]"
 
console.log(iterator.next());  //{ value: "h", done: false }
console.log(iterator.next());  //{ value: "i", done: false }
console.log(iterator.next());  //{ value: undefined, done: true }
                        
                    

Генераторы

Понятие генератора

Это функции с возможностью повторного входа после выхода. Их контекст исполнения (значения переменных) сохраняется при последующих входах
                        
function* gen(i){
    while(true){
        yield i++;
        yield* anotherGenerator(i);
    }
}
                        
                    

Алгоритм работы

  1. При вызове функции-генератора (тело исполняется не сразу) возвращается объект-итератор
  2. При вызове метода next() итератора, тело генератора исполняется до первого встреченного оператора yield
  3. Оператор yield определяет возвращаемое значение (либо делегирует возврат другому генератору при помощи yield*)
  4. Метод next() возвращает объект со свойством value (содержащим значение) и свойством done (указывает, что отдано последнее значение)

Простой пример

                        
function* idMaker(){
    var index = 0;
    while(true)
        yield index++;
}

var gen = idMaker();
console.log(gen.next().value);//0
console.log(gen.next().value);//1
console.log(gen.next().value);//2
//...
                        
                    

Развернутый пример

                        
function* generateSequence() {
    yield 1;
    yield 2;
    return 3;
}
                        
                    

Шаг 1: замороженный вызов функции

                        
//создаeт generator
let generator = generateSequence();
                        
                    

Шаг 2: yield 1

                        
let one = generator.next();
console.log(JSON.stringify(one));//{value: 1, done: false}
                        
                    

Шаг 3: yield 2

                        
let two = generator.next();
console.log(JSON.stringify(two));//{value: 2, done: false}
                        
                    

Шаг 4: return 3

                        
let three = generator.next();
console.log(JSON.stringify(three));//{value: 3, done: true}
                        
                    
Новые вызовы generator.next() не имеют смысла. Они будут возвращать один и тот же объект

Генератор – итератор

                        
function* generateSequence() {
    yield 1;
    yield 2;
    return 3;
};

let generator = generateSequence();
for(let value of generator) {
    console.log(value);//1, затем 2
}

//Cтандартный перебор итератора 
//игнорирует value на последнем значении,
//при done: true
                        
                    

Более сложный пример

                        
function *foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var it = foo( 5 );
console.log(it.next());     //{ value:6, done:false }
console.log(it.next(12));   //{ value:8, done:false }
console.log(it.next(13));   //{ value:42, done:true }
                        
                    

Пример с числами Фибоначчи

                        
let fibonacci = {
    *[Symbol.iterator]() {
        let pre = 0, cur = 1;
        for (;;) {
            [ pre, cur ] = [ cur, pre + cur ];
            yield cur;
        }
    }
}

for (let n of fibonacci) {
    if (n > 100)
        break
    console.log(n)
}
//1 2 3 5 8 13 21 34 55 89
                        
                    

Композиция генераторов

                    
function* generateSequence(start, end) {
    for (let i = start; i <= end; i++) yield i;
}

function* generateAlphaNum() {
    //0..9
    yield* generateSequence(48, 57);
    //A..Z
    yield* generateSequence(65, 90);
    //a..z
    yield* generateSequence(97, 122);
}

let str = '';
for(let code of generateAlphaNum()) {
  str += String.fromCharCode(code);
}
console.log(str);
//0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
                        
                    

Использование throw

Внешний код может вернуть генератору в качестве результата yield любое значение, в том числе и ошибку
                        
function* gen() {
    try {
        let result = yield "Сколько будет 2 + 2?";//(2)
    } catch(e) {
        console.log(e);//fatal error
    }
}

let generator = gen();
let question = generator.next().value;
console.log(question);//Сколько будет 2 + 2?
generator.throw(new Error("fatal error"));//(1)
//Error: fatal error
                        
                    

Коллекции Set, Map, WeakSet и WeakMap

Set

Коллекция для хранения множества значений, причём каждое значение может встречаться лишь один раз
                        
new Set([iterable]);
                        
                    

Основные свойства и методы

add(item) добавляет в коллекцию item, возвращает set
delete(item) удаляет item из коллекции, возвращает true, если он там был
has(item) возвращает true, если item есть в коллекции
clear() очищает set
keys()/values() возвращает объект-итератор на все значения множества
size возвращает количество элементов

Пример

                        
var mySet = new Set();
mySet.add(1);
mySet.add(5);
mySet.add("some text");

mySet.has(1);//true
mySet.has(3);//false
mySet.has(Math.sqrt(25));  //true
mySet.has("Some Text".toLowerCase());//true

mySet.size;//3
mySet.delete(5);//удаляет 5 из set
mySet.has(5);//false, 5 было удалено
mySet.size;//2, было удалено одно значение
                        
                    

Обход множества

                        
var mySet = new Set();
mySet.add(1); mySet.add("some text");

for (let item of mySet) console.log(item);
//1, "some text" 
for (let item of mySet.keys()) console.log(item);
//1, "some text" 
for (let item of mySet.values()) console.log(item);
//1, "some text"

//преобразует set в Array
var myArr = [...mySet];//[1, "some text"]
//обратное преобразования 
var mySet2 = new Set([1,2,3,4]);

mySet2.forEach(function(value) {
  console.log(value);
});
//1 2 3 4
                        
                    

Map

Коллекция для хранения записей вида ключ-значение
                        
new Map([iterable])
                        
                    

Основные свойства и методы

size возвращает общее количество записей
delete(key) удаляет запись с ключом key, возвращает true, если такая запись была
clear() очищает словарь
has(key) возвращает true, если ключ есть в словаре
keys() возвращает итерируемый объект для ключей
values() возвращает итерируемый объект для значений
get(key) возвращает значение элемента по ключу
set(key, value) устанавливает новое значение для ключа

Пример

                        
var myMap = new Map();
var keyObj = {},
    keyFunc = function () {},
    keyString = "a string";

myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, "value associated with keyObj");
myMap.set(keyFunc, "value associated with keyFunc");
myMap.size;//3

myMap.get(keyString);   //"value associated with 'a string'"
myMap.get(keyObj);      //"value associated with keyObj"
myMap.get(keyFunc);     //"value associated with keyFunc"
myMap.get("a string");  //"value associated with 'a string'"
                        //т.к. keyString === 'a string'

myMap.get({});          //undefined, т.к.keyObj !== {}
myMap.get(function(){});//undefined, т.к. keyFunc !== function (){}
                        
                    

Использование NaN в качестве ключей

                        
var myMap = new Map();
myMap.set(NaN, "not a number");
myMap.get(NaN);//"not a number"

var otherNaN = Number("foo");
myMap.get(otherNaN);//"not a number"
                        
                    

Обход словаря

                        
var myMap = new Map();
myMap.set(0, "zero"); myMap.set(1, "one");

for (var [key, value] of myMap) {
  console.log(key + " = " + value);
}//"0 = zero" и "1 = one"

for (var key of myMap.keys()) {
  console.log(key);
}//"0" и "1"

for (var value of myMap.values()) {
  console.log(value);
}//"zero" и "one"

myMap.forEach(function(value, key, myMap) {
  console.log(key + " = " + value);
})//"0 = zero" и "1 = one"
                        
                    

WeakSet

Коллекция, элементами которой могут быть только объекты. Ссылки на эти объекты являются слабыми. Каждый объект может быть добавлен в WeakSet только один раз
                        
new WeakSet([iterable]);
                        
                    

Пример

                        	
var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window);//true
ws.has(foo);//false, foo не добавлен в WeakSet
ws.delete(window);//удаляет window из WeakSet
ws.has(window);//false, window был удалён

ws.add('string');//Uncaught TypeError: Invalid value used in weak set
                        
                    

WeakMap

Коллекция пар ключ-значение. В качестве ключей могут быть использованы только объекты, а значения могут быть произвольных типов
                        
new WeakMap([iterable])
                        
                    

Пример

                        
var wm1 = new WeakMap(),
    wm2 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window,
    o4 = [1, 2, 3];

wm1.set(o1, 37);
wm1.set(o2, "azerty");
wm2.set(o1, o2);//значением может быть что угодно,
//включая объект или функцию
wm2.set(o3, undefined);
wm2.set(wm1, wm2);//ключами и значениями могут быть другие WeakMap

wm1.get(o2);//"azerty"
wm2.get(o2);//undefined, потому что нет значения для o2 в wm2
wm2.get(o3);//undefined, потому что это установленное значение
wm1.has(o2);//true
wm2.has(o2);//false
wm2.has(o3);//true (даже если значение равно 'undefined')
                        
                    

Прокси (Proxy)

Понятие Proxy

Особый объект, который перехватывает обращения к другому объекту и, при необходимости, модифицирует их
                        
let proxy = new Proxy(target, handler)
                        
                    
  • target – объект, обращения к которому надо перехватывать
  • handler – объект с функциями-перехватчиками для операций к target

Если перехватчика нет – операция идет над target

Пример

                        
var target = {};
var superhero = new Proxy(target, {
   get: function(target, name, receiver) {
       console.log('get was called for: ', name);
       return target[name];
   }
});

superhero.power = 'Flight';
console.log(superhero.power);
//get was called for:  power
//Flight
                        
                    

Виды перехватчиков

getPrototypeOf перехватывает обращение к методу getPrototypeOf
setPrototypeOf перехватывает обращение к методу setPrototypeOf
isExtensible перехватывает обращение к методу isExtensible
preventExtensions перехватывает обращение к методу preventExtensions
getOwnPropertyDescriptor перехватывает обращение к методу getOwnPropertyDescriptor
defineProperty перехватывает обращение к методу defineProperty
has перехватывает проверку существования свойства
get перехватывает чтение свойства
set перехватывает запись свойства
deleteProperty перехватывает удаление свойства оператором delete
ownKeys перехватывает обращения к методу getOwnPropertyNames
apply перехватывает вызовы target()
construct перехватывает вызовы new target()

Перехватчики get/set

                        
get(target, property, receiver)
                        
                    
  • target – целевой объект
  • property – имя свойства
  • receiver – объект, к которому было применено присваивание. Обычно сам прокси, либо прототипно наследующий от него
                        
set(target, property, value, receiver)
                        
                    
  • target – целевой объект
  • property – имя свойства
  • value – значение свойства
  • receiver – объект, к которому было применено присваивание

Пример get/set перехватчика

                    
let user = {};

let proxy = new Proxy(user, {
    get(target, prop) {
        console.log(`Чтение ${prop}`);
        return target[prop];
    },

    set(target, prop, value) {
        console.log(`Запись ${prop} ${value}`);
        target[prop] = value;
        return true;
    }
});

proxy.firstName = "Maks";//Запись firstName Maks
proxy.firstName;//Чтение firstName
console.log(user.firstName);//Maks
                        
                    

Перехватчик has

                        
let dictionary = {
  'Hello': 'Привет'
};

dictionary = new Proxy(dictionary, {
    has(target, phrase) {
        //target - исходный объект
        //phrase - имя проверяемого свойства
        return true;
    }
});

console.log("Any text" in dictionary);//true	
                        
                    

Перехватчик deleteProperty

                    
let dictionary = {
    'Hello': 'Привет'
};

let proxy = new Proxy(dictionary, {
    deleteProperty(target, phrase) {
        //target - исходный объект
        //phrase - имя удаляемого свойства
        return true; 
    }
});

//не удалит свойство
delete proxy['Hello'];
console.log("Hello" in dictionary);//true
//так как нет ловушки has, операция in сработает на исходном объекте
console.log("Hello" in proxy);//true
                        
                    

Перехватчик ownKeys

                        
var target = {};

var proxy = new Proxy({}, {
    ownKeys: function(target) {
        console.log('ownKeys');
        return ['a', 'b', 'c'];
    }
});

console.log(Object.getOwnPropertyNames(proxy));//"ownKeys"
//["a", "b", "c"]
                        
                    

Перехватчик apply

                        
function sum(a, b) {
    return a + b;
}

let proxy = new Proxy(sum, {
    //передаст вызов в target, предварительно сообщив о нём
    apply: function(target, thisArg, argumentsList) {
        //target – исходный объект
        //thisArgument – контекст this вызова
        //argumentsList – аргументы вызова в виде массива
        console.log(`Сумма: ${argumentsList}`);
        return target.apply(thisArg, argumentsList);
    }
});

console.log(proxy(1, 2));
//Сумма 1,2
//3
                        
                    

Перехватчик construct

                        
function User(name, surname) {
    this.name = name;
    this.surname = surname;
}

let UserProxy = new Proxy(User, {
    construct: function(target, argumentsList, newTarget) {
        //target – исходный объект.
        //argumentsList – аргументы вызова в виде массива
        //newTarget - конструктор, который был вызван
        console.log(newTarget);
        console.log(`Запуск new с аргументами: ${argumentsList}`);
        return new target(...argumentsList);
    }
});

let user = new UserProxy("Maksim", "Hladki");
//Запуск new с аргументами: Maksim,Hladki
                        
                    

Обещание

(Promise)

Понятие промиса

Объект, используемый как заглушка для результата некоего отложенного (и асинхронного) вычисления

Callback hell

                            
getData(function(a){  
    getMoreData(a, function(b){
        getMoreData(b, function(c){ 
            getMoreData(c, function(d){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});
                            
                        

Синтаксис

                        
new Promise(executor);
new Promise(function(resolve, reject) { ... });
                        
                    
  • onFulfilled – срабатывают, когда promise в состоянии «выполнен успешно»
  • onRejected – срабатывают, когда promise в состоянии «выполнен с ошибкой»

Алгоритм

  1. Код, которому надо сделать что-то асинхронно, создаёт объект promise и возвращает его
  2. Внешний код, получив promise, навешивает на него обработчики
  3. После завершения процесса асинхронный код переводит promise в состояние fulfilled (с результатом) или rejected (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде

Навешивание обработчиков

                        
promise.then(onFulfilled, onRejected)
//onFulfilled – функция, которая будет вызвана с результатом при resolve
//onRejected – функция, которая будет вызвана с ошибкой при reject
                        
                    
                        
//onFulfilled сработает при успешном выполнении
promise.then(onFulfilled)
//onRejected сработает при ошибке
promise.then(null, onRejected)
                        
                    

Метод then

Метод возвращает Promise и принимает два аргумента, callback-функции для случаев выполнения и отказа соответственно
                        
p.then(onFulfilled, onRejected);
p.then(
    function(value) {
        //выполнение
    }, 
    function(reason) {
        //отказ
    }
);
                        
                    

Метод catch

Для того, чтобы поставить обработчик только на ошибку, вместо .then(null, onRejected) можно написать .catch(onRejected)
                        
promise.catch(onRejected);
promise.catch(function(reason) {
    //отказ
});
                        
                    
catch всегда возвращает положительный промис

throw в промисах

Синхронный throw – то же самое, что reject
                        
let p = new Promise((resolve, reject) => {
    //то же что reject(new Error("Ошибка"))
    throw new Error("Ошибка");
});

p.catch(console.log);//Ошибка
                        
                    

Эмуляция асинхронности

                        
let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("result");
    }, 1000);
});

promise.then(
    result => {
        console.log("Fulfilled: " + result);
        //result - аргумент resolve
    },
    error => {
        console.log("Rejected: " + error);
        //error - аргумент reject
    }
);

//Fulfilled: result
                        
                    

Еще пример

                        
var promise = new Promise(function(resolve, reject) {
    console.log('in Promise constructor function');
    setTimeout(function() {
        console.log('in setTimeout callback');
        resolve('foo');
    }, 100);
});

console.log('created promise');
promise.then(function(result) {
    console.log('promise returned: ' + result);
});

console.log('hooked promise.then()');
//in Promise constructor function
//created promise
//hooked promise.then()
//in setTimeout callback
//promise returned: foo
                        
                    

Promise после reject/resolve – неизменны

                        
let promise = new Promise((resolve, reject) => {
  // через 1 секунду готов результат: result
  setTimeout(() => resolve("result"), 1000);

  // через 2 секунды — reject с ошибкой, он будет проигнорирован
  setTimeout(() => reject(new Error("ignored")), 2000);
});

promise.then(
    result => console.log("Fulfilled: " + result),//сработает
    error => console.log("Rejected: " + error)//не сработает
);
//Fulfilled: result
                        
                    

Промисификация

Процесс, при котором из асинхронной функции делают обертку, возвращающую promise
                        
function httpGet(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.onload = function() {
        if (this.status == 200) {
            resolve(this.response);
        } else {
            var error = new Error(this.statusText);
            error.code = this.status;
            reject(error);
        }
    };
    xhr.onerror = function() {
        reject(new Error("Network Error"));
    };
    xhr.send();
  });
}
                        
                    

Метод Promise.resolve

Метод возвращает Promise, выполненный с переданным значением. Если переданное значение является thenable (имеет метод then), возвращаемое обещание будет следовать thenable - объекту, принимая свое состояние; в ином случае возвращаемое обещание будет выполнено с переданным значением
                        
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
                            
                        

Пример 1

                        
Promise.resolve("Success").then(
    function(value) {
        console.log(value);//"Success"
    }, 
    function(value) {
        //не будет вызванна
    }
);
                            
                        

Пример 2

                        
var p = Promise.resolve([1,2,3]);
p.then(function(v) {
    console.log(v[0]);//1
});
                        
                    

Пример 3

                        
var original = Promise.resolve(true);
console.log(original);//Promise {resolved: true}

var cast = Promise.resolve(original);

cast.then(function(v) {
    console.log(v);//true
});
                        
                    

Метод Promise.reject

Метод возвращает объект Promise, который был отклонен по указанной причине
                        
Promise.reject(reason);
                        
                    

Пример

                        
Promise.reject("Проверка статического reject").then(
    function(reason) {
        //не вызывается
    }, 
    function(reason) {
        console.log(reason);//"Проверка статического reject"
    }
);
                            
                        
                        
Promise.reject(new Error("Ошибка")).then(
    function(error) {
        //не вызывается
    }, 
    function(error) {
        console.log(error);//Ошибка
    }
);
                            
                        

Метод Promise.all

Возвращает обещание, которое выполнится тогда, когда будут выполнены все обещания, переданные в виде перечисляемого аргумента, или отклонено любое из переданных обещаний
                        
Promise.all(iterable);
//iterable - перечисляемый объект, например, массив (Array)
                        
                    

Пример 1

                        
var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "foo");
}); 

Promise.all([p1, p2, p3]).then(values => { 
  console.log(values); 
});
//[3, 1337, "foo"] 
                        
                    

Пример 2

                        
var p1 = Promise.resolve(3);
var p2 = Promise.reject("Promise Reject");
var p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "foo");
}); 
    
Promise.all([p1, p2, p3]).then(
    values => console.log(values),
    error => console.log(error)
);
//Promise Reject
                        
                    

Пример 3

                    
var p1 = new Promise((resolve, reject) => { 
  setTimeout(resolve, 1000, "one"); 
});

var p2 = new Promise((resolve, reject) => { 
  setTimeout(resolve, 2000, "two"); 
});

var p3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 3000, "three");
});

var p4 = new Promise((resolve, reject) => {
  reject("reject");//Это обещание прервет Promise.all
});

Promise.all([p1, p2, p3, p4]).then(
    value => console.log(value), 
    reason => console.log(reason)
);//"reject"
                        
                    

Пример 4

                    
function fetchAsync (url, timeout, onData, onError) {
    //TODO
}

let fetchPromised = (url, timeout) => {
    return new Promise((resolve, reject) => {
        fetchAsync(url, timeout, resolve, reject)
    });
}

Promise.all([
    fetchPromised("http://test/method1", 500),
    fetchPromised("http://test/method2", 500),
    fetchPromised("http://test/method3", 500)
]).then((data) => {
    let [foo, bar, baz] = data
    console.log(`success: foo=${foo} bar=${bar} baz=${baz}`)
}, (err) => {
    console.log(`error: ${err}`)
});
                        
                    

Метод Promise.race

Метод возвращает выполненое или отклоненное обещание, в зависимости от того, с каким результатом завершится первое из переданных обещаний, со значением или причиной отклонения этого обещания
                        
Promise.race(iterable);
//iterable - итерируемый объект, такой как (Array)
                        
                    

Пример 1

                        
var p1 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "один"); 
});

var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "два"); 
});

Promise.race([p1, p2]).then(function(value) {
    console.log(value);//"два"
    //Оба вернули resolve, однако p2 вернул результат первым
});
                        
                    

Пример 2

                        
var p1 = Promise.resolve(3);
var p2 = Promise.reject("Promise Reject");
var p3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "foo");
}); 
        
Promise.race([p1, p2, p3]).then(
    values => console.log(values),
    error => console.log(error)
);
//3
                         
                    

Пример 3

                        
var p3 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "три");
});

var p4 = new Promise(function(resolve, reject) { 
    setTimeout(reject, 500, "четыре"); 
});

Promise.race([p3, p4]).then(function(value) {
    console.log(value);//"три"
    //p3 быстрее, поэтому выведется значение его resolve
}, function(reason) {
    //Не вызывается
});
                        
                    

Пример 4

                        
var p5 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "пять"); 
});

var p6 = new Promise(function(resolve, reject) { 
    setTimeout(reject, 100, "шесть");
});

Promise.race([p5, p6]).then(
    function(value) {
        //Не вызывается
    }, 
    function(reason) {
        console.log(reason);//"шесть"
        //p6 быстрее, выводится его rejects
    }
);
                        
                    

Цепочки промисов

                        
something(...)
    .then(...)
    .then(...)
    .then(...);
                        
                    
Если очередной then вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат

Пример 1

                        
Promise.resolve("Success")  
    .then(x =>  { console.log("THEN1", x);  })
    .catch(x => { console.log("CATCH1", x); })
    .then(x =>  { console.log("THEN2", x);  })
    .catch(x => { console.log("CATCH2", x); });
//THEN1 Success
//THEN2 undefined
                        
                    

Пример 2

                        
Promise.reject("Fail")  
    .then(x =>  { console.log("THEN1", x);  })
    .catch(x => { console.log("CATCH1", x); })
    .then(x =>  { console.log("THEN2", x);  })
    .catch(x => { console.log("CATCH2", x); });
//CATCH1 Fail
//THEN2 undefined
                        
                    

Пример 3

                        
Promise.reject("Fail")  
    .catch(x => {
        console.log("CATCH1", x);
        return Promise.reject("B");
    })
    .then(x =>  { console.log("THEN1", x);  })
    .catch(x => { console.log("CATCH2", x); })
    .then(x =>  { console.log("THEN2", x);  });
//CATCH1 Fail
//CATCH2 B
//THEN2 undefined
                        
                    

Пример 4

                        
//get - магическая функция получения данных
get(url)
    .then(function(response) {
        response = JSON.parse(response);
        var secondURL = response.data.url
        return get( secondURL ); //возвращаем новый промис
    })
    .then(function(response) {
        response = JSON.parse(response);
        var thirdURL = response.data.url
        return get( thirdURL ); // возвращаем новый промис
    })
    .catch(function(err) {
        handleError(err);
    });
                        
                    

Fetch API

Метод fetch: замена XMLHttpRequest
                        
let promise = fetch(url[, options]);
//url – URL, на который сделать запрос,
//options – необязательный объект с настройками запроса.
                            
                        

Свойства options

method метод запроса
headers заголовки запроса (объект)
body тело запроса: arrayBuffer, blob, formData, json или text
mode указывает, в каком режиме кросс-доменности предполагается делать запрос: same-origin, no-cors, cors
credentials указывает, пересылать ли куки и заголовки авторизации вместе с запросом: omit, same-origin, include
cache указывает, как кешировать запрос: default, no-store, reload, no-cache, force-cache, only-if-cached
redirect «follow» для обычного поведения при коде 30x (следовать редиректу) или «error» для интерпретации редиректа как ошибки

Пример 1

                        
fetch('/article/fetch/user.json')
    .then(function(response) {
        console.log(response.headers.get('Content-Type')); 
        //application/json; charset=utf-8
        console.log(response.status);//200
        return response.json();
    })
    .then(function(user) {
        console.log(user.name);//Maks
    })
    .catch(console.log);
                        
                    

Пример 2

                        
var myImage = document.querySelector('img');
fetch('flowers.jpg')
    .then(function(response) {
        return response.blob();
    })
    .then(function(myBlob) {
        var objectURL = URL.createObjectURL(myBlob);
        myImage.src = objectURL;
    });
                        
                    

Пример 3

                        
var myHeaders = new Headers();
var myInit = { 
    method: 'GET',
    headers: myHeaders,
    mode: 'cors',
    cache: 'default' 
};

fetch('flowers.jpg',myInit)
    .then(function(response) {
        return response.blob();
    })
    .then(function(myBlob) {
        var objectURL = URL.createObjectURL(myBlob);
        myImage.src = objectURL;
    });
                            
                        

Пример 4

                        
var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
    method: "POST",
    body: form
});
                        
                    

Пример 5

                        
var request = new Request("/users", {
    method: 'POST', 
    mode: 'cors', 
    headers: new Headers({
        'Content-Type': 'text/plain'
    }),
    body: data
});

fetch(request).then(function() { 
    //обработка ответа
});
                        
                    

Модули

(modules)

Модули в ES5

CommonJS (CJS)

  • компактный синтаксис
  • предназначен для синхронной загрузки
  • преимущественно используется на стороне сервера

Asynchronous Module Definition (AMD)

  • синтаксис немного сложнее, что позволяет AMD работать без eval() или этапа компиляции
  • предназначен для асинхронной загрузки
  • преимущественно используется на стороне клиента

Модули в ES6

Модулем считается файл с кодом. В этом файле ключевым словом export помечаются переменные и функции, которые могут быть использованы снаружи. Другие модули могут подключать их через вызов import

Целью модулей es6 было создание формата, удобного как для пользователей CJS, так и для пользователей AMD. В связи с этим они имеют такой же компактный синтаксис, как и модули CJS. С другой стороны, они не такие динамичные как и AMD

Особенности

  • на этапе компиляции возникнет ошибка, если попытаться импортировать что-то, что не было предварительно экспортировано
  • можно легко осуществить асинхронную загрузку модулей
  • декларативный синтаксис (для импорта и экспорта)
  • программная загрузка (чтобы задать конфигурацию загрузки модулей и для условной загрузки модулей)

Пример

При запуске в chrome видем ошибку Uncaught SyntaxError: Unexpected token import. Слабая поддержка браузеров. Необходимо использовать babel.js либо аналогичный инструмент

                        
lib/math.js
index.js
other.js
                        
                    
                        
// lib/math.js
export function sum (x, y) { return x + y; }
export var pi = 3.141593;

// index.js
import * as math from "lib/math";
console.log("2π = " + math.sum(math.pi, math.pi));

// other.js
import { sum, pi } from "lib/math";
console.log("2π = " + sum(pi, pi));
                        
                    

Export

                        
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };

export let name1, name2, …, nameN;//или var, const
export let name1 = …, name2 = …, …, nameN;//или var, const

export default выражение;
export default function (…) { … } //или class, function*
export default function name1(…) { … } //или class, function*

export { name1 as default, … };
export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;
                        
                    

Export переменных

  • возможно перед объявлением переменных, функций и классов
  • возможно отдельно, при этом в фигурных скобках указывается то, что именно экспортируется
                        
export let one = 1;
                        
                    
                        
let two = 2;
export {two};
                        
                    
                        
export {one, two};
                        
                    

Ключевое слово AS

При AS можно указать, что переменная one будет доступна снаружи (экспортирована) под другим именем
                        
export {one as once, two as twice};
                        
                    

Export функций и классов

                        
export class User {
    constructor(name) {
        this.name = name;
    }
};

export function sayHi() {
    console.log("Hello!");
};
//export {User, sayHi}
                        
                    

Экспорт по умолчанию

Иногда модуль экспортирует только одно значение (класс). В таком случае удобно определить это значение как экспортируемое по умолчанию
                        
//user.js
export default class User {
    constructor(name) {
        this.name = name;
    }
};

//login.js:
import User from './user';
new User("Петя");
                        
                    

Import

Другие модули могут подключать экспортированные значения при помощи ключевого слова import
                        
import defaultMember from "module-name"; 
import * as name from "module-name"; 

import { member } from "module-name"; 
import { member as alias } from "module-name"; 
import { member1 , member2 } from "module-name"; 
import { member1 , member2 as alias2 , [...] } from "module-name";

import defaultMember, { member [ , [...] ] } from "module-name"; 
import defaultMember, * as name from "module-name"; 
import "module-name";
                        
                    

Пример

                        
//nums.js:
export let one = 1;
export let two = 2;
                        
                    
                        
import {one, two} from "./nums";
console.log(`${one} and ${two}`);//1 and 2
                        
                    
                        
//использование as
//импорт one под именем item1, а two – под именем item2
import {one as item1, two as item2} from "./nums";
console.log(`${item1} and ${item2}`);//1 and 2
                        
                    

Импорт всех значений в виде объекта

                        
import * as numbers from "./nums";
//теперь экспортированные переменные - свойства numbers
console.log(`${numbers.one} and ${numbers.two}`);//1 and 2
                        
                    

Встроенные модули

ES5

                        
<script>
    (function () {  //начало самовызывающейся функции
        var tmp = 100;  //не станет глобальной
    }());  //конец самовызывающейся функции
</script>
                        
                    

ES6

                        
<script>
    module {  //анонимный внутренний модуль
        let tmp = 100;  //не станет глобальной
    }
</script>
                        
                    

Импорт модулей и загрузка скриптов

                        
System.import(['module1', 'module2'], 
   function (module1, module2) { //успешное выполнение … }, 
   function (err) { //ошибка … } );
                        
                    

Другие особенности

Поддержка bin/oct литералов

ES5

                    
parseInt("111110111", 2) === 503;
parseInt("767", 8) === 503;
0767 === 503; 
                    
                

ES6

                    
0b111110111 === 503
0o767 === 503
                    
                

Работа с бинарными данными

ArrayBuffer

Используется для работы с бинарными данными. Представляет собой ссылку на поток двоичных данных, однако работать с ними напрямую возможности не дает. Вместо этого, необходимо создать типизированный массив или объект DataView
                        
new ArrayBuffer(length)//длина буфера в байтах
                        
                    

Пример

                        
var buffer = new ArrayBuffer(12);
var dataView = new DataView(buffer);
var int8View = new Int8Array(buffer);

dataView.setInt32(0, 0x1234ABCD);

console.log(dataView.getInt32(0).toString(16));//1234abcd
console.log(dataView.getInt8(0).toString(16));//12
console.log(int8View[0].toString(16));//12
                        
                    

Типизированный массив

                        
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);

где TypedArray() это одно из следующих значений:
Int8Array(), 
Uint8Array(), 
Uint8ClampedArray(), 
Int16Array(), 
Uint16Array(), 
Int32Array(), 
Uint32Array(), 
Float32Array(), 
Float64Array()
                        
                    

Пример 1

                        
let typedArray = new Uint8Array([0,1,2]);
console.log(typedArray.length);//3
typedArray[0] = 5;

let normalArray = [...typedArray];//[5,1,2]

console.log(ArrayBuffer.isView(new Int32Array()));//true
console.log(ArrayBuffer.isView(typedArray));//true
console.log(ArrayBuffer.isView(normalArray));//false

let dataView = new DataView(typedArray.buffer);
console.log(dataView.getUint8(0));//5
                        
                    

Пример 2

                        
var buffer = new ArrayBuffer(12);

var x = new DataView(buffer, 0);
x.setInt8(0, 22);
x.setFloat32(1, Math.PI);

console.log(x.getInt8(0));  //22
console.log(x.getFloat32(1));//3.1415927410125732
                        
                    

Пример 3

                        
let ui8 = Uint8Array.of(0,1,2);
for (let byte of ui8) {
    console.log(byte);
}
                        
                    

Пример 4

                    
function ab2str(buf) {
    return String.fromCharCode.apply(null, new Uint16Array(buf));
}

function str2ab(str) {
    var buf = new ArrayBuffer(str.length*2);//2 bytes for each char
    var bufView = new Uint16Array(buf);
    for (var i=0, strLen=str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}
                        
                    

Пример 5

                        
let fileInput = document.getElementById('fileInput');
let file = fileInput.files[0];
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function () {
    let arrayBuffer = reader.result;
    //···
};
                        
                    

Пример 6

                        
let xhr = new XMLHttpRequest();
xhr.open('GET', someUrl);
xhr.responseType = 'arraybuffer';
    
xhr.onload = function () {
    let arrayBuffer = xhr.response;
    //···
};
    
xhr.send();
                        
                    

Пример 7

                        
let socket = new WebSocket('ws://127.0.0.1:8081');
socket.binaryType = 'arraybuffer';
    
socket.addEventListener('open', function (event) {
    let typedArray = new Uint8Array(4);
    socket.send(typedArray.buffer);
});
    
socket.addEventListener('message', function (event) {
    let arrayBuffer = event.data;
    //...
});
                        
                    

Новый метод: find

ES5

                        
[ 1, 3, 4, 2 ].filter(function (x) { return x > 3; })[0];//4
                        
                    

ES6

                        
[ 1, 3, 4, 2 ].find(x => x > 3);//4	
                        
                    

Новые методы: isNaN/isFinite

ES5

                        
var isNaN = function (n) {return n !== n;};
var isFinite = function (v) {
    return (typeof v === "number" 
	&& !isNaN(v) && v !== Infinity && v !== -Infinity);
};
isNaN(42);//false;
isNaN(NaN);//true;
isFinite(Infinity);//false;
                        
                    

ES6

                        
Number.isNaN(42);//false
Number.isNaN(NaN) //true
Number.isFinite(-Infinity);//false
Number.isFinite(NaN);//false
Number.isFinite(123);//true
                        
                    

Новый метод: isSafeInteger

ES5

                        
function isSafeInteger (n) {
    return (
           typeof n === 'number'
        && Math.round(n) === n
        && -(Math.pow(2, 53) - 1) <= n
        && n <= (Math.pow(2, 53) - 1)
    );
}

isSafeInteger(42) === true;
isSafeInteger(9007199254740992) === false;
                        
                    

ES6

                        
Number.isSafeInteger(42) === true
Number.isSafeInteger(9007199254740992) === false
                        
                    

Новое свойство: EPSILON

ES5

                        
console.log(0.1 + 0.2 === 0.3);//false
console.log(Math.abs((0.1 + 0.2)-0.3) < 2.220446049250313e-16);//true
                        
                    

ES6

                        
console.log(0.1 + 0.2 === 0.3);//false
console.log(Math.abs((0.1 + 0.2) - 0.3) < Number.EPSILON);//true
                        
                    

Новый метод: trunc

ES5

                        
function mathTrunc (x) {
    return (x < 0 ? Math.ceil(x) : Math.floor(x));
}

console.log(mathTrunc(42.7));//42
console.log(mathTrunc( 0.1));//0
console.log(mathTrunc(-0.1));//-0
                        
                    

ES6

                        
console.log(Math.trunc(42.7));//42
console.log(Math.trunc( 0.1));//0
console.log(Math.trunc(-0.1));//-0
                        
                    

Новый метод: sign

ES5

                        
function mathSign (x) {
    return ((x === 0 || isNaN(x)) ? x : (x > 0 ? 1 : -1));
}

console.log(mathSign(7));//1
console.log(mathSign(0));//0
console.log(mathSign(-0));//-0
console.log(mathSign(-7));//-1
console.log(mathSign(NaN));//NaN
                        
                    

ES6

                        
console.log(Math.sign(7));//1
console.log(Math.sign(0));//0
console.log(Math.sign(-0));//-0
console.log(Math.sign(-7));//-1
console.log(Math.sign(NaN));//NaN
                        
                    

Локализация

                    
var l10nEN = new Intl.NumberFormat("en-US");
var l10nDE = new Intl.NumberFormat("de-DE");
l10nEN.format(1234567.89);//1,234,567.89
l10nDE.format(1234567.89);//1.234.567,89

var l10nUSD = new Intl.NumberFormat("en-US", 
    { style: "currency", currency: "USD" });
var l10nEUR = new Intl.NumberFormat("de-DE", 
    { style: "currency", currency: "EUR" });

l10nUSD.format(100200300.40);//$100,200,300.40
l10nEUR.format(100200300.40);//100.200.300,40 €

var l10nEN = new Intl.DateTimeFormat("en-US");
var l10nDE = new Intl.DateTimeFormat("de-DE");
l10nEN.format(new Date("2015-01-02"));//1/2/2015
l10nDE.format(new Date("2015-01-02"));//2.1.2015
                    
                

Спасибо за внимание