มีอะไรใหม่ใน ES6? มุมมองของการแปลง CoffeeScript
เผยแพร่แล้ว: 2022-03-11ฉันเป็นแฟนของ CoffeeScript มาสองปีแล้ว ฉันพบว่าการเขียน CoffeeScript มีประสิทธิภาพมากขึ้น ทำผิดพลาดน้อยลง และการรองรับการแก้จุดบกพร่องของซอร์สแมปโค้ด CoffeeScript นั้นไม่เจ็บปวดเลย
เมื่อเร็ว ๆ นี้ฉันได้เล่นกับ ES6 โดยใช้ Babel และโดยรวมแล้วฉันเป็นแฟนตัวยง ในบทความนี้ ฉันจะรวบรวมสิ่งที่ค้นพบของฉันเกี่ยวกับ 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 คุณจะรู้สึกเหมือนอยู่บ้าน ลองเปรียบเทียบไวยากรณ์ระหว่างทั้งสองกับตัวอย่างง่ายๆ:
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 สำหรับการกำหนดวิธีการ
สุดยอด!
มี gotcha ขนาดเล็กที่มีวิธีจัดการ super
ใน ES6 CoffeeScript จัดการ super
ด้วยวิธี Ruby (“โปรดส่งข้อความไปยังวิธี superclass ที่มีชื่อเดียวกัน”) แต่ครั้งเดียวที่คุณสามารถใช้ super ที่ "naked" ใน ES6 อยู่ใน Constructor 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 Functions ซึ่งคุณจะได้รับผลตอบแทนโดยปริยายสำหรับหนึ่งซับเช่นนี้:
array.map( x => x * 10 )
วิธีนี้มีประโยชน์แต่อาจสร้างความสับสนเล็กน้อยเนื่องจากคุณต้องการ return
หากคุณเพิ่มเครื่องมือจัดฟันแบบหยิก:
array.map( x => { return x * 10 })
อย่างไรก็ตาม มันยังคงสมเหตุสมผล เหตุผลก็คือถ้าคุณเพิ่มวงเล็บปีกกา นั่นเป็นเพราะคุณต้องการใช้หลายบรรทัด และถ้าคุณมีหลายบรรทัด ก็ควรที่จะชัดเจนว่าคุณกำลังส่งคืนอะไร
โบนัสไวยากรณ์คลาส ES6
เราได้เห็นแล้วว่า CoffeeScript มีกลวิธีเชิงวากยสัมพันธ์เล็กน้อยเมื่อพูดถึงการกำหนดคลาส แต่ ES6 ก็มีกลอุบายบางอย่างในตัวเองเช่นกัน
Getters และ Setters
ES6 มีการสนับสนุนที่มีประสิทธิภาพสำหรับการห่อหุ้มผ่าน getters และ setters ดังแสดงในตัวอย่างต่อไปนี้:
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 เราจะต้องใช้งานสิ่งนี้ผ่านเมธอด getter เช่น bananaStore.getBananas()
สิ่งนี้ไม่เป็นธรรมชาติเลยเมื่อเราคุ้นเคยกับการเข้าถึงคุณสมบัติโดยตรงใน JavaScript นอกจากนี้ยังทำให้การพัฒนาเกิดความสับสนเพราะเราต้องคิดสำหรับแต่ละคุณสมบัติว่าเราควรเข้าถึงอย่างไร - มันคือ .bananas หรือ .bananas
getBananas()
?

ตัวตั้งค่ามีประโยชน์เท่าเทียมกัน เนื่องจากเราสามารถล้างชุดข้อมูลหรือแม้กระทั่งโยนข้อยกเว้นเมื่อมีการตั้งค่าข้อมูลที่ไม่ถูกต้อง ดังที่แสดงในตัวอย่างของเล่นด้านบน สิ่งนี้จะคุ้นเคยกับทุกคนจากพื้นหลัง Ruby
โดยส่วนตัวฉันไม่คิดว่านี่หมายความว่าคุณควรคลั่งไคล้การใช้ getters และ setters สำหรับทุกสิ่ง หากคุณสามารถได้รับบางสิ่งโดยการห่อหุ้มตัวแปรอินสแตนซ์ของคุณผ่านตัวรับและตัวตั้งค่า (เช่นในตัวอย่างด้านบน) ให้ทำต่อไป แต่ลองนึกภาพว่าคุณเพิ่งมีแฟล็กบูลีน เช่น 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 ของฉันที่ใช้ backtick เพื่อรันคำสั่งของระบบ มันจึงรู้สึกผิดมากในตอนแรก การใช้เครื่องหมายดอลลาร์ให้ความรู้สึกเหมือนอายุ 80 – แต่นั่นอาจเป็นแค่ฉัน
ฉันคิดว่าเนื่องจากข้อกังวลเรื่องความเข้ากันได้แบบย้อนหลังจึงเป็นไปไม่ได้ที่จะใช้การแก้ไขสตริงสไตล์ 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) })
สังเกตว่าฉันละเว้นวงเล็บรอบ unary callback ออกไปเพราะฉันทำได้!
ตัวอักษรวัตถุบนเตียรอยด์
Object literals ใน 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' }
มีประโยชน์มากและควรค่าแก่การจดจำ
ทุกสิ่งที่ชั้นเรียนทำได้ ฉันก็ทำได้เช่นกัน!
ตอนนี้ Object literals สามารถทำได้เกือบทุกอย่างที่คลาสสามารถทำได้ แม้กระทั่งบางอย่างเช่น:
let obj = { _foo: 'foo', get foo() { return this._foo }, set foo(str) { this._foo = str }, isFoo() { return this.foo === 'foo' } }
ไม่ค่อยแน่ใจว่าทำไมคุณถึงต้องการเริ่มทำอย่างนั้น แต่ตอนนี้คุณทำได้! คุณไม่สามารถกำหนดวิธีการคงที่ได้แน่นอน… เพราะนั่นไม่สมเหตุสมผลเลย
For-ลูปจะทำให้คุณสับสน
ไวยากรณ์ for-loop ของ ES6 กำลังจะแนะนำข้อบกพร่องของหน่วยความจำของกล้ามเนื้อที่ดีสำหรับ CoffeeScripters ที่มีประสบการณ์ เนื่องจาก for-of ของ ES6 แปลเป็น for-in ของ CoffeeSCript และในทางกลับกัน
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
ฉันควรเปลี่ยนเป็น ES6 หรือไม่
ฉันกำลังทำงานในโปรเจ็กต์ Node.js ที่ค่อนข้างใหญ่โดยใช้ CoffeeScript 100% และฉันก็ยังมีความสุขและมีประสิทธิผลมากกับมัน ฉันจะบอกว่าสิ่งเดียวที่ฉันอิจฉาจริงๆใน ES6 คือตัวรับและเซ็ตเตอร์
ทุกวันนี้ยังรู้สึกเจ็บปวดเล็กน้อยเมื่อใช้ ES6 ในทางปฏิบัติ หากคุณสามารถใช้ Node.js เวอร์ชันล่าสุดได้ คุณก็จะได้รับคุณลักษณะ ES6 เกือบทั้งหมด แต่สำหรับเวอร์ชันเก่าและในเบราว์เซอร์ สิ่งต่างๆ ยังคงไม่ค่อยสดใส ได้ คุณสามารถใช้ Babel ได้ แต่แน่นอนว่านั่นหมายถึงการรวม Babel เข้ากับสแต็กของคุณ
ต้องบอกว่าฉันสามารถเห็น ES6 ในปีหน้าหรือสองปีหน้าได้รับจำนวนมาก และฉันหวังว่าจะเห็นสิ่งที่ยิ่งใหญ่กว่าใน ES7
สิ่งที่ฉันพอใจมากเกี่ยวกับ ES6 ก็คือ "JavaScript แบบเก่าธรรมดา" นั้นเกือบจะเป็นมิตรและใช้งานได้จริงเหมือนกับ CoffeeScript สิ่งนี้ยอดเยี่ยมสำหรับฉันในฐานะนักแปลอิสระ – ในอดีตฉันเคยไม่ชอบทำงานในโครงการ JavaScript เล็กน้อย – แต่ด้วย ES6 ทุกอย่างดูเหมือนจะดูสดใสขึ้นเล็กน้อย