0,0
рейтинг
6 марта 2015 в 10:32

Разработка → ECMAScript 6 перевод

Границы моего языка олицетворяют границы моего мира.
— Людвиг Витгенштейн

Последние несколько месяцев я пишу только ECMAScript 6 код, воспользовавшись трансформацией [1] в поддерживаемые в настоящее время версии JavaScript.

ECMAScript 6, далее ES6 и ранее ES.next, является последней версией спецификации. По состоянию на август 2014 новые возможности не обсуждаются, но детали и крайние случаи до сих пор уточняются. Ожидается, что стандарт будет завершен и опубликован в середине 2015 года.

Принятие ES6 одновременно привело к повышению производительности (что делает мой код более лаконичным) и ликвидации целого класса ошибок путем устранения распространённых подводных камней JavaScript.

Более того, это подтвердило мою веру в эволюционный подход к языку и проектирования программного обеспечения, в противоположность clean-slate recreation .

Это должно быть достаточно очевидным для вас, если вы использовали CoffeeScript, который сосредотачивается на хороших частях JS и скрывает плохие. ES6 смог принять на столько много инноваций из CoffeeScript, что некоторые даже ставят под сомнение дальнейшее развитие последнего.



Вместо того, чтобы сделать тщательный анализ новых возможностей я расскажу о наиболее интересных из них. Чтобы стимулировать разработчиков обновляться, новые языки и фреймворки должны (1) иметь убедительную историю совместимости и (2) предлагать вам достаточно большую морковку.

# Синтаксис модулей


ES6 знакомит с синтаксисом для определения модулей и объявления зависимостей. Я подчеркиваю слово синтаксис потому что ES6 не имеет отношение к фактической реализации того, как модули будут выбраны или загружены.

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

Рассмотрим в качестве примера простую задачу написания многоразового использования CRC32 в JavaScript.

До сих пор, не существовало никаких рекомендаций о том, как на самом деле решить эту задачу. Общий подход это объявить функцию:

function crc32(){
  // …
}

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

В этой ситуации, Node.JS выбрал путь введения функции require и объектов module.exports и exports . Несмотря на успех в создании преуспевающей экосистемы модулей, возможности взаимодействия по-прежнему были несколько ограничены.

Типичный сценарий, чтобы проиллюстрировать эти недостатки это генерация связки модулей для браузера, с помощью таких инструментов, как browserify или webpack. Они еще находятся в зачаточном состоянии, потому что они воспринимают require() как синтаксис, эффективно избавляя себя от свойственного им динамизма.

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

require(woot() + ‘_module.js’);

Другими словами, алгоритм упаковщика не может знать заранее, что означает woot().

ES6 ввел правильный набор ограничений, учитывая большинство существующих вариантов использования, черпая вдохновение из самых неформально-существующих специальных модульных систем, как jQuery $.

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

Следующий код:

import crc32 from ‘crc32’;

работает для

export default function crc32(){}

но не для

export function crc32(){}

последний считается именованным экспортом и требует синтаксис { } в конструкции import:

import { crc32 } from ‘crc32’;

Другими словами, самая простая (и, пожалуй, наиболее желательная) форма определения модуля требует дополнительное ключевое слово default. Или в случае его отсутствия, использование { } при импорте.

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


Одним из наиболее распространенных шаблонов, возникших в современном JavaScript коде является использование вариантных объектов.

Такая практика широко используется в новых браузерных API, например в WHATWG fetch (современная замена XMLHttpRequest):

fetch(‘/users’, {
  method: ‘POST’,
  headers: {
    Accept: ‘application/json’,
    ‘Content-Type’: ‘application/json’
  },
  body: JSON.stringify({
    first: ‘Guillermo’,
    last: ‘Rauch’
  })
});

Повсеместное принятие этой модели эффективно препятствует падению экосистемы JavaScript в логическую ловушку.

Если принять, что API принимает обычные аргументы, а не объект с параметрами, то вызов fetch превращается в задачу запоминания порядка аргументов и ввода ключевого слова null в нужное место.

// пример ночного кошмара из альтернативного мира
fetch(‘/users’, ‘POST’, null, null, {
  Accept: ‘application/json’,
  ‘Content-Type’: ‘application/json’
  }, null, JSON.stringify({
    first: ‘Guillermo’,
    last: ‘Rauch’
}));

Со стороны реализации, однако, это не выглядит так же красиво. Глядя на объявление функции, ее сигнатура больше не описывает входные возможности:

function fetch(url, opts){
  // …
}

Обычно это сопровождается ручной установкой значений по-умолчанию локальным переменным:

opts = opts || {};
var body = opts.body || '';
var headers = opts.headers || {};
var method = opts.method || 'GET';

И к сожалению для нас, несмотря на свою распространенность, практика использования || фактически привносит трудно выявляемые ошибки. Например, в этом случае мы не допускаем того, что opts.body может быть 0, поэтому надежный код скорее всего будет выглядеть так:

var body = opts.body === undefined ? '' : opts.body;

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

fetch(url, { body='', method='GET', headers={} }){
  console.log(method); // нету opts.
}

Собственно, значение по умолчанию можно применить и ко всему объекту с параметрами:

fetch(url, { method='GET' } = {}){
  // значение по умолчанию для второго параметра - {}
  // выведет "GET":
  console.log(method);
}

Вы также можете деструктурировать оператор присваивания:

var { method, body } = opts;

Это напоминает мне о выразительности, предоставленные with, но без магии или негативных последствий.

# Новые соглашения


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

Я расскажу о некоторых из них.

# let/const вместо var


Вместо того, чтобы писать var x = y скорее всего вы будете писать let x = y. let позволяет объявлять переменные с блочной областью видимости:

if (foo) {
  let x = 5;
  setTimeout(function(){
    // тут x равен `5`
  }, 500);
}
// тут x равен `undefined`

Это особенно полезно для for или while циклов:

for (let i = 0; i < 10; i++) {}
// `i` здесь не существует.

Используйте const, если вы хотите обеспечить неизменяемость с той же семантикой, как и let.

# строковые шаблоны вместо конкатенации


В связи с отсутствием sprintf или подобными утилитами в стандартной библиотеки JavaScript, составление строк всегда было более болезненным, чем следовало бы.

Строковые шаблоны сделали встраивание выражений в строки тривиальной операцией, также как и поддержку нескольких линий. Просто замените ‘ на `

let str = `
  Здравствуйте ${first}.
  Мы в ${new Date().getFullYear()} году
`;

# классы вместо прототипов


Определение класса было громоздкой операцией и требовало глубокого знания внутреннего устройства языка. Даже несмотря на то, что, польза понимания внутреннего устройства очевидна, порог входа для новичков был неоправданно высоким.

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

class A extends B {
  constructor(){}
  method(){}
  get prop(){}
  set prop(){}
}

Я изначально был удивлен, узнав, классы не всплывают (hoisted) (объяснение тут). Поэтому вы должны думать о них, переводя в var A = function(){} в противоположность function A(){}.

# ()=> вместо function


Не только потому что (x, y) => {} короче написать, чем function (x,y) {}, но поведение this в теле функции, скорее всего, будет ссылаться на то, что вы хотите.

Так называемые функции “толстые стрелки” лексически связанны. Рассмотрим пример метода внутри класса, который запускает два таймера:

class Person {
  constructor(name){
    this.name = name;
  }

  timers(){
    setTimeout(function(){
      console.log(this.name);
    }, 100);

    setTimeout(() => {
      console.log(this.name);
    }, 100);
  }
}

К ужасу новичков, первый таймер (с использованием function) выведет «undefined». А вот второй правильно выведет name.

# Первоклассная поддержка async I/O


Асинхронное выполнение кода сопровождало нас в течение почти всей истории языка. setTimeout, в конце концов, был введен примерно в то время, когда вышел JavaScript 1.0.

Но, пожалуй, язык не поддерживает асинхронность на самом деле. Возвращаемое значение вызовов функций, которые запланированы “выполниться в будущем” обычно равны undefined или в случае с setTimeoutNumber.

Введение Promise позволило заполнить очень большую пропасть в совместимости и композиции.

С одной стороны, вы найдете API более предсказуемым. В качестве теста, рассмотрим новое fetch API. Как это работает за сигнатурой, которую мы только что описали? Вы угадали. Оно возвращает Promise.

Если Вы использовали Node.JS в прошлом, вы знаете, что есть неформальная договоренность о том, что обратные вызовы следуют сигнатуре:

function (err, result){}

Также неофициально указана идея о том, что обратные вызовы будут вызываться только один раз. И null будет значение в случае отсутствия ошибок (а не undefined или false). За исключением, возможно, это не всегда так.

# Вперед к будущему


ES6 набирает немалые обороты в экосистеме. Chrome и io.js уже добавили некоторый функционал из ES6. Много уже было написано об этом.

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

Эволюция языка и его предполагаемый функционал, опережают реализацию. Как говорилось выше, Promise — по-настоящему интересен как самостоятельный блок, который предлагает решение проблемы callback hell раз и навсегда.

Стандарт ES7 предлагает сделать это путем введения возможности ожидания (async) объекта Promise:

async function uploadAvatar(){
  let user = await getUser();
  user.avatar = await getAvatar();
  return await user.save();
}

Хотя эта спецификация обсуждается уже давно, тот же инструмент, который компилирует ES6 в ES5 уже реализовал это.

Предстоит еще много работы для того, чтобы убедиться, что процесс принятия нового синтаксиса языка и API становится еще более лишенным странностей для тех, кто только приступает к работе.

Но одно можно сказать точно: мы должны принять это будущее.

Сноски:
1. ^ я использую слово “трасформация” в статье, чтобы объяснить компиляцию исходного кода в исходный код в JavaScript. Но значение этого термина технически спорно.
Перевод: Guillermo Rauch
Руслан Исмагилов @isruslan
карма
9,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (34)

  • +1
    И правда, выглядит лучше, чем CoffeeScript. Даже возникло желание написать что-нибудь на ES6 :)
    • +2
      Это же дело вкуса, ANSI-шникам скобки, точки с запятыми подавай, Питон-подобным (как кофе) скобки уберите, отступы наше всё, линейные функции и записи это круто

      Если хотите писать на ES6 то радость могут доставить неупомянутые генераторы, так же WeakMap и WeakSet и итераторы и «протокол итераторов» =) (благо вроде поддерживаются в ноде и последних мажорных браузерах нативно)
  • 0
    Я считаю очень удобным отсутствие в CoffeeScript скобок. Было бы неплохо иметь возможность также писать и на ES6. В этих примерах везде присутствуют правильные отступы и скобки одновременно. Надеюсь CoffeeScript не загнется… очень бы хотелось поддержки await из ES7, хотя давно есть форк IcedCoffeeScript.
    • –1
      Вы рассматриваете код какой-то развесистой функции, которой нашли по поиску. Вы можете перейти в любом приличном редакторе быстро перейти на открывающую или закрывающую скобку, перейдя таким образом в начало или конец функции. Как вы это сделаете без скобок?
      • +1
        Парсер коффескрипта прекрасно знает где начало и конец функции, так что это скорее вопрос поддержки языка со стороны редактора или IDE. Было бы очень классно если бы разработчики языка сразу создавали библиотеки для такого рода вещей. Имея стандартизованный API для таких библиотек можно осуществить поддержку в редакторе любого языка. Боюсь такое если и появится, то появится не скоро…
        Но даже без таких библиотек (в случае с CoffeeScript, TypeScript и т.д.) через SourceMaps можно использовать весь инструментарий для яваскрипта. Не знаю применяют ли это на практике, но теоретически можно)
        Пишу в atom.io, пока плагина linter-coffeescript мне хватает.
        • –1
          для TypeScript такая библиотека есть
    • +2
      Давно не писал на кофе, но учитывая его синтаксис async / await реализуется в нём элементарно. Реализация асинхронных функций с нативными генераторами в babel работает по этом моему предложению. С месяц назад в кофе добавили поддержку синтаксиса генераторов. Нам даже не нужен новый синтаксис, достаточно хелпера async, использовать вместо await yield — и получаем полностью ES7 совместимые асинхронные функции, рабочий пример:

      delay = (time)-> new Promise (resolve)-> setTimeout resolve, time
      
      sleepRandom = async (time)->
        yield delay time * 1e3
        0 | Math.random() * 1e3
      sleepError = async (time, msg)->
        yield delay time * 1e3
        throw Error msg
      
      (async ()->
        try
          console.log 'Run'               # Run
          console.log yield sleepRandom 5 # 936, after 5 sec.
          [a, b, c] = yield Promise.all [
            sleepRandom 5
            sleepRandom 15
            sleepRandom 10
          ]
          console.log a, b, c             # 210 445 71, after 15 sec.
          yield sleepError 5, 'Irror!'
          log 'Will not be displayed'
        catch e
          console.log e                   # Error: 'Irror!', after 5 sec.
      )()
      

      Хотя для старых браузеров нам нужен полифил Promise и прогонять результат через регенератор.
      • 0
        А что насчет yield? Насколько я помню это мало где поддерживается в полной мере.
        • 0
          Ну так я и написал — для IEбраузеров, не поддерживающих генераторы, можно прогонять конечный код через regenerator. Babel, например, для генераторов тоже его использует.
  • +10
    Извините, однако от этого перевода я заплакал кровью на первой трети и ушел читать оригинал. Живые человеки таким языком не разговаривают, серьезно.
    Однако, за ссылку на последний — спасибо.
    • +4
      Прошу прощения за ваши глаза. Любые замечания/поправки можно написать в ЛС, я это справлю.
  • +6
    Присоединяюсь к Matrosked, переводу не помешала бы основательная вычитка. И еще вот это:
    Как только функция была объявлена, она будет существовать до тех пор, пока код не будет интерпретирован.
    Что тут имелось в виду?
    • 0
      трудности перевода:)

      Оригинал:
      "One just to assume the function will exist prior to the code’s interpretation."

      Подозреваю, что переводится как:
      "Остаётся только надеяться, что эта функция будет существовать до момента интерпретации кода".
  • +2
    Сейчас браузеры поддерживают не достаточное кол-во фич из ES6, для меня ключевыми для перехода являются for… of loops и arrow functions, если с первым ещё более менее (Firefox и Chrome), то последний пока что только в Firefox есть и то не полностью, а это значит что нужно использовать «трансформацию» в ES5 для всех браузеров.

    Дак вот эта «трансформация» не такая уж и незаметная — результирующий код не слабо разбухает и становится медленее, чем если писать сразу в ES5. (Я использовал google traceur для «компиляции»).
    Если сравнивать с CoffeScript, я не давно переписал кусок кода на ES6, мне пришлось добавить много «мусора» (,{}:") в «чистый» CS, в итоге код разбух и стал не такой «чистый», хотя это мелочи.

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

    А такие вещи как Map/Set уже использую во всю, т.к. они не вносят нового синтаксиса, (а для старых браузеров самодельные аналоги либо es6-shim).
    • –3
      Быстрее работает WeakMap и WeakSet. К тому же, если на ключ все ссылки исчезают, (и доступ по ключу невозможен) сборщик это якобы понимает и (по стандарту) удаляет данный ключ-значение.

      Вообще ES6 это убийца следующих вещей:
      IE8 (хорошо, вроде)
      Аккумуляторов смартфонов (хорошо для продавцов внешних аккумов, а так лучи ненависти всем, кто так бодро «мощности компов выросли, мы можем юзать тяжеловесные функции без проблем»)
      Работы по стандартам (ибо черновик, не стандарт)

      С другой стороны это созидатель:
      классов и ООП в нормальном виде
      работы и дисциплины с перечислениями
      Позитивных классов

      В итоге использовать или нет зависит сильно от целевой «аудитории» (браузеры и устройства))
    • 0
      Я использовал google traceur для «компиляции»

      В этом все и дело. Возьмите babel
      • 0
        .
      • –1
        Нет, с ним не легче — кода в 3,5 больше и работает в 10 раз медленнее у меня на Chrome, вот тест.
        Исходный код такой:
        ES6:
        function run() {
          var s = '';
          for(v of ['a', 'b', 'c', 'd', 'e']) {
            s += v;
          }
        }
        

        CS:
        run = ->
          s = ''
          for v in ['a', 'b', 'c', 'd', 'e']
            s += v
          null
        

        • –3
          const run = () => ['a', 'b', 'c', 'd', 'e'].join();
          

          Какая-то фигня оторванная от реальности, если честно.
        • 0
          Не корректно. Вы сравниваете обход array-like объекта через обычный цикл for с обходом объекта через протокол итераторов. Попробуйте loose mode.
          • 0
            Вы сравниваете обход array-like объекта...

            Я просто написал код «обхода» массива, это babel выдал мне такой код. По сути эти 2 кода делаю одно и тоже, так что все честно.
            Добавил в тест «loose mode», да, он быстр, но почему он не включен по умолчанию?
            • 0
              for-of это не код обхода массива, а цикл для обхода объекта по протоколу итераторов. С помощью цикла for-in из кофе не обойти Map или Set, с помощью for-of из ES6 / babel — без проблем.

              Почему не включен по умолчанию? По ссылке что дал вроде написано. Он работает не совсем по спецификации ES6 и в ооочень редких случаях результат может не совпадать. Можно подменить итератор массива и цикл будет работать не совсем корректно. В случае преждевременного завершения (ошибки) должен быть вызван метод return итератора, если такой имеется (правда это пока нигде больше не поддерживается).

              А по поводу верхнего комментария треда — странно писать про нежелание использовать медленный код код и использовать Map / Set из es6-shim, почему — см. тут., да и es6-shim заменяет нативные коллекции абсолютно везде.
              • 0
                странно писать про нежелание использовать медленный код код и использовать Map / Set из es6-shim
                Я не использую map из shim, я использую «самодельные аналоги» которые не плохи по скорости.

                Кстати ваша модификация map с symbol не имеет смысла, по крайней мере сейчас, т.к. map и set имеют больше поддержки браузерами чем symbol, и перекрывают его.
                • 0
                  А вы пролистайте чуть выше и увидите как реализованы символы :) Хотя символов как таковых в реализации коллекций никогда и не было, упрощение. Текущая реализация серьёзно отличается от описанной в статье, но основные принципы те же.
  • 0
    ‘/users’
    

    Просто замените ‘ на `

    Кавычки! (ง •̀_•́)ง
    • 0
      Грустная шутка, конечно, но потом появятся новые кавычки, для экранирования этих кавычек.
      • –1
        Шта? 0_o Кривые кавычки статье автора, а не в JS.
        • –1
          Да? А мне казалось, что обратные кавычки вошли или войдут в стандарт.
          Извиняюсь, если не прав.
          • 0
            Так и есть. Но присмотритесь еще раз к кавычкам в статье:
            ‘/users’ 
            
            • 0
              А! Простите, я не понял вас. Я думал, что ваш комментарий относится к новому синтаксису языка.
  • –1
    >Promise — по-настоящему интересен как самостоятельный блок, который предлагает решение проблемы callback hell раз и навсегда.
    Иногда требуется обрабатывать события многократно. Promise не очень тут помогает,
    Например, вызвать функцию, если события е1 и е2 произошли раньше е3, которое произошло в течение 5 секунд после события е4, которое предшествовало е1 и е2.
    Интересно, предусмотрен ли какой-нибудь агрегатор для подобных ситуаций в будущем стандарте. Когда-то давно видел подобное на флеше.
    • 0
      Для многократной обработки событий есть общепринятый паттерн EventEmitter. Реализации есть и для браузеров и для серверных платформ. nodejs.org/api/events.html
      • 0
        Даже псевдокода получается слишком много:
        listen(e4)
        on(e4){
          listen(e1)
          listen(e2)
          on(e5)
            unlisten(e1, e2, e3, e5)
          delay_emit(e5, 5000)
          on(e1 and e2)
            listen(e3)
             call(func)
        

        Имелось ввиду какое-то подобие логики событий:
        if(e4 before (e1 and e2) before e3
         and e4 before timeout(0..5000) before e3) call(func)
        
        • 0
          А в каком языке есть встроенная реализация подобной логики?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.