От переводчика:
Предлагаю вашему вниманию перевод краткого (действительно краткого) руководства по ES6. В нём можно ознакомиться с основными понятиями стандарта.
Оригинальный текст в некоторых случаях был дополнен или заменён на более подходящий источник. Например, часть определения ключевого слова const
является переводом документации с MDN.
Чтобы лучше разобраться в некоторых концепциях (для выполнения качественного перевода) использовалось описание стандарта на сайте MDN, руководство "You Don't Know JS: ES6 & Beyond" и учебник Ильи Кантора.
Перевод выложил на Гитхаб: https://github.com/etnolover/ES6-for-humans-translation. В случае нахождения ошибок пишите, исправлю.
Ссылка на оригинальный текст: https://github.com/metagrover/ES6-for-humans
Update 22.07.2016: добавил опрос про const
Содержание
let
,const
и блочная область видимости- Стрелочные функции
- Параметры по умолчанию
- Spread/Rest оператор
- Расширение возможностей литералов объекта
- Восьмеричный и двоичный литералы
- Деструктуризация массивов и объектов
- Ключевое слово super для объектов
- Строковые шаблоны и разделители
- for...of против for...in
- Map и WeakMap
- Set и WeakSet
- Классы в ES6
- Тип данных Symbol
- Итераторы
- Генераторы
- Опрос: в каком регистре писать
const
?
1. let, const и блочная область видимости
Ключевое слово let
позволяет объявлять переменные с ограниченной областью видимости — только для блока {...}, в котором происходит объявление. Это называется блочной областью видимости. Вместо ключевого слова var
, которое обеспечивает область видимости внутри функции, стандарт ES6 рекомендует использовать let
.
var a = 2;
{
let a = 3;
console.log(a); // 3
}
console.log(a); // 2
Другой формой объявления переменной с блочной областью видимости является ключевое слово const
. Оно предназначено для объявления переменных (констант), значения которых доступны только для чтения. Это означает не то, что значение константы неизменно, а то, что идентификатор переменной не может быть переприсвоен.
Вот простой пример:
{
const ARR = [5, 6];
ARR.push(7);
console.log(ARR); // [5,6,7]
ARR = 10; // TypeError
ARR[0] = 3; // значение можно менять
console.log(ARR); // [3,6,7]
}
О чём стоит помнить:
- Когда дело касается поднятия переменных (hoisting)
let
иconst
, их поведение отличается от традиционного поведенияvar
иfunction
. Иlet
иconst
не существуют до своего объявления (от переводчика: для подробностей автор оригинального руководства отсылает к статье Temporal Dead Zone) - Областью видимости
let
иconst
является ближайший блок. - При использовании
const
рекомендуется использовать ПРОПИСНЫЕ_БУКВЫ. - В
const
одновременно с объявлением переменной должно быть присвоено значение.
2. Стрелочные функции
Стрелочные функции представляют собой сокращённую запись функций в ES6. Стрелочная функция состоит из списка параметров ( ... )
, за которым следует знак =>
и тело функции.
// Классическое функциональное выражение
let addition = function(a, b) {
return a + b;
};
// Стрелочная функция
let addition = (a, b) => a + b;
Заметим, что в примере выше, тело функции представляет собой краткую запись, в которой не требуется явного указания на то, что мы хотим вернуть результат.
А вот пример с использованием блока из фигурных скобок:
let arr = ['apple', 'banana', 'orange'];
let breakfast = arr.map(fruit => {
return fruit + 's';
});
console.log(breakfast); // ['apples', 'bananas', 'oranges']
Это ещё не всё!...
Стрелочные функции не просто делают код короче. Они тесно связаны с ключевым словом this
и привязкой контекста.
Поведение стрелочных функций с ключевым словом this
отличается от поведения обычных функций с this
. Каждая функция в JavaScript определяет свой собственный контекст this
, но внутри стрелочных функций значение this
то же самое, что и снаружи (стрелочные функции не имеют своего this
). Посмотрим на следующий код:
function Person() {
// Конструктор Person() определяет `this` как экземпляр самого себя.
this.age = 0;
setInterval(function growUp() {
// Без использования `use strict`, функция growUp() определяет `this`
// как глобальный объект, который отличается от `this`,
// определённого конструктором Person().
this.age++;
}, 1000);
}
var p = new Person();
В ECMAScript 3/5 это поведение стало возможным изменить, присвоив значение this
другой переменной.
function Person() {
var self = this;
self.age = 0;
setInterval(function growUp() {
// Коллбэк относится к переменной `self`,
// значением которой является ожидаемый объект.
self.age++;
}, 1000);
}
Как сказано выше, внутри стрелочных функций значение this
то же самое, что и снаружи, поэтому следующий код работает так, как от него и ожидается:
function Person() {
this.age = 0;
setInterval(() => {
this.age++; // `this` относится к объекту person
}, 1000);
}
var p = new Person();
Узнать больше о 'Лексическом this' в стрелочных функциях на сайте MDN
3. Параметры по умолчанию
ES6 позволяет установить параметры по умолчанию при объявлении функции. Вот простой пример:
let getFinalPrice = (price, tax = 0.7) => price + price * tax;
getFinalPrice(500); // 850, так как значение tax не задано
getFinalPrice(500, 0.2); // 600, значение tax по-умолчанию заменяется на 0.2
4. Spread / Rest оператор
...
оператор называют как spread или rest, в зависимости от того, как и где он используется.
При использовании в любом итерируемом объекте (iterable), данный оператор "разбивает" ("spread") его на индивидуальные элементы:
function foo(x, y, z) {
console.log(x, y, z);
}
let arr = [1, 2, 3];
foo(...arr); // 1 2 3
Другим распространённым использованием оператора ...
является объединение набора значений в один массив. В данном случае оператор работает как "rest" (от переводчика: не нашёл подходящего перевода на русский язык, из примера ниже всё станет ясно)
function foo(...args) {
console.log(args);
}
foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
5. Расширение возможностей литералов объекта
ES6 позволяет объявить литералы объекта с помощью короткого синтаксиса для инициализации свойств из переменных и определения функциональных методов. Также, стандарт обеспечивает возможность вычисления свойств непосредственно в литерале объекта.
function getCar(make, model, value) {
return {
// с синтаксисом короткой записи можно
// пропускать значение свойства, если оно
// совпадает с именем переменной, значение
// которой мы хотим использовать
make, // аналогично make: make
model, // аналогично model: model
value, // аналогично value: value
// вычисляемые свойства теперь работают в
// литералах объекта
['make' + make]: true,
// Короткая запись метода объекта пропускает
// ключевое слово `function` и двоеточие. Вместо
// "depreciate: function() {}" можно написать:
depreciate() {
this.value -= 2500;
}
};
}
let car = getCar('Kia', 'Sorento', 40000);
console.log(car);
// {
// make: 'Kia',
// model:'Sorento',
// value: 40000,
// makeKia: true,
// depreciate: function()
// }
6. Восьмеричный и двоичный литералы
В ES6 появилась новая поддержка для восьмеричных и двоичных литералов.
Добавление к началу числа 0o
или 0O
преобразует его в восьмеричную систему счисления (аналогично, 0b
или 0B
преобразует в двоичную систему счисления). Посмотрим на следующий код:
let oValue = 0o10;
console.log(oValue); // 8
let bValue = 0b10;
console.log(bValue); // 2
7. Деструктуризация массивов и объектов
Деструктуризация помогает избежать использования вспомогательных переменных при взаимодействии с объектами и массивами.
function foo() {
return [1, 2, 3];
}
let arr = foo(); // [1,2,3]
let [a, b, c] = foo();
console.log(a, b, c); // 1 2 3
function bar() {
return {
x: 4,
y: 5,
z: 6
};
}
let { x: a, y: b, z: c } = bar();
console.log(a, b, c); // 4 5 6
8. Ключевое слово super для объектов
ES6 позволяет использовать метод super
в (безклассовых) объектах с прототипами. Вот простой пример:
var parent = {
foo() {
console.log("Привет от Родителя!");
}
}
var child = {
foo() {
super.foo();
console.log("Привет от Ребёнка!");
}
}
Object.setPrototypeOf(child, parent);
child.foo(); // Привет от Родителя!
// Привет от Ребёнка!
9. Строковые шаблоны и разделители
ES6 предоставяляет более простой способ вставки значения переменной или результата выражения (т.н. "интерполяцию"), которые рассчитываются автоматически.
${ ... }
используется для вычисления значения переменной/выражения.- `` Обратные кавычки используются как разделитель для таких случаев.
let user = 'Кевин';
console.log(`Привет, ${user}!`); // Привет, Кевин!
10. for...of против for...in
for...of
используется для перебора в цикле итерируемых объектов, например, массивов.
let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname of nicknames) {
console.log(nickname);
}
// di
// boo
// punkeye
for...in
используется для перебора в цикле всех доступных для перебора (enumerable) свойств объекта.
let nicknames = ['di', 'boo', 'punkeye'];
nicknames.size = 3;
for (let nickname in nicknames) {
console.log(nickname);
}
// 0
// 1
// 2
// size
11. Map и WeakMap
ES6 представляет новые структуры данных — Map
и WeakMap
. На самом деле, мы используем "Map" в JavaScript всё время. Каждый объект можно представить как частный случай Map
.
Классический объект состоит из ключей (всегда в строковом виде) и значений, тогда как в Map
для ключа и значения можно использовать любое значение (и объекты, и примитивы). Посмотрим на этот код:
var myMap = new Map();
var keyString = "строка",
keyObj = {},
keyFunc = function() {};
// устанавливаем значения
myMap.set(keyString, "значение, связанное со 'строка'");
myMap.set(keyObj, "значение, связанное с keyObj");
myMap.set(keyFunc, "значение, связанное с keyFunc");
myMap.size; // 3
// получаем значения
myMap.get(keyString); // "значение, связанное со 'строка'"
myMap.get(keyObj); // "значение, связанное с keyObj"
myMap.get(keyFunc); // "значение, связанное с keyFunc"
WeakMap
WeakMap
это Map
, в котором ключи обладают неустойчивыми связями, что позволяет не мешать сборщику мусора удалять элементы WeakMap
. Это означает, что можно не беспокоиться об утечках памяти.
Стоить отметить, что в WeakMap
, в отличие от Map
, каждый ключ должен быть объектом.
Для WeakMap
есть только четыре метода: delete(ключ)
, has(ключ)
, get(ключ)
и set(ключ, значение)
.
let w = new WeakMap();
w.set('a', 'b');
// Uncaught TypeError: Invalid value used as weak map key
var o1 = {},
o2 = function(){},
o3 = window;
w.set(o1, 37);
w.set(o2, "azerty");
w.set(o3, undefined);
w.get(o3); // undefined, потому что это заданное значение
w.has(o1); // true
w.delete(o1);
w.has(o1); // false
12. Set и WeakSet
Объекты Set это коллекции уникальных значений. Дублированные значения игнорируются, т.к. коллекция должна содержать только уникальные значения. Значения могут быть примитивами или ссылками на объекты.
let mySet = new Set([1, 1, 2, 2, 3, 3]);
mySet.size; // 3
mySet.has(1); // true
mySet.add('строки');
mySet.add({ a: 1, b:2 });
Вы можете перебирать Set
в цикле с помощью forEach
или for...of
. Перебор происходит в том же порядке, что и вставка.
mySet.forEach((item) => {
console.log(item);
// 1
// 2
// 3
// 'строки'
// Object { a: 1, b: 2 }
});
for (let value of mySet) {
console.log(value);
// 1
// 2
// 3
// 'строки'
// Object { a: 1, b: 2 }
}
У Set
также есть методы delete()
и clear()
.
WeakSet
Аналогично WeakMap
, объект WeakSet
позволяет хранить объекты с неустойчивыми связями в коллекции. Объект в WeakSet
уникален.
var ws = new WeakSet();
var obj = {};
var foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false, foo не был добавлен к коллекции
ws.delete(window); // удаляет window из коллекции
ws.has(window); // false, window был удалён
13. Классы в ES6
В ES6 представили новый синтаксис для классов. Здесь стоит отметить, что класс ES6 не представляет собой новую объектно-ориентированную модель наследования. Это просто синтаксический сахар для существующего в JavaScript прототипного наследования.
Класс в ES6 представляет собой просто новый синтаксис для работы с прототипами и функциями-конструкторами, которые мы привыкли использовать в ES5.
Функции, записанные с помощью ключевого слова static
, используются для объявления статических свойств класса.
class Task {
constructor() {
console.log("Создан экземпляр task!");
}
showId() {
console.log(23);
}
static loadAll() {
console.log("Загружаем все tasks...");
}
}
console.log(typeof Task); // function
let task = new Task(); // "Создан экземпляр task!"
task.showId(); // 23
Task.loadAll(); // "Загружаем все tasks..."
extends и super в классах
Посмотрим на следующий код:
class Car {
constructor() {
console.log("Создаём новый автомобиль");
}
}
class Porsche extends Car {
constructor() {
super();
console.log("Создаём Porsche");
}
}
let c = new Porsche();
// Создаём новый автомобиль
// Создаём Porsche
В ES6 ключевое слово extends
позволяет классу-потомку наследовать от родительского класса. Важно отметить, что конструктор класса-потомка должен вызывать super().
Также, в классе-потомке можно вызвать метод родительского класса с помощью super.имяМетодаРодителя()
.
Узнать больше о классах на сайте MDN
О чём стоит помнить:
- Объявления классов не поднимаются наверх (not hoisted). Сначала нужно объявить класс и только после этого использовать его, иначе будет ошибка ReferenceError.
- Нет необходимости использовать ключевое слово
function
во время задания функций внутри определения класса.
14. Тип данных Symbol
Symbol это уникальный и неизменяемый тип данных, представленный в ES6. Целью Symbol
является создание уникального идентификатора, к которому нельзя получить доступ.
Вот как можно создать Symbol
:
var sym = Symbol("опциональное описание");
console.log(typeof sym); // symbol
Заметим, что использовать new
вместе с Symbol(…)
нельзя.
Если Symbol
используется как свойство/ключ объекта, он сохраняется таким специальным образом, что свойство не будет показано при нормальном перечислении свойств объекта.
var o = {
val: 10,
[Symbol("случайный")]: "Я - символ",
};
console.log(Object.getOwnPropertyNames(o)); // val
Чтобы извлечь символьные свойства объекта, нужно использовать Object.getOwnPropertySymbols(o)
15. Итераторы
Итератор обращается к элементам коллекции по одному, в то же время сохраняя память о своей текущей позиции в этой коллекции. У итератора есть метод next()
, который возвращает следующий элемент в последовательности. Этот метод возвращает объект с двумя свойствами: done (окончен ли перебор) и value (значение).
В ES6 есть метод Symbol.iterator
, который определяет итератор для объекта по-умолчанию. При каждой необходимости перебора в цикле для объекта (например, в начале цикла for..of), его метод итератора вызывается без аргументов, и возвращённый итератор используется для того, чтобы получить значения для перебора.
Посмотрим на массив, который является перебираемым (iterable), и на итератор, который есть у массива для обработки его значений:
var arr = [11,12,13];
var itr = arr[Symbol.iterator]();
itr.next(); // { value: 11, done: false }
itr.next(); // { value: 12, done: false }
itr.next(); // { value: 13, done: false }
itr.next(); // { value: undefined, done: true }
Заметим, что можно написать собственный итератор через определение obj[Symbol.iterator]()
с описанием объекта.
Подробнее про итераторы:
На сайте MDN
16. Генераторы
Функции-генераторы представляют собой новую особенность ES6, которая позволяет функции создавать много значений в течение некоторого периода времени, возвращая объект (называемый генератором), который может быть итерирован для выброса значений из функции по одному за раз.
Функция-генератор возвращает итерируемый объект при своём вызове.
Функция-генератор записывается с помощью знака *
после ключевого слова function
, а в теле функции должно присутствовать ключевое слово yield
.
function *infiniteNumbers() {
var n = 1;
while (true) {
yield n++;
}
}
var numbers = infiniteNumbers(); // возвращает перебираемый объект
numbers.next(); // { value: 1, done: false }
numbers.next(); // { value: 2, done: false }
numbers.next(); // { value: 3, done: false }
Каждый раз при вызове yield
возвращённое значение становится следующим значением в последовательности.
Также заметим, что генераторы вычисляют свои возвращённые значения по запросу, что позволяет им эффективно представлять последовательности, затратные с точки зрения вычислений, или даже бесконечные последовательности.
17. Промисы
В ES6 появилась встроенная поддержка промисов. Промис это объект, который ждёт выполнения асинхронной операции, после которого (т.е. после выполнения) промис принимает одно из двух состояний: fulfilled (resolved, успешное выполнение) или rejected (выполнено с ошибкой).
Стандартным способом создания промиса является конструктор new Promise()
, который принимает обработчик с двумя функциями как параметрами. Первый обработчик (обычно именуемый resolve
) представляет собой функцию для вызова вместе с будущим значением, когда оно будет готово; второй обработчик (обычно именуемый reject
) является функцией, которая вызывается для отказа от выполнения промиса, если он не может определить будущее значение.
var p = new Promise(function(resolve, reject) {
if (/* условие */) {
resolve(/* значение */); // fulfilled successfully (успешный результат)
} else {
reject(/* reason */); // rejected (ошибка)
}
});
Каждый промис обладает методом then
, в котором есть два коллбэка. Первый коллбэк вызывается, если промис успешно выполнен (resolved), тогда как второй коллбэк вызывается, если промис выполнен с ошибкой (rejected).
p.then((val) => console.log("Промис успешно выполнен", val),
(err) => console.log("Промис выполнен с ошибкой", err));
При возвращении значения от then
коллбэки передадут значение следующему коллбэку then
.
var hello = new Promise(function(resolve, reject) {
resolve("Привет");
});
hello.then((str) => `${str} Мир`)
.then((str) => `${str}!`)
.then((str) => console.log(str)) // Привет Мир!
При возвращении промиса, успешно обработанное значение промиса пройдёт к следующему коллбэку, для того, чтобы эффективно соединить их вместе.
Эта простая техника помогает избежать ада с коллбэками ("callback hell").
var p = new Promise(function(resolve, reject) {
resolve(1);
});
var eventuallyAdd1 = (val) => {
return new Promise(function(resolve, reject){
resolve(val + 1);
});
}
p.then(eventuallyAdd1)
.then(eventuallyAdd1)
.then((val) => console.log(val)) // 3
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (87)
Igogo2012
19.07.2016 10:42-1В ECMAScript 3/5 это поведение стало возможным изменить, присвоив значение this другой переменной.
Вы видимо про Function.prototype.bind не слышали?mx2000
19.07.2016 12:04Вы видимо не слышали, что поддержка bind появилась только в 2011 году.
rock
19.07.2016 12:49mx2000
19.07.2016 19:56поддержка bind появилась в Firefox 4.0 в 2011 году. С остальными браузерами примерно та же фигня. Дата выпуска спецификации совершенно минорна в данном контексте.
rock
19.07.2016 20:18До сих пор полной поддержки ES6 нет ни в одном стабильном браузере, а если брать в расчёт до сих пор живые IE… вот только ES6 используется чуть менее, чем повсеместно.
Function#bind
достаточно хорошо, хоть и не полностью, полифилится, чем все с самого начала и пользовались. Так что-что, а поддержка браузерами, в отличии от спецификации, в данном контексте, как вы выразились, "совершенно минорна".mx2000
19.07.2016 21:54-1С самого начала повсеместно было именно self = this, потому что это очевидное и простое решение. А уже потом, с появлением prototypejs публика прозрела, что оказывается JS — это Scheme в Cи-подобном синтаксисе. Вернитесь в 2005 год и расскажите мне, что там у вас полифилится и насколько хорошо.
EtnoLover
19.07.2016 13:13+3Про Function.prototype.bind я слышал, спасибо.
Здесь есть два момента:
1. Это перевод, в котором не было написано про bind
2. Насколько я понимаю, суть текста верна — для решения проблемы можно использовать приём с var self = this.
В связи с перечисленными двумя пунктами, в чём конкретно у вас претензия к тексту? В отстутствии упоминания про Function.prototype.bind?
iShatokhin
19.07.2016 10:57+5Отличный материал, но стоило бы упомянуть, что у стрелочных функций нет не только своего this, но и своих arguments (а также super и new.target).
EtnoLover
19.07.2016 13:18По мере чтения оригинального текста у меня появлялось желание что-то дописать, переписать, задать другую структуру. Но особо себя в этом не поощрял, за исключением нескольких случаев.
Думаю, что можно создать PR автору, он достаточно оперативно их рассматривает (мой PR рассмотрел в тот же день).
В любом случае спасибо за полезный комментарий.
MaxKitsch
19.07.2016 10:57+3«rest» (от переводчика: не нашёл подходящего перевода на русский язык, из примера ниже всё станет ясно)
Я бы предположил «остаток» или, по созвучию, «хвост» — использование rest-оператора в качестве единственного агрумента функции — это вырожденный случай, в общем виде это что-то вроде
function(a, b, c, ...moreArgs) {...}
Т.е. оператор возвращает в виде массива оставшиеся неописанными аргументы.EtnoLover
19.07.2016 13:21+1Спасибо за пример. В моём понимании, «остаток» или «хвост» ничего не говорят сами по себе. Вы написали, что означает «хвост», и мне стало понятно. Без объяснения это было бы неочевидно. В итоге просто поменялся бы «rest» на «хвост», но смысл бы очевидней не стал. ИМХО, конечно.
vmb
19.07.2016 11:16+1Спасибо за перевод. Пожалуйста, проверьте:
1. Не работают ссылки на разделы (а код страницы показывает отсутствие якорей в заголовках).
2. В начале раздела про шаблоны странное:
\${… }`` — зачем-то экранирование и ${… } вынесено за кавычки
``` — зачем-то тройные кавычкиEtnoLover
19.07.2016 13:22+1Спасибо за комментарий. Ссылки поправлю вечером.
vmb
19.07.2016 21:11К сожалению, ссылки всё ещё не работают. Я сам пробовал атрибуты
id
иname
в заголовках, а также<a name='...'></a>
перед заголовками, но Хабр это всё вычищает, хоть в исходном коде для редактирования сохраняет. Не могу понять, что нужно сделать, чтобы получалось как здесь, например.EtnoLover
20.07.2016 09:29+1Аналогично. У них в коде как раз id и name, но в моей публикации это почему-то не работает.
Вчера минут 40 потратил на то, чтобы попробовать различные варианты, но все не работали.
Простите, что не исправил. Буду разбираться дальше, а то непорядок.
P.S. Какое совпадение, я тоже ориентировался на дайджест фронтенд-разработки)
FlashManiac
19.07.2016 12:03А как быть с приватными членами в классах?
Zenitchik
19.07.2016 13:09Согласно философии JavaScript: если не хотите обращаться к членам объекта из других объектов — не обращайтесь к ним.
impwx
19.07.2016 18:13Помимо сказанного RubaXa и Zenitchik, есть еще пара вариантов:
- Использовать конвенцию, префиксуя приватные члены класса символом нижнего подчеркивания
- Обернуть всё в функцию, используя ее локальные переменные для хранения приватных данных (с помощью замыканий)
Стоит заметить, что TS ограничивает доступ к приватным переменным только на уровне проверки типов. Они все равно будут доступны из кода на чистом JS, или при приведении объекта к типуany
.
FlashManiac
19.07.2016 21:26Зачем тогда вообще нужен этот новый стандарт? В принципе классический JS он самодостаточен и очень гибок. И все эти прибамбасы особо сильно не упрощают жизнь. Самым логичным было бы улучшить статический анализатор до уровня когда практически все ошибки были бы сразу видны на этапе компиляции. Так как сейчас в JS это самая большая головная боль на мой взгляд. И именно из-за того, что сложно делать объемные приложения на JS выдумывают всякие препроцессоры и новые стандарты.
Zenitchik
20.07.2016 19:21Классовое наследование — это хороший синтаксический сахар. Одна строчка вместо трёх.
Лично мне не нравится, что системную переменную super ввели только для классов. Общая идея JavaScript предполагает, что раз есть системная переменная, то она всегда должна быть чему-то равна. Как это сделано для this.
Вы, кстати, писали когда-нибудь объёмное приложение на JavaScript? Я с не вижу в этом особой головной боли. А препроцессоры считаю ересью.
FlashManiac
22.07.2016 13:37Да, писал большие и маленькие проекты. И в каждом проекте использовал разные стили — и мне это очень понравилось. Стили выбирал исходя из задачи. Если это сложный проект — писал в стандартном стиле классы с наследованием через прототипы — каждый класс в отдельном файле. Если же что то простое — писал модулем в одном файле. Единственное, что напрягало — так это то, что не всегда видны ошибки — только при запуске, а так же IDE показывают непонятно что ))).
С IDE вообще беда — ни одна среда пока что не может нормально обработать класс и отобразить что то адекватное в автокомплите. После долгих переборов IDE остановился пока что на VisualStudio. Там какой то хитрый автокомплит, что походу дела среда запускает некоторые куски кода, и получаем более адекватные подсказки при написании кода. Остальные же редакторы заточены под определенный стиль.
ChALkeRx
19.07.2016 18:19+1Справедливости для: приватные члены — просто соглашение. В тех же плюсах его нарушить достаточно просто. В js вы можете для этого использовать либо Symbol, либо по-старинке через нижнее подчёркивание, причём и то и другие — тоже соглашение и тоже запросто нарушается. Ну либо можно всё через замыкания и/или WeakMap делать, но получится страшно.
Demogor
21.07.2016 08:13На замыканиях — это вообще классика.
Кстати, иногда неплохо затрудняет отладку какого-нибудь обфусцированного кода, когда, к примеру, нужно вызвать напрямую какой-нибудь «приватный» метод.raveclassic
21.07.2016 09:07В принципе это можно сделать прямо в дебаггере из консоли, если поставить брейкпоинт на любом месте класса. Консоль подхватывает текущий контекст this и само замыкание.
Demogor
21.07.2016 10:15Конечно. Но для срабатывания брейкпоинта метод должен быть вызван тем или иным способом. К примеру:
function test(){ var innerFunction=function(){ console.log("You can't call me outside"); }; this.outer=function(){ console.log("You can call me, but for what?"); }; setTimeout(innerFunction, 1000); }
Сферический пример в вакууме.
innerFunction вызовется 1 раз и нанай. А если для отладки нужно ее запустить раз 5. к примеру? Ну или не отладки. а каких-нибудь личных хитровымудренных нужд(к примеру. для автоматизации каких-либо процессов на сайте при помощи userscri[t'ов).raveclassic
21.07.2016 15:30Ну ставите брейкпоинт на строку с setTimeout и вызываете сначала test(), а потом innerFunction() сколько нужно
Darkside73
19.07.2016 13:18+3Еще полезное применение деструктуризации аргументов функции:
function userId({id}) { return id; } let user = {id: 1} userId(user)
maks1mp
19.07.2016 13:23+2в стрелочных функциях пример можно еще короче записать
let arr = ['apple', 'banana', 'orange'];
let breakfast = arr.map(fruit => fruit + 's');
alert(breakfast);xaolas
20.07.2016 13:58Лучше так:
const arr = ['apple', 'banana', 'orange'];
const breakfast = arr.map(fruit => `${fruit}s`);
console.log(breakfast);
LeusMaximus
19.07.2016 13:35Может быть стоит связаться с автором, чтобы он разместил в своем репозитории ссылку на репозиторий с переводом?
ChALkeRx
19.07.2016 14:35+4При использовании
const
рекомендуется использовать ПРОПИСНЫЕ_БУКВЫ.
Кем рекомендуется? Укажите ссылку, пожалуйста.
let nicknames = ['di', 'boo', 'punkeye']; nicknames.size = 3; for (let nickname of nicknames) { console.log(nickname); }
По-хорошему, стоит использовать
const
для всех переменных, которые не планируется переприсваивать:
const nicknames = ['di', 'boo', 'punkeye']; nicknames.size = 3; for (const nickname of nicknames) { console.log(nickname); }
Функции, записанные с помощью ключевого слова
static
, используются для объявления статических свойств класса.
Пока что только только методов. Более общие свойства — в stage-1.
ChALkeRx
19.07.2016 14:42+1А, извините, это перевод. Надо будет задать эти вопросы в оригинальный репозиторий, если не забуду.
Upd: по крайней мере последний вопрос — к переводу, в оригинале:
Functions defined using the static keyword implement static/class functions on the class.
EtnoLover
19.07.2016 15:38А как бы перевели вы эту фразу? ("Functions defined using the static keyword implement static/class functions on the class")
EtnoLover
19.07.2016 14:46Спасибо за комментарии.
По поводу прописных букв написано на сайте MDN в части с примерами, если переводить дословно — "использование всех прописных букв является общим соглашением".
"Common convention is to use all-uppercase letters"ChALkeRx
19.07.2016 15:14+1Документация с сайта MDN работает по принципу вики, и хотя в целом она адекватна, там есть весьма спорные вещи. Не следует ей всегда слепо верить.
За ссылку спасибо. =)
ChALkeRx
19.07.2016 15:20+1Конкретно эту правку внёс https://github.com/agutoli, и это был его единственный вклад в MDN.
Оттуда она и перекочевала в ваш документ.
На самом деле общепринятая практика — это eslint c prefer-const, с которым в коде почти все переменные будут
const
. И прописными буквами их все никто не называет.
gearbox
19.07.2016 15:02Ну как бы прописные для констант имхо со времен С идет. Вы потом в коде как определять будете что это константа и в нее писать не надо? Держать в голове? Зачем?
ChALkeRx
19.07.2016 15:10Дело в том, что в коде на яваскрипте большая часть переменных — фактически константы (не переприсваиваются). Вы предлагаете всё именовать большими буквами?
EtnoLover
19.07.2016 15:34+3Мне нравится подход, описанный на сайте learn.javascript.ru:
Константы, которые жёстко заданы всегда, во время всей программы, обычно пишутся в верхнем регистре. Например: const ORANGE = "#ffa500".
Большинство переменных – константы в другом смысле: они не меняются после присвоения. Но при разных запусках функции это значение может быть разным. Для таких переменных можно использовать const и обычные строчные буквы в имени.
ChALkeRx
19.07.2016 15:36+1То, что написано там — имеет смысл, да. Причём не только для переменных, но и для свойств объектов.
Но к ключевому слову
const
это имеет очень слабое отношение.
gearbox
19.07.2016 16:22+1Нет конечно (хотя про большую часть я бы поспорил). Но переменная, которая не переприсваивается — это не константа. Она не переприсваивается в силу алгоритма работы и при смене алгоритма может быть изменена. Константа — не переприсваивается в силу алгоритма и при доработках — надо иметь в виду что изначально в нее не планировалось ничего писать — не потому что так вышло при реализации а потому что так изначально планировалось. И этот момент неплохо бы видеть при внесении правок. Поэтому данные из конфига — обычно в верхнем регистре. А временные переменные, рабочие лошадки — в нижнем. Но это соглашение, не более того. Оно придумано для облегчения жизни, и если его соблюдение лично Вам жизнь не облегчает — нет смысла его придерживаться.
ChALkeRx
19.07.2016 17:17+1Нет конечно (хотя про большую часть я бы поспорил).
Зашёл в первый попавшийся проект, посчитал — 90.6%
const
, 9.4%let
. Общую статистику по всем подводить как-то не хочется, но если хотите — посчитайте =). Но в таком проекте, где сvar
слезли уже.
Всё, что вы дальше говорите — к
const
не имеет отношения. Дело как раз в том, чтоconst
— это не то же самое множество, что константы, которые именуются большими буквами. Те могут быть какvar
в ES5, так и вообще свойствами какого-то объекта вродеMyLib.CONSTANT_NAME
. Аconst
как раз используется для тех переменных, которые не собираются переприсваивать, и говорить «для переменныхconst
названия пишите в верхнем регистре» — глупость несусветная.
Пример:
for (const name of names) { … }
Тут я точно знаю с первого взляда на код, что
name
внутри блока — всегда один и тот же и не изменяется волшебным алгоритмом. Я его что, доженNAME
назвать?gearbox
20.07.2016 16:37и говорить «для переменных const названия пишите в верхнем регистре» — глупость несусветная.
А кто такое сказал?
Я его что, дожен NAME назвать?
Нет, Вы мне ничего не должны. Я Вам тоже.
ChALkeRx
20.07.2016 22:11+1А кто такое сказал?
Да в статье ж русским по белому написано:
При использовании
const
рекомендуется использовать ПРОПИСНЫЕ_БУКВЫ.gearbox
21.07.2016 12:46-1При использовании const рекомендуется
и говорить «для переменных const названия пишите в верхнем регистре»
разницу видите? Вы сами придумали глупость и сами ее глупостью назвали.
ChALkeRx
21.07.2016 17:20+1[зануда-моуд]
Хорошо. Рекомендовать использовать прописные буквы для именования всех без разбораconst
-переменных — глупость.
[/зануда-моуд]
Так лучше?
gearbox
21.07.2016 21:16Вы рано зануду выключили, я только прогреваться стал )
Рекомендация — то же, что совет, пожелание или предписание, высказанное в необязательной форме
С вики конечно, но сути не меняет.
Необязательная форма подразумевает нестрогое исполнение, то есть по сути — по обстоятельствам. А никак не всех без разбора. Поэтому есть рекомендации, есть стандарты, в стандартах не просто так расписывают в начале should, must, may.ChALkeRx
21.07.2016 22:49+1Да, но только отношение
const
к константам, которые действительно стоит именовать заглавными буквами — примерно как между тапками и красным. Тапки бывают красные, бывают не красные. Красные бывают тапки, бывают не тапки.
Так стоит ли в статье-инструкции по рукоделию для новичков включать пункт «тапки красьте в красный цвет»?
raveclassic
22.07.2016 00:37Тапки бывают красные, бывают не красные. Красные бывают тапки, бывают не тапки.
Какая классная фраза!
А по теме, тоже придерживаюсь именования заглавными констант уровня модуля, а «на месте» — строчными.
gearbox
22.07.2016 14:50Новички — это будущие профессионалы и не надо к ним относиться как к бабочкам. Есть краска, есть тапки, есть кисточка. Они могут красить тапки в красный цвет, могут не красить. А могут и не тапки. Могут не кисточкой. Могут забить на программирование и пойти красить заборы — тоже польза.
dom1n1k
19.07.2016 15:47+3Symbol не понимаю
NeoCode
19.07.2016 16:29+1Да, тоже интересно было бы что бы кто нибудь понимающий раскрыл суть этой концепции.
Я не разработчик на JavaScript, но языки программирования вообще — мой интерес, и поэтому я сталкивался с этой концепцией где-то еще… но так и не понял в чем фишка.
А вообще очень приятные фичи, рад за JavaScript.
impwx
19.07.2016 18:30+1Допустим, у вас есть объект
{ x: 1 }
. Поскольку строки сравниваются по значению, любой, кто в состоянии написать буквуx
, будет иметь доступ к этому свойству объекта.
С другой стороны, каждыйSymbol
является уникальным:
var a = Symbol("a"); var b = Symbol("a"); console.log(a === a); // true console.log(a === b); // false
УSymbol
есть еще две особенности. Во-первых, его можно (и нужно!) использовать в качестве ключа объекта. Кроме него ключами могут быть только числа и строки, никакие другие объекты использовать нельзя. Во-вторых, при перечислении содержимого объекта (for(var x in obj) ...
) свойства с символьными ключами не указываются.
Таким образом, можно создать уникальный идентификатор свойства и сохранить его в локальную переменную. Свойство будет содержаться в объекте, но обратиться к нему можно будет только если есть доступ к этому идентификатору. Получаются «почти что» приватные поля:
function createPerson(age) { var agePty = Symbol("age"); var person = { }; person[agePty] = age; // обращение по уникальному Symbol person.getAge = function() { return person[agePty]; }; person.birthday = function() { person[agePty]++; }; return person; // agePty больше не видна } var me = createPerson(21); person.getAge(); // 21 person.birthday(); person.getAge(); // 22 me["age"]; // undefined - недоступно me[Symbol("age")]; // также недоступно
Я говорю «почти» приватные, потому что список символов в объекте всё равно можно получить через специальный API.dom1n1k
19.07.2016 19:24+1Ну если это аналог private-свойств у экземпляров классов, то… как-то это костылями попахивает, честно говоря.
impwx
19.07.2016 21:35-2Вы правы. Но поскольку сама архитектура языка полностью состоит из костылей, оно даже как-то гармонично вписывается.
raveclassic
20.07.2016 00:40+1Не совсем. Вы можете через символ скрыть какую-то сервисную информацию на объекте, которую потом, имея доступ к этому символу (например через импорт в другом модуле), эту информацию по символу из объекта достать.
Ну и, опять же, каждый символ уникален, даже с одним и тем же описанием. Это дает гарантии на уникальность полей.
Мне, например, приглянулся способ «наладить общение» между декораторами в библиотеке, когда у одного из них есть доступ к символу другого, чтобы скорректировать по наличию этого символа на прототипе свою работу.dom1n1k
20.07.2016 02:32А как это будет фолбечиться в ES5?
raveclassic
20.07.2016 09:33+1Есть вменяемый полифилл для символов в составе core-js. Ну и на MDN достаточно радостные новости
Large
19.07.2016 23:05+1Полезен чтоб разделить методы и данные, убрать коллизию имен, привести API к одному виду. Хороший замах на перегрузку операторов (возможно появится как раз благодаря символу (уже сейчас можно переопределять например for of с помощью Symbol.iterator)).
Large
19.07.2016 23:10Если рассматривать это как шпаргалку — хорошо бы добавить разделы про Reflection и Proxy.
yarkov
20.07.2016 00:03Огромное спасибо за перевод. Все таки на родном языке читать приятнее и понятнее, чем на буржуйском ))
Demogor
20.07.2016 00:28+1Спасибо большое за перевод, весьма интересно.
Небольшой хинт — spread еще весьма удобно использовать для фокусов в стиле пуша массива в массив:
var a=[1,2,3];
var b=[4,5];
a.push(...b); //a==[1,2,3,4,5];preston_f
21.07.2016 13:28+1Есть же старый добрый «concat»
a = a.concat(b);
Кроме того у push`a есть ограничение на количество аргументов (для FF 47 — 5e5, для Ya 16 — 2.5e5).
Да и работает concat куда быстрее — jsfiddleiShatokhin
22.07.2016 00:48+1concat создает новый массив. Также spread работает не только с массивами, но и массиводоподобными элементами, например, arguments.
p.s. «a.push.apply(a, b)» работает быстрее, чем concat.preston_f
25.07.2016 00:53+1У concat тоже есть плюс — он может объединять сразу больше 2-х массивов. А приведенный вами же пример «a.push.apply(a, b)» сводит на нет, ваш первый комментарий. Т.к. «apply» также прекрасно работает с массиводоподобными объектами.
P.S. spread работает не с массиводоподобными, а с итерируемыми объектами! Т. е. если просто создадите объект с числовой последовательность в качестве ствойств и свойство «length», то spread работать не будет ( Exception: TypeError: obj is not iterable ).
titulusdesiderio
Огромное человеческое спасибо!
Готовлюсь к собеседованию — и в планах было выделить сегодняшний день на ES2015. А тут ты с переводом. Как знал! как знал.
EtnoLover
Очень рад, что перевод оказался вовремя.
P.S. Для собеседования детали лучше посмотреть в учебнике Ильи Кантора, там больше подробностей.
Aingis
Опыт говорит, что на собеседованиях про ES6 практически не спрашивают. Максимум: «работал?» или что-то вроде «как работает let». Хотя, на мой взгляд, знать полезно. Порой function foo(...args) {...} или Array.from(...) писать проще чем [].prototype.slice.call(...) и т.п.
titulusdesiderio
Согласен. У меня и нет цели освоить весь современный стандарт за сегодня. Хочу лишь ознакомиться. А такая краткая выжимка — самое подходящее для этой цели.