Ήμουν CoffeeScript ανεμιστήρα για πάνω από δύο χρόνια τώρα. Βρίσκω ότι είμαι πιο παραγωγικός γράφοντας το CoffeeScript, κάνω λιγότερα ανόητα λάθη και με την υποστήριξη για τους χάρτες προέλευσης που εντοπίζεται ο κώδικας CoffeeScript είναι εντελώς ανώδυνος.
Πρόσφατα έπαιζα με ES6 χρησιμοποιώντας Βαβυλωνία και γενικά είμαι θαυμαστής. Σε αυτό το άρθρο, θα συγκεντρώσω τα ευρήματά μου σχετικά με το ES6 από την προοπτική ενός μετατροπέα CoffeeScript, θα δούμε τα πράγματα που μου αρέσουν και να δω τι πράγματα μου λείπουν ακόμα.
Είχα πάντα μια σχέση αγάπης / μίσους με τη συντακτική σημαντική εσοχή του CoffeeScript. Όταν όλα πάνε καλά παίρνεις το 'Look ma, no hands!' καυχητικά δικαιώματα χρήσης μιας τέτοιας υπερ-μινιμαλιστικής σύνταξης. Αλλά όταν τα πράγματα πάνε στραβά και η λανθασμένη εσοχή προκαλεί πραγματικά σφάλματα (πιστέψτε με, συμβαίνει), δεν αισθάνεται ότι τα οφέλη αξίζουν.
if iWriteCoffeeScript if iAmNotCareful badThings = 'happen'
Συνήθως, αυτά τα σφάλματα προκαλούνται όταν έχετε ένα μπλοκ κώδικα με μερικές ένθετες δηλώσεις και κατά λάθος εσοχή μιας δήλωσης λανθασμένα. Ναι, συνήθως προκαλείται από κουρασμένα μάτια, αλλά είναι πολύ πιο πιθανό να συμβεί στο CoffeeScript κατά τη γνώμη μου.
Όταν άρχισα να γράφω ES6 δεν ήμουν σίγουρος ποια θα ήταν η αντίδραση του εντέρου μου. Αποδεικνύεται ότι πραγματικά αισθάνθηκε πραγματικά καλά για να αρχίσετε ξανά να χρησιμοποιείτε σγουρά τιράντες. Η χρήση ενός σύγχρονου επεξεργαστή βοηθά επίσης, καθώς γενικά πρέπει μόνο να ανοίξετε το στήριγμα και ο επεξεργαστής σας είναι αρκετά ευγενικός για να το κλείσει για εσάς. Ένιωσα λίγο σαν να επιστρέψω σε ένα ήρεμο, καθαρό σύμπαν μετά τη μαγεία των βουντών του CoffeeScript.
if (iWriteES6) { if (iWriteNestedStatements) { let badThings = 'are less likely to happen' } }
Φυσικά επιμένω να πετάξω τα ερωτηματικά. Αν δεν τα χρειαζόμαστε, λέω να τα πετάξω. Τους βρίσκω άσχημο και είναι επιπλέον δακτυλογράφηση.
Η υποστήριξη της τάξης στο ES6 είναι φανταστική και αν μετακινείστε από το CoffeeScript θα νιώσετε σαν στο σπίτι σας. Ας συγκρίνουμε τη σύνταξη μεταξύ των δύο με ένα απλό παράδειγμα:
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` } }
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 για τον καθορισμό μεθόδων.
Υπάρχει ένα μικρό gotcha με τον τρόπο super
αντιμετωπίζεται στο ES6. Λαβές CoffeeScript super
με τον τρόπο Ruby ('παρακαλώ στείλτε ένα μήνυμα στη μέθοδο superclass με το ίδιο όνομα'), αλλά η μόνη φορά που μπορείτε να χρησιμοποιήσετε ένα 'γυμνό' σούπερ στο ES6 είναι στον κατασκευαστή. Το ES6 ακολουθεί μια πιο συμβατική προσέγγιση τύπου Java όπου super
είναι μια αναφορά στην παρουσία του superclass.
Στο 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' } } } }
Όπως μπορούμε να δούμε στο παραπάνω παράδειγμα, αυτό είναι γενικά καλό, αλλά εξακολουθώ να βρίσκομαι να ξεχάσω να το προσθέσω και να λαμβάνω απροσδόκητες απροσδιόριστες επιστροφές παντού! Υπάρχει μια εξαίρεση που έχω συναντήσει, σχετικά με τις λειτουργίες Arrow, όπου λαμβάνετε μια σιωπηρή επιστροφή για ένα liners όπως αυτό:
array.map( x => x * 10 )
Αυτό είναι κάπως βολικό, αλλά μπορεί να είναι λίγο συγκεχυμένο καθώς χρειάζεστε το return
αν προσθέσετε τα σγουρά τιράντες:
array.map( x => { return x * 10 })
Ωστόσο, εξακολουθεί να έχει νόημα. Ο λόγος είναι ότι εάν έχετε προσθέσει τα σγουρά τιράντες είναι επειδή θέλετε να χρησιμοποιήσετε πολλές γραμμές και αν έχετε πολλές γραμμές, είναι λογικό να είστε σαφείς τι επιστρέφετε.
Έχουμε δει ότι το CoffeeScript έχει μερικά συντακτικά κόλπα στο μανίκι του όταν πρόκειται για τον ορισμό τάξεων, αλλά το ES6 έχει επίσης μερικά δικά του κόλπα.
Το ES6 έχει ισχυρή υποστήριξη για ενθυλάκωση μέσω getter και setter, όπως φαίνεται στο ακόλουθο παράδειγμα:
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.
Προσωπικά δεν νομίζω ότι αυτό σημαίνει ότι πρέπει να τρελαίνετε χρησιμοποιώντας τα κατακτητικά και τα ρυθμιστικά για όλα. Εάν μπορείτε να κερδίσετε κάτι ενσωματώνοντας τις μεταβλητές παρουσίας σας μέσω getter και setter (όπως στο παραπάνω παράδειγμα), προχωρήστε και κάντε το. Αλλά φανταστείτε ότι έχετε απλώς μια δυαδική σημαία όπως 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, αυτή η σύνταξη αισθάνεται λίγο yuk. Ίσως λόγω του ιστορικού μου Ruby όπου χρησιμοποιείται ένα backtick για την εκτέλεση εντολών συστήματος, στην αρχή φαίνεται πολύ λάθος. Επίσης, χρησιμοποιώντας το σύμβολο του δολαρίου αισθάνεστε κάπως ογδόντα - αλλά ίσως αυτό είναι μόνο εγώ.
Υποθέτω ότι λόγω ανησυχιών συμβατότητας προς τα πίσω δεν ήταν δυνατή η εφαρμογή παρεμβολής συμβολοσειράς στυλ CoffeeScript. 'What a #{expletive} shame'
.
Οι λειτουργίες βέλους θεωρούνται δεδομένες στο CoffeeScripter. Το ES6 εφάρμοσε σχεδόν λίγο τη σύνταξη του βέλους λίπους του CoffeeScript. Έχουμε λοιπόν μια ωραία σύντομη σύνταξη και παίρνουμε το λεξιλογικό πεδίο
this
Το ισοδύναμο στο ES6 είναι:
var that = this doSomethingAsync().then( function(res) { that.foo(res) })
Παρατηρήστε πώς άφησα την παρένθεση γύρω από το unary callback, γιατί μπορώ!
Τα κυριολεκτικά αντικείμενα στο ES6 έχουν κάποιες σοβαρές βελτιώσεις απόδοσης. Μπορούν να κάνουν κάθε είδους πράγματα αυτές τις μέρες!
Δεν συνειδητοποίησα μέχρι να γράψω αυτό το άρθρο ότι από το CoffeeScript 1.9.1 μπορούμε τώρα να το κάνουμε αυτό:
doSomethingAsync().then( res => { this.foo(res) })
Ποιος είναι πολύ λιγότερο πόνος από κάτι σαν αυτό που ήταν απαραίτητο πριν:
dynamicProperty = 'foo' obj = {'#{dynamicProperty}': 'bar'}
Το ES6 έχει μια εναλλακτική σύνταξη που νομίζω ότι είναι πολύ ωραίο, αλλά όχι μια τεράστια νίκη:
dynamicProperty = 'foo' obj = {} obj[dynamicProperty] = 'bar'
Αυτό προέρχεται πραγματικά από το CoffeeScript, αλλά ήταν κάτι που αγνοούσα μέχρι τώρα. Ωστόσο, είναι πολύ χρήσιμο:
let dynamicProperty = 'foo' let obj = { [dynamicProperty]: 'bar' }
Τώρα το περιεχόμενο του obj είναι let foo = 'foo' let bar = 'bar' let obj = { foo, bar }
. Αυτό είναι εξαιρετικά χρήσιμο και αξίζει να το θυμάστε.
Οι γραμματοσειρές αντικειμένων μπορούν τώρα να κάνουν σχεδόν όλα όσα μπορεί να κάνει μια τάξη, ακόμη και κάτι σαν:
{ foo: 'foo', bar: 'bar' }
Δεν είμαι σίγουρος γιατί θα θέλατε να ξεκινήσετε να το κάνετε αυτό, αλλά τώρα μπορείτε! Φυσικά δεν μπορείτε να ορίσετε στατικές μεθόδους… καθώς αυτό δεν θα είχε κανένα νόημα.
Η σύνταξη ES6 for-loop θα εισαγάγει μερικά ωραία σφάλματα μυϊκής μνήμης για έμπειρους CoffeeScripters, καθώς το for-of του ES6 μεταφράζεται σε for-in του CoffeeSCript και αντίστροφα.
let obj = { _foo: 'foo', get foo() { return this._foo }, set foo(str) { this._foo = str }, isFoo() { return this.foo === 'foo' } }
for (let i of [1, 2, 3]) { console.log(i) } // 1 // 2 // 3
Αυτή τη στιγμή εργάζομαι σε ένα αρκετά μεγάλο Node.js έργο χρησιμοποιώντας 100% CoffeeScript και είμαι ακόμα πολύ χαρούμενος και εξαιρετικά παραγωγικός με αυτό. Θα έλεγα ότι το μόνο πράγμα που ζηλεύω πραγματικά στο ES6 είναι οι λάτρεις και οι ρυθμιστές.
Επίσης, εξακολουθεί να είναι ελαφρώς οδυνηρό χρησιμοποιώντας το ES6 στην πράξη σήμερα. Εάν μπορείτε να χρησιμοποιήσετε την πιο πρόσφατη έκδοση Node.js, τότε έχετε σχεδόν όλα Χαρακτηριστικά ES6 , αλλά για παλαιότερες εκδόσεις και στο πρόγραμμα περιήγησης τα πράγματα είναι ακόμα λιγότερο ρόδινα. Ναι, μπορείτε να χρησιμοποιήσετε το Babel, αλλά φυσικά αυτό σημαίνει την ενσωμάτωση του Babel στη στοίβα σας.
Έχοντας πει ότι μπορώ να δω το ES6 τον επόμενο χρόνο ή δύο να κερδίζει πολύ έδαφος, και ελπίζω να δω ακόμα μεγαλύτερα πράγματα στο ES7.
Αυτό που πραγματικά μου αρέσει με το ES6 είναι ότι το 'απλό παλιό JavaScript' είναι σχεδόν εξίσου φιλικό και ισχυρό εκτός κιβωτίου με το CoffeeScript. Αυτό είναι υπέροχο για μένα ως ελεύθερος επαγγελματίας - στο παρελθόν ήμουν λίγο αντίθετος να δουλεύω σε έργα JavaScript - αλλά με το ES6 όλα φαίνονται λίγο πιο λαμπερά.