Лекция
Привет, Вы узнаете о том , что такое Новое в ECMAScript 6. Деструктурирующее присваивание, спред (spread) и рест (Rest) операторы, Разберем основные их виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое Новое в ECMAScript 6. Деструктурирующее присваивание, спред (spread) и рест (Rest) операторы , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).
Новые добавления в язык называются ECMAScript 6. Или ES6 или ES2015+.
С момента появления в 1995, JavaScript развивался медленно. Новые возможности добавлялись каждые несколько лет. ECMAScript появился в 1997, его целью было направить развитие JavaScript в нужное русло. Выходили новые версии – ES3, ES5, ES6 и так далее.
Как видите, между версиями ES3, ES5 и ES6 есть пропуски длиной в 10 и 6 лет. Новая модель – делать маленькие изменения каждый год. Вместо того, чтобы накопить огромное количество изменений и выпустить их все за раз, как это было с ES6.
Деструктурирующее присваивание – это специальный синтаксис, который позволяет «распаковать» массивы или объекты в набор переменных, так как иногда они более удобны. Деструктуризация также прекрасно работает со сложными функциями, которые имеют много параметров, значений по умолчанию и так далее.
Пример деструктуризации массива:
// у нас есть массив с данными город, страна let arr = ["Амстердам", "Нидерланды"] // деструктурирующее присваивание // записывает аналогично city=arr[0], country=arr[1] let [city, country] = arr; console.log(city); console.log(country);
выведет
Амстердам
Нидерланды
Теперь мы можем использовать переменные вместо элементов массива.
Отлично смотрится в сочетании со split или другими методами, возвращающими массив:
let [city, surname] = "Арсметрдам,Нидерланды".split(',');
Деструктуризация и сопоставление параметров
ES5 var user = {firstName: 'Adrian', lastName: 'Mejia'}; function getFullName(user) { var firstName = user.firstName; var lastName = user.lastName; return firstName + ' ' + lastName; } console.log(getFullName(user)); // Adrian Mejia
То же самое (но короче):
ES6 const user = {firstName: 'Adrian', lastName: 'Mejia'}; function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; } console.log(getFullName(user)); // Adrian Mejia
Глубокое сопоставление
ES5 function settings() { return { display: { color: 'red' }, keyboard: { layout: 'querty'} }; } var tmp = settings(); var displayColor = tmp.display.color; var keyboardLayout = tmp.keyboard.layout; console.log(displayColor, keyboardLayout); // red querty
То же самое (но короче):
ES6 function settings() { return { display: { color: 'red' }, keyboard: { layout: 'querty'} }; } const { display: { color: displayColor }, keyboard: { layout: keyboardLayout }} = settings(); console.log(displayColor, keyboardLayout); // red querty
Это также называют деструктуризацией объекта (object destructing).
Как видите, деструктуризация может быть очень полезной и может подталкивать к улучшению стиля кодирования.
Советы:
Новый оператор ... называется spread (распростанение, расширение) или rest (остаток) в зависимости от того, где и как он используется.
Начнем сразу с примера:
var log = function(a, b, c) { console.log(a, b, c); }; log(...['Spread', 'Rest', 'Operator']); // Spread Rest Operator
В данном примере переданный в функцию массив разделяется на три значения: Spread, Rest и Operator, после чего передаются функции log. Таким образом, оператор ..., используемый перед массивом, или любой другой коллекцией значений (строки, объекты), называет spread.
Подобным образом использовать массив можно было и до появления оператора ... с помощью метода функций apply:
log.apply(null, [1, 2, 3]); // 1 2 3 // Равнозначно log(...[1, 2, 3]); // 1 2 3
раньше для этого можно было испольовать переменную arguments, однако стрелочные функции не имеют "arguments"
Использование оператора spread не ограничивается передачей параметров функции. Несколько примеров его полезного использования:
Клонирование свойств массивов
var arr = ['will', 'love']; var data = ['You', ...arr, 'spread', 'operator']; console.log(data); // ['You', 'will', 'love', 'spread', 'operator']
Подобным образом можно скопировать и весь массив целиком
var arr = [1, 2, 3, 4, 5]; var data = [...arr]; console.log(data); // [1, 2, 3, 4, 5]
Важно понимать, что при подобном использовании оператора ... происходит именно копирование всех свойств, а не ссылки на массив.
var arr = [1, 2, 3, 4, 5]; var data = [...arr]; var copy = arr; arr === data; // false − ссылки отличаются − два разных массива arr === copy; // true − две переменные ссылаются на один массив
Преобразование коллекции DOM элементов
Раньше для преобразования коллекций в массивы приходилось использовать подобные конструкции:
var links = document.querySelectorAll('a'); var linksArr = Array.slice.call(links); // Или более короткий вариант var linksArr = [].slice.call(document.links); Array.isArray(links); // false Array.isArray(linksArr); // true
Подобные преобразования очень распространены, но не могут похвастаться элегантностью и, тем более, очевидностью − если подобную конструкцию увидит разработчик, никогда не использовавший ее самостоятельно, то его шансы понять происходящее стремятся к нулю.
Преобразование DOM коллекции в массив с помощью оператора spread выглядит следующим образом:
var links = [...document.querySelectorAll('a')]; // Или просто var links = [...document.links]; Array.isArray(links); // true
Замена apply для функций конструкторов
Проще всего продемонстрировать использование ..., как замену apply для функций конструкторов, на примере Date. Об этом говорит сайт https://intellect.icu . Обычно конструктор Date работает подобным образом:
var birthday = new Date(1993, 3, 24); // 24 Апреля 1993 года console.log(birthday); // "1993-04-23T21:00:00.000Z"
Все работает хорошо до тех пор, пока нет необходимости передать массив [1993, 3, 24] в конструктор Date. В данной ситуации вам пришлось бы написать “костыль”:
// Никогда не используйте подобный код − он абсолютно безумен new (Date.bind.apply(Date, [null].concat([1993, 3, 24]))); // 24 Апреля 1993 года
ES6 дает возможность избежать подобных ситуаций:
var day = [1993, 3, 24]; var birthday = new Date(...day);
В самом начале статьи я уже упоминал, что оператор ... интерпретируется по-разному, в зависимости от контекста применения. Spread используется для разделения коллекций на отдельные элементы, а rest, наоборот, для соединения отдельных значений в массив.
var log = function(a, b, ...rest) { console.log(a, b, rest); }; log('Basic', 'rest', 'operator', 'usage'); // Basic rest ['operator', usage]
Используя параметр ...rest в функции log вы говорите интерпретатору: “собери все оставшиеся элементы в массив с именем rest”. Разумеется, если вы не передадите в функцию других именновах параметров, то ... соберет все аргументы:
var log = function(...args) { conole.log(args); }; log(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]
Таким образом, больше нет необходимости переводить псевдо-массив arguments функций в настоящий массив − оператор rest сделает это сам:
// Раньше var sum = function() { var args = [].slice.call(arguments); return args.reduce(function(s, num) { return s += num; }, 0); }; // Теперь var sum = function(..args) { return args.reduce(function(s, num) { return s + num; }, 0); }; // Еще короче с использованием стрелочных функций var sum = (...args) => args.reduce((s, num) => s + num, 0);
Когда мы видим "..." в коде, это могут быть как остаточные параметры, так и оператор расширения.
Как отличить их друг от друга:
Полезно запомнить:
Вместе эти конструкции помогают легко преобразовывать наборы значений в массивы и обратно.
К аргументам функции можно обращаться и по-старому — через псевдомассив arguments.
От аргументов к rest-параметрам и операции spread.
В ES5 работать с переменным количеством аргументов неудобно.
ES5 function printf(format) { var params = [].slice.call(arguments, 1); console.log('params: ', params); console.log('format: ', format); } printf('%s %d %.2f', 'adrian', 321, Math.PI);
С rest ... все намного проще.
ES6 function printf(format, ...params) { console.log('params: ', params); console.log('format: ', format); } printf('%s %d %.2f', 'adrian', 321, Math.PI);
В ES6 мы перешли от var к let/const.Для чего это почему ельзя всегда использоватьс var?
Проблема var в том, что переменная "протекает" в другие блоки кода, такие как циклы for или блоки условий if:
ES5 var x = 'outer'; function test(inner) { if (inner) { var x = 'inner'; // scope whole function return x; } return x; // gets redefined on line 4 } test(false); // undefined test(true); // inner
В строке test(false) можно ожидать возврат outer, но нет, мы получаем undefined. Почему?
Потому что даже не смотря на то, что блок if не выполняется, на 4й строке происходит переопределение var x как undefined.
ES6 спешит на помощь:
ES6 let x = 'outer'; function test(inner) { if (inner) { let x = 'inner'; return x; } return x; // gets result from line 1 as expected } test(false); // outer test(true); // inner
Изменив var на let мы откорректировали поведение. Если блок if не вызывается, то переменная x не переопределяется.
IIFE (immediately invoked function expression)
Давайте сначала рассмотрим пример:
ES5 { var private = 1; } console.log(private); // 1
Как видите, private протекает наружу. Нужно использовать IIFE (immediately-invoked function expression):
ES5 (function(){ var private2 = 1; })(); console.log(private2); // Uncaught ReferenceError
Если взглянуть на jQuery/lodash или любые другие проекты с открытым исходным кодом, то можно заметить, что там IIFE используется для содержания глобальной среды в чистоте. А глобальные штуки определяются со специальными символами вроде _, $ или jQuery.
В ES6 не нужно использовать IIFE, достаточно использовать блоки и let:
ES6 { let private3 = 1; } console.log(private3); // Uncaught ReferenceError
Const
Можно также использовать const если переменная не должна изменяться.
Итог:
Не нужно больше делать вложенную конкатенацию, можно использовать шаблоны. Посмотрите:
ES5 var first = 'Adrian'; var last = 'Mejia'; console.log('Your name is ' + first + ' ' + last + '.');
С помощью бэктика () и интерполяции строк ${}` можно сделать так:
ES6 const first = 'Adrian'; const last = 'Mejia'; console.log(`Your name is ${first} ${last}.`);
Не нужно больше конкатенировать строки с + \n:
ES5 var template = '
\n' + ' \n' + ' \n' + '\n' + '
\n' + ' \n' + '
В ES6 можно снова использовать бэктики:
ES6 const template = `
Оба блока кода генерируют одинаковый результат
В ECMAScript 6 перешли от “функций-конструкторов” к “классам” .
Каждый объект в JavaScript имеет прототип, который является другим объектом. Все объекты в JavaScript наследуют методы и свойства от своего прототипа.
В ES5 объектно-ориентированное программирование достигалось с помощью функций-конструкторов. Они создавали объекты следующим образом:
ES5 var Animal = (function () { function MyConstructor(name) { this.name = name; } MyConstructor.prototype.speak = function speak() { console.log(this.name + ' makes a noise.'); }; return MyConstructor; })(); var animal = new Animal('animal'); animal.speak(); // animal makes a noise.
В ES6 есть новый синтаксический сахар. Можно сделать то же самое с меньшим кодом и с использованием ключевых слов class и construсtor. Также заметьте, как четко определяются методы: construсtor.prototype.speak = function () vs speak():
ES6 class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } const animal = new Animal('animal'); animal.speak(); // animal makes a noise.
Оба стиля (ES5/6) дают одинаковый результат.
Советы:
Давайте продолжим предыдущий пример с классом Animal. Допустим, нам нужен новый класс Lion.
В ES5 придется немного поработать с прототипным наследованием.
ES5 var Lion = (function () { function MyConstructor(name){ Animal.call(this, name); } // prototypal inheritance MyConstructor.prototype = Object.create(Animal.prototype); MyConstructor.prototype.constructor = Animal; MyConstructor.prototype.speak = function speak() { Animal.prototype.speak.call(this); console.log(this.name + ' roars '); }; return MyConstructor; })(); var lion = new Lion('Simba'); lion.speak(); // Simba makes a noise. // Simba roars.
Не будем вдаваться в детали, но заметьте несколько деталей:
В ES6 есть новые ключевые слова extends и super.
ES6 class Lion extends Animal { speak() { super.speak(); console.log(this.name + ' roars '); } } const lion = new Lion('Simba'); lion.speak(); // Simba makes a noise. // Simba roars.
Посмотрите, насколько лучше выглядит код на ES6 по сравнению с ES5. И они делают одно и то же! Win!
Совет:
Переходим от callback hell к промисам (promises)
ES5 function printAfterTimeout(string, timeout, done){ setTimeout(function(){ done(string); }, timeout); } printAfterTimeout('Hello ', 2e3, function(result){ console.log(result); // nested callback printAfterTimeout(result + 'Reader', 2e3, function(result){ console.log(result); }); });
Одна функция принимает callback чтобы запустить его после завершения. Нам нужно запустить ее дважды, одну за другой. Поэтому приходится вызывать printAfterTimeout во второй раз в коллбеке.
Все становится совсем плохо когда нужно добавить третий или четвертый коллбек. Давайте посмотрим, что можно сделать с промисами:
ES6 function printAfterTimeout(string, timeout){ return new Promise((resolve, reject) => { setTimeout(function(){ resolve(string); }, timeout); }); } printAfterTimeout('Hello ', 2e3).then((result) => { console.log(result); return printAfterTimeout(result + 'Reader', 2e3); }).then((result) => { console.log(result); });
С помощью then можно обойтись без вложенных функций.
В ES5 обычные определения функций не исчезли, но был добавлен новый формат – стрелочные функции.
В ES5 есть проблемы с this:
ES5 var _this = this; // need to hold a reference $('.btn').click(function(event){ _this.sendData(); // reference outer this }); $('.input').on('change',function(event){ this.sendData(); // reference outer this }.bind(this)); // bind to outer this
Нужно использовать временный this чтобы ссылаться на него внутри функции или использовать bind. В ES6 можно просто использовать стрелочную функцию!
ES6 // this will reference the outer one $('.btn').click((event) => this.sendData()); // implicit returns const ids = [291, 288, 984]; const messages = ids.map(value => `ID is ${value}`);
От for переходим к forEach а потом к for...of:
ES5 // for var array = ['a', 'b', 'c', 'd']; for (var i = 0; i < array.length; i++) { var element = array[i]; console.log(element); } // forEach array.forEach(function (element) { console.log(element); });
ES6 for…of позволяет использовать итераторы
ES6 // for ...of const array = ['a', 'b', 'c', 'd']; for (const element of array) { console.log(element); }
От проверки параметров переходим к параметрам по умолчанию. Вы делали что-нибудь такое раньше?
ES5 function point(x, y, isFlag){ x = x || 0; y = y || -1; isFlag = isFlag || true; console.log(x,y, isFlag); } point(0, 0) // 0 -1 true point(0, 0, false) // 0 -1 true point(1) // 1 -1 true point() // 0 -1 true
Скорее всего да. Это распространенный паттерн проверки наличия значения переменной. Но тут есть некоторые проблемы:
Если параметр по умолчанию это булева переменная или если задать значение 0, то ничего не получится. Почему? Расскажу после этого примера с ES6 ;)
В ES6 все получается лучше с меньшим количеством кода:
ES6 function point(x = 0, y = -1, isFlag = true){ console.log(x,y, isFlag); } point(0, 0) // 0 0 true point(0, 0, false) // 0 0 false point(1) // 1 -1 true point() // 0 -1 true
Получаем ожидаемый результат. Пример в ES5 не работал. Нужно проверять на undefined так как false, null, undefined и 0 – это все falsy-значения. С числами можно так:
ES5 function point(x, y, isFlag){ x = x || 0; y = typeof(y) === 'undefined' ? -1 : y; isFlag = typeof(isFlag) === 'undefined' ? true : isFlag; console.log(x,y, isFlag); } point(0, 0) // 0 0 true point(0, 0, false) // 0 0 false point(1) // 1 -1 true point() // 0 -1 true
С проверкой на undefined все работает как нужно.
Исследование, описанное в статье про Новое в ECMAScript 6. Деструктурирующее присваивание, спред (spread) и рест (Rest) операторы, подчеркивает ее значимость в современном мире. Надеюсь, что теперь ты понял что такое Новое в ECMAScript 6. Деструктурирующее присваивание, спред (spread) и рест (Rest) операторы и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Комментарии
Оставить комментарий
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)