Co nowego w ES6? Perspektywa konwersji CoffeeScript

Opublikowany: 2022-03-11

Jestem fanem CoffeeScript od ponad dwóch lat. Uważam, że bardziej wydajnie piszę CoffeeScript, popełniam mniej głupich błędów, a dzięki obsłudze map źródłowych debugowanie kodu CoffeeScript jest całkowicie bezbolesne.

Co nowego w ES6? Perspektywa konwersji CoffeeScript

Ostatnio gram z ES6 używając Babel i ogólnie jestem fanem. W tym artykule skompiluję moje odkrycia dotyczące ES6 z perspektywy konwertyty CoffeeScript, przyjrzę się rzeczom, które kocham i zobaczę, czego jeszcze mi brakuje.

Wcięcie syntaktycznie znaczące: dobrodziejstwo czy zmora?

Zawsze miałem trochę miłości/nienawiści z syntaktycznie znaczącym wcięciem w CoffeeScript. Kiedy wszystko pójdzie dobrze, otrzymasz komunikat „Spójrz mamo, bez rąk!” chwalenie się prawami do używania tak superminimalistycznej składni. Ale kiedy coś pójdzie nie tak, a nieprawidłowe wcięcie powoduje prawdziwe błędy (zaufaj mi, tak się dzieje), nie wydaje się, aby korzyści były warte zachodu.

 if iWriteCoffeeScript if iAmNotCareful badThings = 'happen'

Zazwyczaj te błędy są powodowane, gdy masz blok kodu z kilkoma zagnieżdżonymi instrukcjami i przypadkowo wciśniesz jedną instrukcję niepoprawnie. Tak, zwykle jest to spowodowane przez zmęczone oczy, ale moim zdaniem jest to znacznie bardziej prawdopodobne w CoffeeScript.

Kiedy zacząłem pisać ES6, nie byłem do końca pewien, jaka będzie moja intuicyjna reakcja. Okazuje się, że naprawdę miło było zacząć ponownie używać nawiasów klamrowych. Korzystanie z nowoczesnego edytora również pomaga, ponieważ zazwyczaj wystarczy otworzyć nawias klamrowy, a twój edytor jest na tyle uprzejmy, aby go dla ciebie zamknąć. Czułem się trochę jak powrót do spokojnego, czystego wszechświata po magii voodoo CoffeeScript.

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

Oczywiście nalegam na wyrzucenie średników. Jeśli ich nie potrzebujemy, mówię je wyrzucić. Uważam je za brzydkie i wymaga dodatkowego pisania.

Wsparcie klas

Wsparcie klasowe w ES6 jest fantastyczne, a jeśli przeniesiesz się z CoffeeScript, poczujesz się jak w domu. Porównajmy składnię między nimi z prostym przykładem:

Klasa 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` } }

Klasa 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"

Pierwsze wrażenia

Pierwszą rzeczą, którą możesz zauważyć, jest to, że ES6 jest nadal o wiele bardziej gadatliwy niż CoffeeScript. Jednym ze skrótów bardzo przydatnych w CoffeeScript jest obsługa automatycznego przypisywania zmiennych instancji w konstruktorze:

 constructor: (@numberOfLegs) ->

Jest to odpowiednik następującego w ES6:

 constructor(numberOfLegs) { this.numberOfLegs = numberOfLegs }

Oczywiście to nie koniec świata, a jeśli już, ta bardziej wyraźna składnia jest łatwiejsza do odczytania. Kolejną drobną rzeczą, której brakuje, jest to, że nie mamy do this skrótu @ , co zawsze było przyjemne, gdy pochodziło z Rubinowego tła, ale znowu nie jest to ogromny przełom. Ogólnie czujemy się tu jak w domu, a do definiowania metod wolę składnię ES6.

Jak super!

Jest mały problem ze sposobem, w jaki super jest obsługiwany w ES6. CoffeeScript obsługuje super w sposób Ruby („proszę wysłać wiadomość do metody nadklasy o tej samej nazwie”), ale „nagiej” super w ES6 można użyć tylko w konstruktorze. ES6 stosuje bardziej konwencjonalne podejście podobne do Javy, w którym super jest odniesieniem do instancji superklasy.

Powrócę

W CoffeeScript możemy użyć niejawnych zwrotów, aby zbudować piękny kod, taki jak:

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

Tutaj masz bardzo małą szansę na odzyskanie „foo” po wywołaniu foo() . ES6 nic z tego nie ma i zmusza nas do powrotu za pomocą słowa kluczowego return :

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

Jak widać w powyższym przykładzie, jest to ogólnie dobra rzecz, ale wciąż zapominam o tym dodać i otrzymuję nieoczekiwane, nieokreślone zwroty w całym miejscu! Jest jeden wyjątek, na który natknąłem się, dotyczący funkcji strzałek, w którym otrzymujesz niejawny zwrot dla jednej linijki, takiej jak ta:

 array.map( x => x * 10 )

Jest to przydatne, ale może być nieco mylące, ponieważ potrzebujesz return , jeśli dodasz nawiasy klamrowe:

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

Jednak nadal ma to sens. Rozumowanie jest takie, że jeśli dodałeś nawiasy klamrowe, to dlatego, że chcesz użyć wielu linii, a jeśli masz wiele linii, ma sens, aby jasno określić, co zwracasz.

Premie składni klasy ES6

Widzieliśmy, że CoffeeScript ma w zanadrzu kilka sztuczek składniowych, jeśli chodzi o definiowanie klas, ale ES6 ma też kilka własnych sztuczek.

Gettery i setery

ES6 ma potężne wsparcie dla enkapsulacji poprzez gettery i settery, co ilustruje poniższy przykład:

 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 } }

Dzięki getterowi, jeśli wejdziemy na bananaStore.bananas , zwróci on tylko dojrzałe banany. To jest naprawdę świetne, ponieważ w CoffeeScript musielibyśmy to zaimplementować za pomocą metody pobierającej, takiej jak bananaStore.getBananas() . Nie wydaje się to wcale naturalne, gdy w JavaScript jesteśmy przyzwyczajeni do bezpośredniego dostępu do właściwości. Sprawia to również, że programowanie jest mylące, ponieważ musimy pomyśleć o każdej właściwości, w jaki sposób powinniśmy uzyskać do niej dostęp — czy jest to .bananas czy getBananas() ?

Setter jest równie przydatny, ponieważ możemy wyczyścić zestaw danych, a nawet zgłosić wyjątek, gdy ustawione są nieprawidłowe dane, jak pokazano w powyższym przykładzie zabawki. Będzie to bardzo znajome dla każdego, kto ma doświadczenie w Ruby.

Osobiście uważam, że nie oznacza to, że powinieneś szaleć, używając do wszystkiego getterów i seterów. Jeśli możesz coś zyskać, hermetyzując zmienne instancji za pomocą metod pobierających i ustawiających (jak w powyższym przykładzie), zrób to. Ale wyobraź sobie, że masz po prostu flagę logiczną, taką jak isEditable . Jeśli nie stracisz niczego przez bezpośrednie ujawnienie właściwości isEditable , twierdzę, że jest to najlepsze podejście, ponieważ jest najprostsze.

Metody statyczne

ES6 obsługuje metody statyczne, co pozwala na wykonanie tej niegrzecznej sztuczki:

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

Teraz, jeśli nie znosisz pisania new Foo() , możesz teraz napisać Foo.new() . Puryści będą narzekać, ponieważ new jest słowem zastrzeżonym, ale po bardzo szybkim teście wydaje się, że działa w Node.js i we współczesnych przeglądarkach. Ale prawdopodobnie nie chcesz tego używać w produkcji!

Niestety, ponieważ w ES6 nie ma obsługi właściwości statycznych, jeśli chcesz zdefiniować stałe klas i uzyskać do nich dostęp w swojej metodzie statycznej, musisz zrobić coś takiego, co jest trochę hackowe:

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

Istnieje propozycja ES7, aby zaimplementować deklaratywne właściwości instancji i klas, abyśmy mogli zrobić coś takiego, co byłoby miłe:

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

Można to już zrobić całkiem elegancko w CoffeeScript:

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

Interpolacja ciągów

Nadszedł czas, kiedy w końcu możemy wykonać interpolację ciągów w JavaScript!

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

Pochodząca z CoffeeScript/Ruby, ta składnia wydaje się trochę kiepska. Być może ze względu na moje doświadczenie w Ruby, w którym do wykonywania poleceń systemowych używany jest backtick, na początku wydaje się to dość złe. Również używanie znaku dolara przypomina lata osiemdziesiąte – ale może to tylko ja.

Przypuszczam, że z powodu obaw o wsteczną kompatybilność po prostu nie było możliwe zaimplementowanie interpolacji ciągów w stylu CoffeeScript. "What a #{expletive} shame" .

Funkcje strzałek

Funkcje strzałek są w CoffeeScripter brane za pewnik. ES6 w dużej mierze zaimplementował składnię Fat Arrow z CoffeeScript. Otrzymujemy więc ładną, krótką składnię i zakres leksykalny this , więc nie musimy przeskakiwać przez takie obręcze, gdy używamy ES5:

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

Odpowiednik w ES6 to:

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

Zauważ, że pominąłem nawias wokół jednoargumentowego wywołania zwrotnego, ponieważ mogę!

Literały obiektowe na sterydach

Literały obiektowe w ES6 mają kilka poważnych ulepszeń wydajności. W dzisiejszych czasach mogą robić różne rzeczy!

Dynamiczne nazwy właściwości

Właściwie nie zdawałem sobie sprawy, dopóki nie napisałem tego artykułu, że od CoffeeScript 1.9.1 możemy teraz zrobić to:

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

Co jest o wiele mniej bolesne niż coś takiego, co było konieczne wcześniej:

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

ES6 ma alternatywną składnię, która moim zdaniem jest całkiem fajna, ale nie jest wielką wygraną:

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

Funkowe skróty

W rzeczywistości jest to zaczerpnięte z CoffeeScript, ale było to coś, czego do tej pory nie wiedziałem. W każdym razie jest to bardzo przydatne:

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

Teraz zawartość obj to { foo: 'foo', bar: 'bar' } . To bardzo przydatne i warte zapamiętania.

Wszystko, co klasa może zrobić, ja też mogę!

Literały obiektowe mogą teraz robić prawie wszystko, co klasa, nawet coś takiego:

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

Nie jestem pewien, dlaczego chcesz zacząć to robić, ale hej, teraz możesz! Nie da się oczywiście zdefiniować metod statycznych… bo to nie miałoby żadnego sensu.

Pętle for, które Cię zdezorientują

Składnia pętli for w ES6 wprowadzi kilka fajnych błędów pamięci mięśniowej dla doświadczonych CoffeeScripterów, ponieważ for-of ES6 przekłada się na for-in w CoffeeSCript i na odwrót.

ES6

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

CoffeeScript

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

Czy powinienem przejść na ES6?

Obecnie pracuję nad dość dużym projektem Node.js przy użyciu 100% CoffeeScript i nadal jestem z niego bardzo zadowolony i bardzo produktywny. Powiedziałbym, że jedyną rzeczą, o którą jestem naprawdę zazdrosny w ES6, są gettery i settery.

Również używanie ES6 w praktyce jest nadal nieco bolesne. Jeśli jesteś w stanie korzystać z najnowszej wersji Node.js, uzyskasz prawie wszystkie funkcje ES6, ale w przypadku starszych wersji i w przeglądarce sytuacja jest jeszcze mniej różowa. Tak, możesz używać Babel, ale oczywiście oznacza to zintegrowanie Babel ze swoim stosem.

Powiedziawszy, że widzę, jak ES6 w ciągu najbliższego roku lub dwóch zyskuje na popularności i mam nadzieję, że w ES7 zobaczę jeszcze lepsze rzeczy.

To, z czego jestem naprawdę zadowolony z ES6, to fakt, że „zwykły stary JavaScript” jest prawie tak przyjazny i potężny, jak CoffeeScript. To jest świetne dla mnie jako freelancera – w przeszłości miałem trochę niechęci do pracy nad projektami JavaScript – ale z ES6 wszystko wydaje się trochę bardziej błyszczące.