Что нового в ES6? Перспектива преобразования CoffeeScript

Опубликовано: 2022-03-11

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

Что нового в ES6? Перспектива преобразования CoffeeScript

Недавно я играл с ES6, используя Babel, и в целом я его фанат. В этой статье я обобщу свои выводы по ES6 с точки зрения конвертера CoffeeScript, посмотрю на то, что мне нравится, и посмотрю, чего мне все еще не хватает.

Синтаксически значимый отступ: благо или вред?

У меня всегда были отношения любви/ненависти к синтаксически значимому отступу CoffeeScript. Когда все идет хорошо, вы получаете сообщение «Смотри, мама, без рук!» хвастаясь правами на использование такого сверхминималистического синтаксиса. Но когда что-то идет не так и неправильный отступ вызывает настоящие ошибки (поверьте мне, такое случается), кажется, что преимущества не стоят того.

 if iWriteCoffeeScript if iAmNotCareful badThings = 'happen'

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

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

 if (iWriteES6) { if (iWriteNestedStatements) { let badThings = 'are less likely to happen' } }

Конечно, я настаиваю на том, чтобы убрать точки с запятой. Если они нам не нужны, я говорю, выбросьте их. Я нахожу их уродливыми, и это лишний набор текста.

Поддержка класса

Поддержка классов в ES6 фантастическая, и если вы переходите с CoffeeScript, вы будете чувствовать себя как дома. Давайте сравним синтаксис между ними на простом примере:

Класс ES6

 class Animal { constructor(numberOfLegs) { this.numberOfLegs = numberOfLegs } toString() { return `I am an animal with ${this.numberOfLegs} legs` } } class Monkey extends Animal { constructor(bananas) { super(2) this.bananas = bananas } toString() { let superString = super.toString() .replace(/an animal/, 'a monkey') return `${superString} and ${this.bananas} bananas` } }

Класс CoffeeScript

 class Animal constructor: (@numberOfLegs) -> toString: -> "I am an animal with #{@numberOfLegs} legs" class Monkey extends Animal constructor: (@numberOfBananas) -> super(2) toString: -> superString = super.toString() .replace(/an animal/, 'a monkey') "#{superString} and #{@numberOfLegs} bananas"

Первые впечатления

Первое, что вы можете заметить, это то, что ES6 по-прежнему намного более многословен, чем CoffeeScript. Одним из очень удобных сочетаний клавиш в CoffeeScript является поддержка автоматического назначения переменных экземпляра в конструкторе:

 constructor: (@numberOfLegs) ->

Это эквивалентно следующему в ES6:

 constructor(numberOfLegs) { this.numberOfLegs = numberOfLegs }

Конечно, на самом деле это не конец света, и во всяком случае этот более явный синтаксис легче читать. Еще одна небольшая вещь, которой не хватает, заключается в том, что у нас нет ярлыка @ для this , что всегда было приятно исходить из фона Ruby, но опять же не является серьезным нарушением условий сделки. В целом мы чувствуем себя здесь как дома, и я предпочитаю синтаксис ES6 для определения методов.

Как Супер!

Есть небольшая проблема с тем, как super обрабатывается в ES6. CoffeeScript обрабатывает super в стиле Ruby («пожалуйста, отправьте сообщение методу суперкласса с тем же именем»), но единственный раз, когда вы можете использовать «голый» супер в ES6, — это в конструкторе. ES6 следует более традиционному подходу, похожему на Java, где super является ссылкой на экземпляр суперкласса.

Я вернусь

В CoffeeScript мы можем использовать неявные возвраты для создания красивого кода, например:

 foo = -> if Math.random() > 0.5 if Math.random() > 0.5 if Math.random() > 0.5 "foo"

Здесь у вас очень мало шансов вернуть «foo» при вызове foo() . ES6 не имеет ничего из этого и заставляет нас вернуться, используя ключевое слово return :

 const foo = () => { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { if (Math.random() > 0.5 ) { return "foo" } } } }

Как мы можем видеть в приведенном выше примере, это, как правило, хорошо, но я все еще забываю добавить его и получаю неожиданные неопределенные результаты повсюду! Есть одно исключение, с которым я столкнулся, касающееся функций стрелок, где вы получаете неявный возврат для одного лайнера, например:

 array.map( x => x * 10 )

Это удобно, но может немного сбивать с толку, так как вам нужен return , если вы добавите фигурные скобки:

 array.map( x => { return x * 10 })

Тем не менее, это все еще имеет смысл. Причина в том, что если вы добавили фигурные скобки, это потому, что вы хотите использовать несколько строк, а если у вас есть несколько строк, имеет смысл четко указать, что вы возвращаете.

Бонусы синтаксиса класса ES6

Мы видели, что у CoffeeScript есть несколько синтаксических хитростей, когда дело доходит до определения классов, но ES6 также имеет несколько собственных хитростей.

Геттеры и сеттеры

ES6 имеет мощную поддержку инкапсуляции с помощью геттеров и сеттеров, как показано в следующем примере:

 class BananaStore { constructor() { this._bananas = [] } populate() { // fetch our bananas from the backend here } get bananas() { return this._bananas.filter( banana => banana.isRipe ) } set bananas(bananas) { if (bananas.length > 100) { throw `Wow ${bananas.length} is a lot of bananas!` } this._bananas = bananas } }

Благодаря геттеру, если мы получим доступ bananaStore.bananas , он вернет только спелые бананы. Это действительно здорово, так как в CoffeeScript нам нужно было бы реализовать это с помощью метода получения, такого как bananaStore.getBananas() . Это не кажется естественным, когда в JavaScript мы привыкли напрямую обращаться к свойствам. Это также делает разработку запутанной, потому что нам нужно продумать для каждого свойства, как мы должны получить к нему доступ — это .bananas или getBananas() ?

Сеттер одинаково полезен, так как мы можем очистить набор данных или даже создать исключение, когда установлены недопустимые данные, как показано в игрушечном примере выше. Это будет хорошо знакомо любому, кто знаком с Ruby.

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

Статические методы

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

 class Foo { static new() { return new this } }

Теперь, если вы ненавидите писать new Foo() , теперь вы можете написать Foo.new() . Пуристы будут ворчать, поскольку слово « new » является зарезервированным, но после очень быстрого тестирования кажется, что оно отлично работает в Node.js и в современных браузерах. Но вы, вероятно, не хотите использовать это в производстве!

К сожалению, поскольку в ES6 нет поддержки статических свойств, если вы хотите определить константы класса и получить к ним доступ в своем статическом методе, вам придется сделать что-то вроде этого, что немного хакерски:

 class Foo { static imgPath() { return `${this.ROOT_PATH}/img` } } Foo.ROOT_PATH = '/foo'

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

 class Foo { static ROOT_PATH = '/foo' static imgPath() { return `${this.ROOT_PATH}/img` } }

Это уже можно довольно элегантно сделать в CoffeeScript:

 class Foo @ROOT_PATH: '/foo' @imgPath: -> @ROOT_PATH

Интерполяция строк

Пришло время, когда мы, наконец, можем сделать интерполяцию строк в Javascript!

 `I am so happy that in the year ${new Date().getFullYear()} we can interpolate strings`

Исходя из CoffeeScript/Ruby, этот синтаксис выглядит немного отвратительно. Возможно, из-за моего опыта работы с Ruby, когда для выполнения системных команд используется обратная кавычка, поначалу это кажется довольно неправильным. Кроме того, использование знака доллара напоминает восьмидесятые — но, может быть, это только мне кажется.

Я предполагаю, что из-за проблем с обратной совместимостью было просто невозможно реализовать интерполяцию строк в стиле CoffeeScript. "What a #{expletive} shame" .

Стрелочные функции

Функции стрелок считаются само собой разумеющимися в CoffeeScripter. ES6 в значительной степени реализовал синтаксис жирной стрелки CoffeeScript. Таким образом, мы получаем хороший короткий синтаксис, и мы получаем лексически ограниченный this , поэтому нам не нужно прыгать через такие обручи при использовании ES5:

 var that = this doSomethingAsync().then( function(res) { that.foo(res) })

Эквивалент в ES6:

 doSomethingAsync().then( res => { this.foo(res) })

Обратите внимание, что я опустил круглые скобки вокруг унарного обратного вызова, потому что я могу!

Литералы объектов на стероидах

Литералы объектов в ES6 имеют несколько серьезных улучшений производительности. В наши дни они могут делать все, что угодно!

Динамические имена свойств

До написания этой статьи я на самом деле не осознавал, что начиная с CoffeeScript 1.9.1 теперь мы можем сделать это:

 dynamicProperty = 'foo' obj = {"#{dynamicProperty}": 'bar'}

Что гораздо меньше боли, чем что-то подобное, что было необходимо раньше:

 dynamicProperty = 'foo' obj = {} obj[dynamicProperty] = 'bar'

В ES6 есть альтернативный синтаксис, который, на мой взгляд, довольно хорош, но не является большой победой:

 let dynamicProperty = 'foo' let obj = { [dynamicProperty]: 'bar' }

Прикольные ярлыки

Это на самом деле взято из CoffeeScript, но я не знал об этом до сих пор. В любом случае это очень полезно:

 let foo = 'foo' let bar = 'bar' let obj = { foo, bar }

Теперь содержимое obj равно { foo: 'foo', bar: 'bar' } . Это очень полезно, и это стоит запомнить.

Все, что может класс, я тоже могу!

Литералы объектов теперь могут делать почти все, что может делать класс, даже что-то вроде:

 let obj = { _foo: 'foo', get foo() { return this._foo }, set foo(str) { this._foo = str }, isFoo() { return this.foo === 'foo' } }

Не совсем уверен, почему вы хотите начать это делать, но эй, теперь вы можете! Конечно, вы не можете определить статические методы... так как это не имеет никакого смысла.

Циклы For, чтобы сбить вас с толку

Синтаксис цикла for в ES6 добавит некоторые приятные ошибки памяти для опытных CoffeeScripters, поскольку for-of в ES6 преобразуется в for-in в CoffeeSCript, и наоборот.

ES6

 for (let i of [1, 2, 3]) { console.log(i) } // 1 // 2 // 3

КофеСкрипт

 for i of [1, 2, 3] console.log(i) # 0 # 1 # 2

Должен ли я перейти на ES6?

В настоящее время я работаю над довольно большим проектом Node.js, используя 100% CoffeeScript, и я все еще очень счастлив и очень продуктивен. Я бы сказал, что единственное, чему я действительно завидую в ES6, — это геттеры и сеттеры.

Кроме того, использование ES6 на практике сегодня все еще немного болезненно. Если вы можете использовать самую последнюю версию Node.js, то вы получаете почти все функции ES6, но для более старых версий и в браузере все еще менее радужно. Да, вы можете использовать Babel, но, конечно, это означает интеграцию Babel в ваш стек.

Сказав, что я вижу, что ES6 в течение следующих года или двух получит много преимуществ, и я надеюсь увидеть еще больше в ES7.

Что меня действительно порадовало в ES6, так это то, что «старый добрый JavaScript» почти такой же удобный и мощный, как и CoffeeScript. Это здорово для меня как фрилансера — в прошлом я немного не хотел работать над проектами JavaScript — но с ES6 все кажется немного блестящим.