Let, Const
- Объявление переменных через
const
иlet
Константы
нельзя переопределять- В случае с объектом нельзя переопределять саму ссылку, но можно переопределять свойства объекта
Локальные переменные
видны лишь на уровне блока{...}
- До кучи локальные переменные в циклах хранят значения для каждой итерации, а не для конечной (это было бы понятно при наличии сложного примера). Просто знайте, если из цикла возвращается только последнее значение итератора, то вам стоит попробовать использовать
let
Локальные переменные
можно переопределять, но нельзя объявлять заново- И
локальные переменные
, иконстанты
видны только после объявления
Destructuring
Деструктуризация
позволяет присваивать значения сразу нескольким переменным и брать эти значения из массива или объекта- Пример с массивом:
Переменные получат значения по порядкуlet [firstL, lastL] = ["JS", "ES"];
- Если значения нужно пропустить, то пишем пустые элементы массива (первого). Если значений справа больше, чем слева, то остальные просто будут игнорироваться. Если наоборот, то недостающие будут
undefined
- В случае, если нужно записать все не поместившиеся значения в отдельную переменную, то можно использовать оператор
... (spread)
. Пример:
Причем вlet [firstL, lastL, ...rest]
rest
будет массив лишних значений. Вполне очевидно (я надеюсь, сука, что очевидно), что спред можно и нужно писать только последним параметром, так как он собирает все параметры, что ещё не были собраны - Если хотите обезопаситься от
undefined
, то можно задать значения по умолчанию (default). Пример:
Причем в дефолтное значение можно запихивать и функции. Дефолтные юзаются в случае, если прилетело значениеlet [firstL="JS", lastL="ES"] = [];
undefined
- Теперь помучаем объекты:
Обратите внимание, что по-умолчанию имена переменных и полей в объекте (справа) должны совпадать, чтобы всё прошло нормальноlet {var1, var2} = {var1: 1, var2: 2};
- Если имена не совпадают, то нужно указать, что куда записывать:
Таким образом будут присвоены значения переменным w, h, titlelet options = { title: "123", width: 100, height: 200 }; let {width: w, height: h, title} = options;
- Дефолтные значения задаются также как и в случае с массивом. До кучи можно комбинировать дефолты с предыдущей фичей:
let {width:w=100, height:h=200, title} = options;
- К сожалению, спреда для объектов не завезли, но он поддерживается в Babel в экспериментальном режиме
- Если будете юзать деструктуризацию с уже существующими переменными, то в случае ошибок всё выражение заключите в круглые скобки. Это проблема с областями видимости локальных переменных. Пример:
let a, b; {a, b} = {a: 5, b: 6}; // дерьмище ({a, b} = {a: 5, b: 6}); // тоже дерьмище, но зато работает
- Деструктуризацию можно изи засовывать внутрь другой деструктуризации:
let options = { size: { width: 100, height: 200 }, items: ["JS", "ES"] }; let {size: {width, height}, items: [item1, item2]} = options;
Functions
- Можно указывать параметры по-умолчанию
Дефолтные параметры используются, только если значение либо не было передано, либо был переданfunction show(title="Заголовок"){...};
undefined
- Также дефолтные параметры можно задавать как вызовы функций
- В объявлении функции аргументы можно принимать через спред (отработает в массив)
function show(firstL, lastL, ...rest){...};
- Также можно юзать спред и в вызове функции
- Никто не мешает юзать деструктуризацию в объявлении и вызове функций
- Баловаться с деструктуризацией нужно аккуратно, и прописывать дефолтные значения для входных параметров, чтобы всегда было, что деструктурировать
- Так как функция - объект (здравствуйте, блять), теперь можно обращаться к полю
name
. Там хранится имя этой функции. Удивительно. Имена есть даже у анонимных функций. Но это ой как не точно - С какого-то перепуга теперь объявление функций внутри любого блока
{...}
видно только внутри этого блока. Каеф - Да и скорее всего любой блок
{...}
теперь имеет свою область видимости. Ну так говорят - Теперь можно создавать функции через оператор
=>
let inc = x => x+1; // новое let inc = function(x) {return x+1;}; // во-первых, это тупо выдаст ошибку из-за повторного объявления локальной переменной. Ну а во-вторых, это почти (но не полностью) аналогично предыдущей строке
- Но у этих чудес есть ряд ограничений и особенностей:
- Если хотите больше одного аргумента, то заключайте их в круглые скобки
- Не хотите аргументов вообще - всё равно пишите пустые круглые скобки
- Нужна функция со сложным кодом - пишите фигурные скобки после
=>
. Также в таком случае не забываем прописыватьreturn
, если он вообще нужен - У стрелочных функций нет своих
this
иarguments
. В этих переменных хранятся значения из родительской области видимости. По этой причине нельзя использовать стрелочные функции со словомnew
(как конструкторы)
Strings
- Новый вид кавычек
`строка`
- В них разрешен перенос строк
- Можно вставлять значения переменных прямо в строку
Можно вставлять не только переменные, но и целые выражения и прочую поеботуlet lang = "JS"; console.log(`This is ${lang}`);
str.includes(s);
- проверяет, содержится лиs
вstr
str.endsWith(s);
- проверяет, заканчивается лиstr
наs
str.startsWith(s);
- начинается лиstr.repeat(times);
- повторяетstr
times
раз- Ещё там добавили что-то про шаблонизацию, улучшили поддержку unicode и блаблабла. Неинтересно пока что
Objects, Prototypes
- При объявлении объекта можно использовать сторонние переменные для записи полей в объект. Сложно, но вот пример:
Поле в объекте будет иметь имяlet lang = "JS"; let course = { lang };
lang
и значение"JS"
- Также можно подставлять как имя поля значение какой-либо переменной
Поле в объекте будет иметь имяlet lng = "lang"; let course = { [lng]: "JS" };
lang
- Причем никто не мешает засовывать туда (в квадратные скобки) целые выражения
- Геттер для прототипа уже был, а вот и сеттер подъехал
Object.setPrototypeOf(obj, newProto);
- Якобы теперь использование
__proto__
не считается незаконным Object.assign(target, src1, src2);
- копирует свойства из всехsrc
вtarget
, сохраняя свойства самогоtarget
. Чем позднее указан источник, тем он приоритетнее. Копирование также идет и вглубьObject.is(obj1, obj2);
- сравнивает obj1 и obj2, но не обольщайтесь. Эта штука хоть и делает, что дваNaN
равны друг другу (при===
это не так), и что+0
и-0
это два разных числа, но нормального сравнения объектов пока что не завезли- Теперь можно писать методы объекта попроще:
Также попроще пишутся геттеры и сеттеры. Плюсом с этой похабщиной идет свойство (для этих новых методов)let user = { sayHi(){...}, name: "John" };
[[HomeObject]]
. В нём хранится ссылка на объект, которому этот метод принадлежит - Приблуда
super
позволяет в методах (новых) объекта получать свойства его прототипа. Только в методах и только в новых, это важно и связано с[[HomeObject]]
. Причем всё это будет работать только в примерно таком коде:
Пиздец да и только. Можно, конечно, поизъебываться со стрелочными функциями и обычными, но я пока что не знаю, зачем вообще всё это может понадобиться. Возможно, потом столкнусь и перепишу инфуlet animal = { say() { console.log("Allo") } }; let human = { \_\_proto\_\_: animal, say() { super.say() } }; human.say();
- В заключение скажу, что
[[HomeObject]]
- неизменяемое свойство. Deal with it
Classes
- Теперь можно создавать классы по-нормальному. Якобы. Давайте взглянем
Всё из объявления класса идёт прямиком в прототип. Конструктор вызывается стандартно (черезclass User { constructor(name) { this.name = name; }; sayHi() { alert(this.name); }; }; let user = new User("Admin"); user.sayHi();
new
) - В отличие от старых костылей новый костыль нельзя вызывать без
new
и его область видимости аналогична области видимостилокальной
переменной - Методы класса - новые методы, они имеют доступ к
super
. Ещё их принудили работать черезuse strict
, даже если вы его не прописывали и вам не особо этого хочется - Методы класса
innumerable
, то есть черезfor..in
мы их красивым списочком не получим - Никто не мешает ебашить классы как анонимные функции прямо в переменные (ну типа того):
let User = class { sayHi() { alert('Allo'); }; };
- И также как и с функциями, классам в таком присвоении можно давать имена. Эти имена будут доступны только внутри них самих. То есть такой класс же вы сможете создать лишь через присвоенную переменную:
Эту штуковину можно использовать банально для передачи как параметра внутрь чего-угодноlet SiteGuest = class User { sayHi() { alert('Allo'); }; }; new SiteGuest().sayHi(); // всё норм new User(); // идите нахуй, нет такого класса
- Геттеры, сеттеры и вычисляемые свойства (это там где вы как имя поля подставляете не строку, а указываете имя переменной с этим именем) вполне себе поддерживаются и в этих классах
- В классах не может быть свойств со значениями, только методы (ну и конструктор среди них)
- Можно также позволить юзать методы класса без создания экземпляра этого класса. Для этого внутри класса пишем:
И его можно будет вызвать просто по имени класса, а не через экземпляр класса:static createGuest() { return new User("Гость"); };
let user = User.createGuest();
- Наследование простейшее:
Там дажеclass User {...}; class Admin extends User {...};
Admin.prototype.__proto__ == User.prototype; // true
Constructor
родителя наследуется автоматически, если не указан в ребенке явным образом. Для прямого вызова конструктора родителя, необходимо написатьsuper();
. Причемsuper();
можно вызвать только внутри конструктора ребенка, и нельзя в других методах. Обратите внимание, что до вызоваsuper();
указателяthis
нет
Symbol
- Новый примитив, служит для создания уникальных идентификаторов
- По сути, сделан для самого языка, а не для использования простыми смертными
- Синтаксис такой:
Тип соответственноlet sym = Symbol(); // без new
symbol
- Можно указывать имя для символа, чисто чтобы не запутаться (думал я)
Но жопс тут в том, что это уникальные штуковины. Два символа с одинаковыми именами неравныlet sym = Symbol("name");
- Тогда логичный вопрос - нахуя? Логичный ответ - не ебу
- Причем символы по-умолчанию ведут себя как
локальные переменные
. Ключевое слово - "по-умолчанию" - Существует глобальный реестр этой приблуды. В нём совпадения имен недопустимы. Вроде бы. Читать и писать в реестр довольно легко и доступ туда можно получить откуда-угодно
let name = Symbol.for("name"); console.log(Symbol.for("name") == name); // true
- Обратите, блять, внимание, что это именно штука для проверки соответствия имени символа к имени переменной этого символа. Всё ещё хуёвее, чем я предполагал
- Также можно получить имя символа по переменной:
Передаем именно переменную, а не строку. И получаем именно строку с именем символаSymbol.keyFor(name);
- Если глобального символа с нужным именем в реестре нет - вернет
undefined
- Символы можно подставлять как вычисляемые свойства объекта. И примечательно тут то, что они
innumerable
и до кучи к ним нельзя обратиться по имени (как-будто бы их нет). Но можно обратится по символу - Есть системные символы, которые понадобятся позже по ходу статей.
Symbol.toPrimitive
- идентификатор для свойства, задающего функцию преобразования объекта в примитив.Symbol.iterator
- идентификатор для свойства, задающего функцию итерации по объекту. И так далее
Iterators
- Итераторы, мать их. Вот вам пример с циклом
for
. У вас там есть какая-то переменная, значения которой меняются описанным образом до тех пор, пока выполняются условия. Особо там не разгуляешься, но для большей части задач - подходит. Итераторы же это штучка поинтереснее. Для них ещё придумали конструкциюfor..of
- Массив - самый простой итератор
Тут всё работает так, как и ожидается. Ничего нового, просто синтаксис чутка другойlet arr = [1, 2, 3]; for(let value of arr) { alert(value); };
- Точно также можем пройтись по строке
"Allo"
вместо массива. Будут возвращаться буквы по одной, что логично - Но что если у нас вот такой объект:
По коду очевидно, что мы хотим из этого получить, но как?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) { alert(num); };
next()
. Это всё обязательно. А вот уже как этот метод будет отдавать значения - это уже ваша забота. Только существуют ограничения, что должно быть 2 поля:value
иdone
. Вvalue
ожидаемо будет передаваемое значение, а вdone
информация, закончился или нет обход по итератору. Ну и обратите внимание, что тутcurrent
меняется прямо в телеnext()
. Если вам это непонятно, то перечитайте статью с инфой про инкременты и дикременты - Метод
next()
может быть определен в объекте, а не в итераторе. В таком случае итератор нужно будет прописывать внутри объекта, также по символьному имени, но его тело будет содержать толькоreturn this;
- Наличие системного итератора позволяет использовать спред и деструктуризацию
- Итераторы могут быть бесконечными. Понадобится
break
- Никто не мешает получать встроенные итераторы простым обращением к ним через символы
Map
Map
- что-то типа объекта, в котором именами свойств может быть что-угодно (даже другой объект), и порядок этих свойств жёстко определен (в обычных объектах свойства могут перебираться например по алфавиту или я хз)- Тут, надеюсь, всё понятно
let map = new Map(); map.set('1', 'str'); map.set(1, 123); console.log(map.get(1)); console.log(map.get('1');
- В
map.size
хранится количество элементов вMap
- Метод
set
можно чейнить (вызывать цепочно):map.set('1', 'str').set(1, 123);
- При объявлении
Map
можно сразу же присвоить значения:
В конструктор должен быть передан итерируемый объект, не обязательно массивlet map = new Map([ ['1', 'str'], [1, 123] ]);
map.delete(key);
- удаляет запись по ключуmap.clear();
- очищаетMap
map.has(key);
- проверяет наличие записи по ключуmap.keys();
- итерируемый объект с ключамиmap.values();
- итерируемый объект со значениямиmap.entries();
- итерируемый объект с записями[ключ, значение]
- Последние три можно использовать в
for..of
- Обращаю внимание, что перебор свойств идет в том же порядке, в котором происходила запись
- Также у
Map
есть свойforEach
Set
Set
- напоминает массив, в котором нет повторяющихся элементов. Если вы попробуете в него записать что-то, что там уже есть - дубликата не появится- Пример
let set = new Set(); set.add(123); set.add(234); set.add(345); set.add(123); set.add(234); console.log(set.size); // количество элементов = 3
set.delete(item);
- удалить значениеset.has(item);
- проверка наличияset.clear();
- очиститьSet
- Перебор элементов аналогичен
Map
WeakMap, WeakSet
WeakMap
иWeakSet
отличаются от своих "сильных" версий (хыдыхыды) тем, что их нельзя очищать, узнавать их размер, обходить и выводить все элементы. В них можно лишь добавлять/удалять/получать/проверять наличие записи по ключу- У
WeakMap
ключами могут быть только объекты. И если ссылка на этот ключ-объект будет удалена отовсюду (кроме этого нашегоWeakMap
), то и запись с эти ключом, и ссылка на этот объект будут удалены, но не моментально, а тогда, когда захочет сборщик мусора. Поэтому аккуратнее с такими штуками
Promises
Promise
- обещание сделать что-то в конце выполнения какого-то кода. Это "что-то" зависит от исхода выполнения кода. Исходов всего два - успех (fulfilled
) или ошибка (rejected
)- К промису цепочкой присоединяются коллбэки (обработчики результата), которые будут брать результат из промиса или предыдущего коллбэка, делать с ним что-то своё и отдавать дальше
- Пример
var promise = new Promise(function(resolve, reject){...});
- Этот код будет выполнен обязательно, и только после его выполнения отрабатывают обработчики. Синтаксис такой:
promise.then(onFulfilled, onRejected).then(...)...
- Можно не указывать второй параметр. Также можно передать null в качестве первого параметра. Надеюсь, очевидно, что в таком случае будет происходить и при каких условиях
- Если нужно поймать именно ошибку, можно писать не
then
, аcatch
. Причем, если во всех обработчиках по ходу цепочки нет отлова реджекта, то можно ловить его в самом конце одним единственнымcatch
- Если в коде промиса где-то прописать
throw
, то он будет воспринят какreject
- Чтобы выйти из промиса с ошибкой - пишем
reject()
и передаем в него то, что вы хотите передать обработчикам ошибок - По аналогии для передачи нормального результата пишем
resolve()
и отдаем ему что-то, но только одно. Это ограничение действует и наreject
- Чтобы в обработчиках ловить два этих ответа (один при успехе, другой при ошибке), нужно просто указать переменные, куда их складывать
promise .then( result => {...}, error => {...} );
- После того как промис принял одно из двух значений (успех или ошибка), вы этот результат уже никак не поменяете
- Промисы очень удобно использовать, например, для рендера результатов запроса куда-нибудь
- Обратите внимание, что (чтобы всё нормально работало) из первой функции (выполнение которой ждут все обработчики) должен вернутся промис
- Также обратите внимание, что если
catch
возвращает результат работы над ошибкой черезreturn
, то этот результат перейдет в ближайшийthen
. Если же ошибку не получилось обработать там - то выкидываем её черезthrow
. Оттуда она попадет в ближайшийcatch
. Если же вы не всё учтете и что-то где-то не поймаете, всё спокойно может рухнуть к хуям - Чтобы запустить несколько промисов одновременно и работать с их результатами пачкой, то можно использовать такую конструкцию:
Обратите внимание, что нужно передать сюда не обязательно массив. Любой итерируемый объект подойдет. В итоге вPromise.all([ promiseFunction(...), promiseFunction(...) ]).then(results => { console.log(results); });
results
у нас будут лежать результаты всех промисов. Эти результаты будут переданы в обработчики только после того, как будут выполнены все промисы - Если же вам нужен результат выполнения только одного промиса из списка (самого шустрого), то используем
Promise.race(...);
- Промис может состоять всего из одной функции:
reject
илиresolve
. Вас никто не ограничивает
Generators
- Генераторы - новый вид функций, который позволяет получать от функции разные значения при разных вызовах
- Вот такое объявление самого генератора:
function* generateSome(){ yield 1; yield 2; return 3; };
- Затем создаем экземпляр такого генератора:
let generator = generateSome();
- Теперь, чтобы получать значения из этого экземпляра, нужно использовать у него метод
next()
:let one = generator.next();
- В
one
будет объект{value: 1, done: false}
- Очевидно, какие значения будут при последующих вызовах. При последнем вызове
done: false
сменится наtrue
. Дальнейшие обращения к этому генератору будут возвращать{value: undefined, done: true}
(по крайней мере в Хроме так) - Нужно заново запустить или даже одновременно два? Никто не мешает
- Звездочку в объявлении можно передвигать к названию. Но лучше не надо
- Генераторы вполне себе итерируемые, поэтому можно их запихивать в
for..of
. Только вот учтите, что проитерируются все значения кроме последнего (потому что тамreturn
). Для конкретно этого случая можно поменятьreturn
наyield
, а вот во всех остальных - не стоит - Можно загонять генераторы внутрь другого генератора и там шалить с ними:
Надеюсь, что понятно, чтоfunction* generateSequence(start, end) { for (let i = start; i <= end; i++) yield i; }; function* generateAlphaNum() { yield* generateSequence(48, 57); yield* generateSequence(65, 90); yield* generateSequence(97, 122); };
yield*
как бы намекает на обращение именно к генератору - Через
yield
можно не только возвращать значения из генератора, но и передавать их внутрь генератора извне:
По-дурацки, но работаетfunction* gen() { let result = yield "2 + 2?"; alert(result); }; let generator = gen(); let question = generator.next().value; setTimeout(() => generator.next(4), 2000);
- Можно закинуть через
.throw()
в генератор ошибку и даже написать там обработчик для ошибок, но зачем? - Наиболее интересным является использование генераторов для последовательной генерации промисов
Modules
- Если нужно поделить большой код на куски, то вполне сойдут модули
- Для начала определяем в файле, что будет передавать наружу:
Передавать можно что-угодно, но я буду называть их переменнымиexport let one = 1; let two = 2; let three = 3; export {two as var1, three as var2};
- Можно определять внешние названия для экспортируемых переменных через
as
- Нельзя экспортировать переменные без имени
- В основном файле уже подключаем модуль и необходимые переменные из него:
Последним указывается путь до модуля, можно без расширенияimport {two, three} from "./nums"
- В импорте также можно использовать
as
- Можно импортировать вообще всё из модуля через
*
- Также если экспортируется всего одна переменная, то можно у неё прописать
export default
(дальше синтаксис как выше). Тогда можно будет при импорте писать не объект со списком переменных (пишется именно объект, даже если всего одна переменная), а просто название дефолтной переменной из модуля
Proxy
- Прокси позволяют модифицировать различные способы обращения к разным переменным и функциям. Проблема только в том, что обращаться нужно будет именно к объекту прокси, через который уже будет идти обращение к переменной
- Перехватывать можно почти всё, впадлу писать список
- Пример
let user = {}; let proxy = new Proxy(user, { get(target, prop) { alert(`Reading ${prop}`); return target[prop]; }, set(target, prop, value) { alert(`Writing ${prop} ${value}`); target[prop] = value; return true; } }); proxy.firstName = "123"; // присваиваем proxy.firstName; // считываем
- Причем вам никто не мешает обращаться к переменной и напрямую (как раньше)
- Тут уже вы можете разгуляться, как вам позволит фантазия