JavaScript: มีอะไรใหม่บ้างใน ES8 (ES2017)

Nuttavut Thongjor

ES8 หรือ ES2017 พึ่งคลอดอย่างเป็นทางการเมื่อไม่นานนี้ ทว่าการอัพเกรดเวอร์ชันครั้งนี้ไม่ได้มีอะไรหวือหวาชนิดที่ต้องร้องกรี๊ดจนตับไหม้ นั่นเพราะฟีเจอร์หลักๆใน ES8 เราๆท่านๆก็ใช้กันเป็นปกติกับวุ้นแปลภาษาสุดวิเศษที่ชื่อว่า Babel กันอยู่แล้ว คิดอะไรไม่ออกจับ Babel ใส่ stage-0 โลด... แล้วคุณจะได้ทุกอย่างที่ต้องการ

แม้ ES8 จะไม่ได้ฟรุ้งฟริ้งแต่ผมก็จำเป็นต้องเขียน ถ้าไม่เขียนเรื่องนี้แล้วจะเอาเรื่องไหนมาเขียน ฮือๆ บทความหมดสต๊อกแล้ว

Object.values และ Object.entries

อ็อบเจ็กต์ใน JavaScript ประกอบด้วยส่วนที่เป็น property (key) และ value เช่น

JavaScript
1const obj = {
2 a: 1,
3 b: 2,
4 c: 3,
5}

เราทราบกันดีว่าหากเราต้องการดึงค่าของ property ทั้งหมดของอ็อบเจ็กต์ออกมา เราสามารถใช้งาน Object.keys() ได้ เช่น

JavaScript
1Object.keys(obj) // ['a', 'b', 'c']

หากสถานการณ์เปลี่ยนไป เมื่อตอนนี้เราต้องการค่าของทุกๆ value เราจะสามารถเข้าถึงได้อย่างไร? ใน ES8 จึงเตรียม Object.values() เพื่อให้เราสามารถดึงทุกค่า value ออกจากอ็อบเจ็กต์ได้ผลลัพธ์กลับในรูปอาร์เรย์

JavaScript
1Object.values(obj) // [1, 2, 3]

การใช้งานทั้ง Object.values และ Object.keys มีสิ่งนึงที่ต้องคำนึงถึงเป็นพิเศษ นั่นคือทั้งสองเมธอดสามารถเข้าถึงได้เฉพาะ property ที่ตั้งค่า enumerable เป็น true ไว้เท่านั้น

JavaScript
1let obj = {}
2
3// สร้าง property ขึ้นมาสามตัวคือ foo, bar, zoo
4// โดยให้เป็น property ของ obj
5Object.defineProperties(obj, {
6 foo: {
7 value: 'foo value',
8 enumerable: true,
9 },
10 bar: {
11 value: 'bar value',
12 enumerable: true,
13 },
14 zoo: {
15 value: 'zoo value',
16 // หากไม่กำหนด enumrable จะถือว่าเป็น false
17 },
18})
19
20// เพราะว่า zoo มี enumerable เป็น false
21// ค่าข้อมูลจึงไม่โผล่ในผลลัพธ์ของการเรียกใช้งาน
22Object.keys(obj) // ['foo', 'bar']
23Object.values(obj) // ['foo value', 'bar value']

นอกจาก Object.values แล้ว ES8 ยังเพิ่ม Object.entries เพื่อคืนค่าอ็อบเจ็กต์ออกมาในรูปของ [[key1, value1], [key2, value2], ..., [keyn, valuen]]

JavaScript
1Object.entries(obj) // [['a', 1], ['b', 2], ['c', 3]]

Trailing commas ในรายการพารามิเตอร์ของฟังก์ชัน

Trailing comma นั้นคือเครื่องหมายคอมมาที่ต่อตูดเป็นตัวสุดท้าย ใน JavaScript เวอร์ชันก่อนหน้านี้เราสามารถใช้ trailing comma ควบคู่กับอ็อบเจ็กต์และอาร์เรย์ได้ดังนี้

JavaScript
1const arr = [
2 'Foo Foo Foo Foo',
3 'Bar Bar Bar Bar',
4 'Zoo Zoo Zoo Zoo', // << นี่ไงคอมมาตัวสุดท้าย
5]
6
7const obj = {
8 foo: 'Foo Foo Foo Foo',
9 bar: 'Bar Bar Bar Bar',
10 zoo: 'Zoo Zoo Zoo Zoo', // << เค้าอยู่นี่ แหมทำเป็นมองไม่เห็น
11}

คำถามคือการไม่ใส่ trailing comma ดูยังไงก็สวยกว่า แล้วแบบนี้เราจะใส่มันไปทำกระเพาะแพะรึ?

Trailing comma นั้นมีประโยชน์ครับ โดยเฉพาะกับการทำ version control สมมติว่าก่อนหน้านี้ไอ้เจ้าก้อนอาร์เรย์ของเรามีลักษณะเป็นดังนี้

JavaScript
1const arr = [
2 'Foo Foo Foo Foo',
3 'Bar Bar Bar Bar', // << trailing comma ตูอยู่หนายย~
4]

จากโค้ดข้างต้นเราไม่ได้ใส่ trailing comma เอาไว้ สามวันถัดมาเราเพิ่มข้อมูลเข้าไปในอาร์เรย์นี้ แล้วทำการ push ขึ้น Git หากเราเปิด Github ขึ้นมาดูเราก็จะเจอว่า Github ได้ไฮไลต์บรรทัดที่สองของเราเพื่อเป็นการบอกว่าเรามีการแก้ไขโค้ด

JavaScript
1const arr = [
2 'Foo Foo Foo Foo',
3 'Bar Bar Bar Bar', // เพื่อเพิ่มบรรทัดที่ 3 เราต้องใส่ , เข้าไป Github ก็จะมองว่าเรามีการแก้ไขโค้ดตรงนี้จึงทำไฮไลต์ให้
4 'Zoo Zoo Zoo Zoo', // นี่คือโค้ดใหม่ที่เราเพิ่มเข้ามา
5]

เมื่อเราเปรียบเทียบโค้ดจำนวนมากอาจปวดหัวได้กับการไฮไลต์ของเครื่องมือ Git เหล่านี้ ปัญหานี้จะหมดไปเมื่อคุณใส่ trailing comma ทุกครั้ง นั่นเพราะทุกครั้งที่เพิ่มไอเทมใหม่ คุณไม่ต้องใส่คอมมาเข้าไปก่อนหน้า Git จึงไม่ทำไฮไลต์แจ้งว่ามีการแก้ไขในบรรทัดบน

เมื่อ trailing comma มีประโยชน์แล้วใยเราจึงไม่เพิ่มไวยากรณ์เพื่ออนุญาตให้ใช้ trailing comma กับรายการพารามิเตอร์ได้ด้วย และนี่คือสิ่งที่ ES8 เตรียมไว้ให้

JavaScript
1function foo(
2 foo1,
3 foo2,
4 foo3,
5 foo4,
6 foo5,
7 foo6,
8 foo7, // ต๊ะเอ๋
9)
10
11foo(foo1, foo2, foo3, foo4, foo5, foo6, foo7,)

Async Functions

หลายคนคงจะเบื่อกับการใช้งาน Promise เพราะขี้เกียจมานั่ง then ยาวไปยาวไป

JavaScript
1const doAsync = () => {
2 return new Promise((resolve, reject) => {
3 setTimeout(() => {
4 if (Math.random() >= 0.5) resolve('BabelCoder!')
5 else reject(new Error('Less than 0.5!'))
6 }, 2000)
7 })
8}
9
10doAsync()
11 .then((text) => {
12 console.log(text)
13 })
14 .catch((error) => {
15 console.error(error.message)
16 })

นาทีนี้ Async/Await เป็นพระเอกขี่ม้าขาวที่ป็อบปูล่ามาก ที่จะมาช่วยซับน้ำตาขา Promise ให้เขียนโปรแกรมได้ลื่นไหลเสมือนนึงทำงานแบบ Synchronous แม้แต่ Node.js ก็ยังซัพพอร์ตเป็นที่เรียบร้อย แล้วมีหรือที่ ES8 จะไม่ปล่อยของออกมาซักที ตอนนี้ Async/Await เป็นมาตรฐานอย่างเต็มตัวใน ES8 แล้วแจ้

สำหรับเพื่อนๆที่รู้สึกเบาหวิวในสมองและสับสนว่า Async/Await คืออะไร เชิญเสพ บทความนี้ โดยพลัน!

Object.getOwnPropertyDescriptors

เวลาที่เราสร้าง property ขึ้นมาซักตัวในอ็อบเจ็กต์ แม้เราจะเห็นว่ามีแต่ key และ value เท่านั้นที่เราเขียนเป็นโค้ด แต่ความจริงแล้ว property แต่ละตัวมีการตั้งค่าที่มากกว่านั้น เช่น หากเราไม่ต้องการให้ property นั้นถูกเขียนค่าใหม่ได้ เราต้องเซ็ต writable ให้เป็น false

JavaScript
1// แม้เราจะสร้าง property ชื่อ foo และ bar อย่างเรียบง่าย
2let obj = { foo: 1, bar: 2 }
3
4// แต่ความเป็นจริง foo ยังมีการตั้งค่าเป็นแบบนี้
5{
6 "configurable": true,
7 "enumerable": true,
8 "value": 1,
9 "writable": true
10}

เราสามารถกำหนดการตั้งค่าของ property เหล่านั้นได้ผ่านทาง Object.defineProperty และ Object.defineProperties เช่น

JavaScript
1let obj = {}
2
3Object.defineProperties(obj, {
4 foo: {
5 value: 'foo value',
6 enumerable: true,
7 },
8 bar: {
9 value: 'bar value',
10 enumerable: true,
11 },
12 zoo: {
13 value: 'zoo value',
14 },
15})

เมื่อเราสามารถตั้งค่า property ได้เราก็ควรสอบถามภายหลังได้เช่นกันว่าการตั้งค่าเหล่านั้นเป็นเช่นไร ES2015 มาพร้อมกับ Object.getOwnPropertyDescriptor ที่จะทำให้เราทราบว่า property ตัวที่ระบุมีการตั้งค่าไว้อย่างไรบ้าง

JavaScript
1let obj = {}
2
3Object.defineProperties(obj, {
4 foo: {
5 value: 'foo value',
6 enumerable: true
7 },
8 bar: {
9 value: 'bar value',
10 enumerable: true
11 },
12 zoo: {
13 value: 'zoo value'
14 }
15})
16
17Object.getOwnPropertyDescriptor(obj, 'foo')
18
19// ผลลัพธ์
20Object {
21 "configurable": false,
22 "enumerable": true,
23 "value": "foo value",
24 "writable": false
25}

ช่างน่าขายหน้ายิ่งนัก ES2015 อนุญาตให้เราเรียกดูการตั้งค่าได้จากทีละ property ด้วยเหตุนี้ ES2017 (ES8) จึงเพิ่ม Object.getOwnPropertyDescriptors(obj) ขึ้นมา เพื่อให้เราสามารถเรียกดูการตั้งค่าของทุกๆ properties ในอ็อบเจ็กต์นั้นได้ในคราวเดียว

JavaScript
1let obj = {}
2
3Object.defineProperties(obj, {
4 foo: {
5 value: 'foo value',
6 enumerable: true
7 },
8 bar: {
9 value: 'bar value',
10 enumerable: true
11 },
12 zoo: {
13 value: 'zoo value'
14 }
15})
16
17Object.getOwnPropertyDescriptors(obj)
18
19// ผลลัพธ์
20Object {
21 "bar": Object {
22 "configurable": false,
23 "enumerable": true,
24 "value": "bar value",
25 "writable": false
26 },
27 "foo": Object {
28 "configurable": false,
29 "enumerable": true,
30 "value": "foo value",
31 "writable": false
32 },
33 "zoo": Object {
34 "configurable": false,
35 "enumerable": false,
36 "value": "zoo value",
37 "writable": false
38 }
39}

String Padding

สมมติว่าเรามีกลุ่มของราคาสินค้า แน่นอนว่าเราสามารถพิมพ์ค่าของพวกมันออกมาดูได้

JavaScript
1const prices = ['1,000,000', '999', '38,900', '64,111']
2
3prices.forEach((price) => console.log(price))
4
5// ผลลัพธ์
6;('1,000,000')
7;('999')
8;('38,900')
9;('64,111')

ช่างบัดซบเสียนี่กระไร เราอยากให้การพิมพ์ผลลัพธ์ของเรานั้นชิดขวาต่างหาก แบบนี้ไงๆ

JavaScript
1' 1,000,000'
2' 999'
3' 38,900'
4' 64,111'

ES8 รู้ใจคุณจึงเตรียม padStart และ padEnd ไว้ให้ จากตัวอย่างก่อนหน้านี้คุณเพียงใช้ padStart ปัญหาชีวิตรักก็จะจบสิ้น

JavaScript
1const prices = ['1,000,000', '999', '38,900', '64,111']
2
3prices.forEach((price) => console.log(price.padStart(10)))

padStart และ padEnd นั้นเราต้องระบุความยาวเข้าไป string ใหม่ที่เป็นผลลัพธ์จะมีความยาวตามที่ระบุ หากความยาวที่ระบุยาวกว่า string ต้นฉบับ JavaScript จะทำการเติมช่องว่างเข้าไปให้จนครบความยาวที่กำหนด โดยกรณีของ padStart จะเป็นการเติมช่องว่างเข้าข้างหน้า ในทางกลับกัน padEnd ก็จะเติมช่องว่างเข้าประตูหลังนั่นเอง อ้า~ ฟิน... ไอ้บ้า!

JavaScript
1'babel coder'.padStart(15) // " babel coder"
2'babel coder'.padEnd(15) // "babel coder "

ในกรณีที่ช่องว่างไม่ใช่สิ่งที่เราต้องการ เราสามารถระบุสิ่งอื่นได้ด้วยการระบุเข้าไปเป็นอาร์กิวเมนต์ตัวที่สอง

JavaScript
1'babel coder'.padEnd(15, '*') // "babel coder****"

ใส่ดอกจัน (เอาน้ำแข็งมาถูหลัง) แล้วให้ความรู้สึกเหมือนเซ็นเซอร์คำหยาบ 18+ เลยแฮะ~

Shared memory และ atomics

เรื่องนี้ข้ามไปเลยเพราะคงได้พูดกันยาวแน่ๆ ขอแปะโป้งไว้กล่าวถึงในบทความอื่นจะดีกว่า แต่ถ้าใครใจร้อน บทความนี้คือคำตอบของทุกสรรพสิ่งจ้า

สรุป

ES2017 หรือ ES8 ก็เหมือนการอัพเดทรายปี ความหวือหวาก็มีอยู่เช่น Async/Await แต่เพราะ Babel ที่เป็นตัวทำลายทุกโลกธาตุ ทำให้เราๆท่านๆนักพัฒนารู้สึกว่าการมาของ ES8 นั้นหน่อมแน้ม เพราะหลายๆฟีเจอร์ข้าก็ใช้ผ่าน Babel อยู่แล้ว พูดอีกก็ถูกอีก แม้ ES8 จะอุบัติขึ้นบนจักรวาลแล้ว แต่เราก็ยังคงต้องใช้ Babel หรือ TypeScript กันต่อไป นั่นเพราะฟีเจอร์หลายๆอย่างก็ยังคงไม่ได้รับการซัพพอร์ตในบราวเซอร์ทั่วๆไป (โดยเฉพาะเจ้าเก่าอย่าง IE/Edge และเจ้าใหม่ผู้เรื่องมากอย่าง Safari)

เอกสารอ้างอิง

Dor Moshe (2017). ES8 was Released and here are its Main New Features. Retrieved July, 12, 2017, from https://hackernoon.com/es8-was-released-and-here-are-its-main-new-features-ee9c394adf66

Dr. Axel Rauschmayer (2017). ES proposal: Shared memory and atomics. Retrieved July, 12, 2017, from http://2ality.com/2017/01/shared-array-buffer.html

สารบัญ

สารบัญ

  • Object.values และ Object.entries
  • Trailing commas ในรายการพารามิเตอร์ของฟังก์ชัน
  • Async Functions
  • Object.getOwnPropertyDescriptors
  • String Padding
  • Shared memory และ atomics
  • สรุป
  • เอกสารอ้างอิง