Сегодня у нас есть возможность писать на ES6 и переносить наши исходники на ES5 во время сборки, независимо от того, используем ли мы Grunt, Gulp или Broccoli. Поскольку такие проекты, как Ember и Angular, тоже рассматривают ES6 как часть своих дорожных карт (Ember App Kit уже поддерживает модули ES6!), Сейчас самое время узнать, как ES6 можно использовать на практике.

В этом руководстве мы перепишем хорошо известное приложение TodoMVC (реализованное с помощью Backbone.js) с использованием семантики языка ECMAScript 6. Реализация стала возможной с использованием компилятора Google Traceur и ES6-Module-Loader.

Если вы раньше не сталкивались с этими инструментами, Traceur - это компилятор JavaScript.next to-JavaScript-of-today, который позволяет вам использовать функции будущего уже сегодня, а загрузчик модулей ES6 динамически загружает модули ES6 в Node.js и текущие браузеры. Если вам интересно, готов ли Traceur к приложениям производственного уровня, Эрик Арвидссон недавно ответил на это в их списке рассылки.

Охваченные функции

Функции, которые мы будем использовать в этом пошаговом руководстве, включают, но не ограничиваются:

Источник и демонстрации

Вы можете запустить готовое приложение, просмотреть версию учебника Docco (app.html и todo-app.html) посмотреть репозиторий проекта или посмотреть исходную реализацию ES5.

Приложение было переписано на ES6 Адди Османи, Паскалем Хартигом, Синдре Сорхусом, Стивеном Савчуком, Риком Уолдроном, Домеником Дениколой и Гаем Бедфордом.

Начните свое приключение ES6 здесь

Наше приложение состоит в основном из трех файлов:

  • index.html - содержащий наши шаблоны и начальную загрузку модуля.
  • app.js - наша основная точка входа в приложение
  • todo-app.js - наш модуль Todos

Он также состоит из таблиц стилей и, конечно, зависимостей Backbone, Traceur и ES6 Module Loader, но в этом руководстве мы сосредоточимся на частях приложения ES6.

Индекс (index.html)

Сначала мы динамически загружаем наше основное приложение - скрипт, расположенный в js / app.js.

System.import - это асинхронный загрузчик для загрузки таких модулей, который не требует от нас включения расширения в наш скрипт. Это автоматически выводится:

Для этого приложения мы будем использовать два файла сценария: js / app.js и js / todo-app.js. Это модули ES6.

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

Точка входа в приложение (app.js)

Импорт

Мы импортируем классы, которые мы определили в модуле TodoApp, используя ключевое слово import.

import {AppView, Filters} from './todo-app';

Документ готов

Стрелочные функции (утверждения)

Затем мы загружаем приложение, как только модель DOM будет готова, используя jQuery.ready () = ›{…}, который, как вы увидите ниже, представляет собой форму оператора синтаксиса стрелочной функции. Фактически, это легкий сахар для функции () {…} .bind (this).

Помимо содержащих операторы вместо автоматически возвращаемого выражения, он имеет те же свойства, что и функции стрелок в форме выражения, которые мы рассмотрим ниже:

$(() => { // Finally, we kick things off by creating the App. new AppView(); new Filters(); Backbone.history.start(); });

Модуль TodoApp (todo-app.js)

Разрушающие присвоения

Объявления констант (const) имеют блочную область видимости и не могут быть повторно объявлены или переназначены их значения после инициализации привязки. Определения основных компонентов Backbone не нужно изменять, поэтому мы можем комбинировать константы и шаблон ES6, называемый деструктурирующим присваиванием для создания более коротких псевдонимов для моделей, представлений и других компонентов.

Это избавляет от необходимости использовать более подробные формы Backbone. *, К которым мы привыкли. При деструктуризации массивов и объектных данных используется синтаксис, который отражает построение массивов и объектных литералов.

const в настоящее время отключен в Traceur из-за https://github.com/google/traceur-compiler/issues/595, но в противном случае он был бы записан как:

const { Model, View, Collection, Router, LocalStorage } = Backbone;

Сейчас мы будем использовать var, так как нам нужна функциональная реализация:

var { Model, View, Collection, Router, LocalStorage } = Backbone; // Note, if Backbone was written with ES6 you // would use an import for this. var ENTER_KEY = 13; // const var TodoFilter = ''; // let

Класс модели Todo

Классы

В JavaScript мы полагались на прототипное наследование всякий раз, когда нам требовалась система, подобная классу. Это привело к чрезмерно подробному коду с использованием настраиваемых типов. ES6 меняет это, удаляя уродливые шаблоны многоэтапного наследования, к которым мы привыкли, и вводя минимальный синтаксис классов, который делает определение классов намного более кратким.

Классы ES6 негласно скрывают наследование прототипов, и единственное реальное изменение состоит в том, что нам нужно меньше печатать. Классы компактны, и мы можем использовать ключевое слово «extends» для реализации нового подкласса из базового класса. Ниже мы делаем это, чтобы определить класс Todo, который расширяет компонент Backbone Model.

class Todo extends Model { // Note the omission of the 'function' keyword // Method declaractions inside ES6 classes don't // use it // Define some default attributes for the todo. defaults() { return { title: '', completed: false }; } // Toggle the `completed` state of this todo item. toggle() { this.save({ completed: !this.get('completed') }); } }

Класс TodoList Collection

Мы определяем новый класс TodoList, расширяющий Backbone's `Collection. Коллекция задач поддерживается localStorage вместо удаленного сервера.

class TodoList extends Collection {

Конструкторы и суперконструкторы

Указание конструктора позволяет нам определить конструктор класса. Использование ключевого слова super в вашем конструкторе позволяет вызывать конструктор родительского класса, чтобы он мог наследовать все его свойства.

constructor(options) { super(options); // Hold a reference to this collection's model. this.model = Todo; // Save all of the todo items under the 'todos' namespace. this.localStorage = new LocalStorage('todos-traceur-backbone'); }

Стрелочные функции (выражения)

Стрелка (= ›) - это сокращенный синтаксис анонимной функции. Ключевое слово function не требуется, а скобки необязательны, если используется единственный параметр. Значение this привязано к содержащейся в нем области, и когда выражение следует за стрелкой, как в этом случае, функция стрелки автоматически возвращает значение этого выражения, поэтому вам не нужно возвращать.

Стрелочные функции более легкие, чем обычные функции, что отражает предполагаемое их использование - у них нет прототипа и они не могут выступать в качестве конструкторов. Из-за того, как они наследуют this от содержащей его области, значение this внутри них не может быть изменено с помощью call или apply.

Напомним, при использовании = ›:

  • Ключевое слово функции не требуется.
  • Скобки необязательны для одного параметра.
  • return не требуется с одним выражением.
  • Стрелочные функции легкие - никаких прототипов или конструкторов

Мы используем стрелочную функцию ниже для нашего метода, который устанавливает элемент Todo как завершенный:

completed() { return this.filter(todo => todo.get('completed')); }

Затем мы рассмотрим фильтрацию списка, чтобы выполнить только те элементы, которые еще не завершены.

remaining() { // The ES6 spread operator reduces runtime boilerplate  // code by allowing an expression to be expanded where  // multiple arguments or elements are normally expected.  // It can appear in function calls or array literals. // The three dot syntax below is to indicate a variable // number of arguments and helps us avoid hacky use of // `apply` for spreading. // Compare the old way... return this.without.apply(this, this.completed()); // ...with the new, significantly shorter way... return this.without(...this.completed()); // This doesn't require repeating the object on which  // the method is called - (`this` in our case). }

Мы храним Todos в последовательном порядке, несмотря на то, что они сохраняются с помощью неупорядоченного GUID в базе данных. Это создает следующий номер заказа для новых товаров.

nextOrder() { if (!this.length) { return 1; } return this.last().get('order') + 1; }

Задачи сортируются по исходному порядку размещения с помощью простого компаратора.

comparator(todo) { return todo.get('order'); } }

Создайте нашу коллекцию Todos.

var Todos = new TodoList();

Класс Todo Item View

Мы создаем класс TodoView, расширяя Backbone’s View следующим образом:

class TodoView extends View { constructor(options) { super(options); // The DOM element for a todo item // is a list tag. this.tagName = 'li';

Затем мы настраиваем кешированный экземпляр нашего шаблона и события прослушивателя DOM для отдельных элементов:

// Cache the template function for a single item. this.template = _.template($('#item-template').html()); this.input = ''; // Define the DOM events specific to an item. this.events = { 'click .toggle': 'toggleCompleted', 'dblclick label': 'edit', 'click .destroy': 'clear', 'keypress .edit': 'updateOnEnter', 'blur .edit': 'close' }; this.listenTo(this.model, 'change', this.render); this.listenTo(this.model, 'destroy', this.remove); this.listenTo(this.model, 'visible', this.toggleVisible); }

Повторно отобразите содержимое задачи.

render() { this.$el.html(this.template(this.model.toJSON())); this.$el.toggleClass('completed', this.model.get('completed')); this.toggleVisible(); this.input = this.$('.edit'); return this; } toggleVisible() { this.$el.toggleClass('hidden', this.isHidden); }

Свойства аксессуара

isHidden () - это свойство доступа получения, но обычно его называют «получателем». Хотя это технически часть ECMAScript 5.1, геттеры и сеттеры позволяют нам писать и читать свойства, которые лениво вычисляют свои значения. Свойства могут обрабатывать значения, присвоенные на этапе постобработки, проверять и преобразовывать во время присвоения.

В общем, это означает использование set and get для привязки свойства объекта к функции, которая вызывается при установке и поиске свойства. Подробнее о свойствах аксессуаров или геттерах и сеттерах.

get isHidden() { var isCompleted = this.model.get('completed'); return (hidden cases only (!isCompleted && TodoFilter === 'completed') || (isCompleted && TodoFilter === 'active') ); }

Переключить состояние модели "завершено".

toggleCompleted() { this.model.toggle(); }

Переключите это представление в режим «редактирования» с отображением поля ввода.

edit() { var value = this.input.val(); this.$el.addClass('editing'); this.input.val(value).focus(); }

Закройте режим «редактирования», сохранив изменения в задаче.

close() { var title = this.input.val(); if (title) { // Note that here we use the new  // object literal property value  // shorthand this.model.save({ title }); } else { this.clear(); } this.$el.removeClass('editing'); }

Если вы нажмете "Ввод", мы закончим редактирование элемента.

updateOnEnter(e) { if (e.which === ENTER_KEY) { this.close(); } }

Удалите элемент и уничтожьте модель.

clear() { this.model.destroy(); } }

Класс приложения

Наш общий AppView - это элемент пользовательского интерфейса верхнего уровня.

export class AppView extends View { constructor() {

Вместо создания нового элемента выполните привязку к существующему каркасу приложения, уже присутствующему в HTML.

this.setElement($('#todoapp'), true); this.statsTemplate = _.template($('#stats-template').html()),

Делегируйте события для создания новых элементов и очистки завершенных.

this.events = { 'keypress #new-todo': 'createOnEnter', 'click #clear-completed': 'clearCompleted', 'click #toggle-all': 'toggleAllComplete' };

При инициализации мы связываемся с соответствующими событиями в коллекции Todos, когда элементы добавляются или изменяются. Начните с загрузки любых уже существующих задач, которые могут быть сохранены в localStorage.

this.allCheckbox = this.$('#toggle-all')[0]; this.$input = this.$('#new-todo'); this.$footer = this.$('#footer'); this.$main = this.$('#main'); this.listenTo(Todos, 'add', this.addOne); this.listenTo(Todos, 'reset', this.addAll); this.listenTo(Todos, 'change:completed', this.filterOne); this.listenTo(Todos, 'filter', this.filterAll); this.listenTo(Todos, 'all', this.render); Todos.fetch(); super(); }

Повторный рендеринг приложения означает лишь обновление статистики - остальная часть приложения не меняется.

render() { var completed = Todos.completed().length; var remaining = Todos.remaining().length; if (Todos.length) { this.$main.show(); this.$footer.show(); this.$footer.html( this.statsTemplate({ completed, remaining }) ); this.$('#filters li a') .removeClass('selected') .filter(`[href="#/${TodoFilter || ''}"]`) .addClass('selected'); } else { this.$main.hide(); this.$footer.hide(); } this.allCheckbox.checked = !remaining; }

Добавьте один элемент в список, создав для него представление и добавив его элемент в ‹ul›.

addOne(model) { var view = new TodoView({ model }); $('#todo-list').append(view.render().el); }

Добавить все элементы в коллекцию Todos сразу.

addAll() { this.$('#todo-list').html(''); Todos.each(this.addOne, this); } filterOne(todo) { todo.trigger('visible'); } filterAll() { Todos.each(this.filterOne, this); }

Создайте атрибуты для нового элемента Todo.

newAttributes() { return { title: this.$input.val().trim(), order: Todos.nextOrder(), completed: false }; }

Если вы нажмете Enter в основном поле ввода, создайте новую модель Todo, сохранив ее в localStorage.

createOnEnter(e) { if (e.which !== ENTER_KEY || !this.$input.val().trim()) { return; } Todos.create(this.newAttributes()); this.$input.val(''); }

Очистите все выполненные задачи и уничтожьте их модели.

clearCompleted() { _.invoke(Todos.completed(), 'destroy'); } toggleAllComplete() { var completed = this.allCheckbox.checked; Todos.each(todo => todo.save({ completed })); } }

Класс Filters Router

export class Filters extends Router { constructor() { this.routes = { '*filter': 'filter' } this._bindRoutes(); }

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

param в функции filter () использует поддержку ES6 для значений параметров по умолчанию. Многие языки поддерживают понятие аргумента по умолчанию для функциональных параметров, но JavaScript до сих пор не поддерживал.

Параметры по умолчанию позволяют избежать необходимости указывать собственные значения по умолчанию в теле функции. Мы обошли эту проблему, выполнив проверку логического ИЛИ (||) на значения аргументов по умолчанию, если они пустые / нулевые / неопределенные или неправильного типа. Исходные значения параметров по умолчанию обеспечивают более четкое решение этой проблемы. Примечательно, что они запускаются только неопределенным значением, а не каким-либо ложным значением.

Сравните старый способ…

function hello(firstName, lastName) { firstName = firstName || 'Joe'; lastName = lastName || 'Schmo'; return 'Hello, ' + firstName + ' ' + lastName; }

… На новый способ, где мы также можем добавить строку шаблона ..

function hello(firstName = 'Joe', lastName = 'Schmo') { return `Hello, ${firstName} ${lastName}`; }

которые мы реализуем следующим образом:

filter(param = '') { // Set the current filter to be used. TodoFilter = param; // Trigger a collection filter event,  // causing hiding/unhiding of Todo view  // items. Todos.trigger('filter'); } }

Вот и все. Не забывайте, что вы можете проверить исходный код приложения в репозитории проекта.

Веб-компоненты

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

Отслеживание поддержки ECMAScript 6

ECMAScript 6 постепенно внедряется поставщиками браузеров, и поэтому нет «фиксированной» даты, доступной повсюду.

Пока спецификации и реализации продолжают дорабатываться и поставляться, вы можете найти нижеприведенные ресурсы, полезные для отслеживания того, где мы находимся с поддержкой браузера и среды:

  • Матрица сравнения характеристик реализаций ECMAScript (V8, JSC, JScript и т.д.) Томаса Лана
  • Функции Harmony, доступные в Node, можно перечислить с помощью node —v8-options | grep harmony
  • Таблица совместимости ECMAScript 6 от Kangax
  • Функции Google V8: Harmony с соответствующими открытыми ошибками в трекере (т.е. какие функции ES6 Chrome и Opera будут поддерживать по умолчанию или за флажком about: flags › Включить экспериментальный JavaScript ).
  • Поддержка ECMAScript 6 в Firefox (MDN). На Buzilla также есть мета-ошибка, которую вы можете проверить, и которая немного опасна (HT @domenic). Кроме того, Рик Уолдрон предлагает подписаться на уведомления компонентов от bugzilla, а затем создать фильтр, однако это может быть немного большим объемом.
  • IE Dev Center о поддерживаемых функциях ES6 (поиск лучшего списка для IE11 +)
  • Официальная черновая спецификация ECMAScript 6 (и неофициальная HTML-версия черновика)

Мы рекомендуем вам подписаться на @esdiscuss, чтобы получать сводку о том, что происходит в списке рассылки es-обсуждения, где есть ссылки на последние обсуждения.

Синдре Сорхус также ведет список реального использования функций ECMAScript 6 в современных библиотеках и фреймворках, которые могут представлять интерес.

Инструменты ES6

Если вы хотите глубоко погрузиться в ES6 сегодня, у сообщества есть постоянно обновляемый список инструментов, которые могут помочь в вашем рабочем процессе, на странице https://github.com/addyosmani/es6-tools.

Мы также недавно играли с http://es6fiddle.net, который отлично подходит для экспериментов с ES6 с нулевой настройкой. Попробуйте !.

Подведение итогов

В заключение, мы надеемся, что вы нашли это краткое руководство полезным. Мы очень рады видеть, что ES6 используется для создания большего количества проектов, и приглашаем вас поделиться с нами своим собственным опытом создания ES6 в Twitter.

С большим количеством ❤z,

Адди, Синдре, Паскаль и Стивен.

Особая благодарность Рику Уолдрону и Доменику Деникола за их отзывы.

Первоначально опубликовано на blog.tastejs.com 24 февраля 2014 г.