Babel Coder

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

beginner

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

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

Object.values และ Object.entries

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

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

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

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

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

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

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

let obj = {}

// สร้าง property ขึ้นมาสามตัวคือ foo, bar, zoo
// โดยให้เป็น property ของ obj
Object.defineProperties(obj, {
  foo: {
    value: 'foo value',
    enumerable: true
  },
  bar: {
    value: 'bar value',
    enumerable: true
  },
  zoo: {
    value: 'zoo value'
    // หากไม่กำหนด enumrable จะถือว่าเป็น false
  }
})

// เพราะว่า zoo มี enumerable เป็น false
// ค่าข้อมูลจึงไม่โผล่ในผลลัพธ์ของการเรียกใช้งาน
Object.keys(obj) // ['foo', 'bar']
Object.values(obj) // ['foo value', 'bar value']

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

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

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

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

const arr = [
  'Foo Foo Foo Foo',
  'Bar Bar Bar Bar',
  'Zoo Zoo Zoo Zoo', // << นี่ไงคอมมาตัวสุดท้าย
]

const obj = {
  foo: 'Foo Foo Foo Foo',
  bar: 'Bar Bar Bar Bar',
  zoo: 'Zoo Zoo Zoo Zoo', // << เค้าอยู่นี่ แหมทำเป็นมองไม่เห็น
}

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

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

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

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

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

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

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

function foo(
  foo1,
  foo2,
  foo3,
  foo4,
  foo5,
  foo6,
  foo7, // ต๊ะเอ๋
)

foo(foo1, foo2, foo3, foo4, foo5, foo6, foo7,)

Async Functions

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

const doAsync = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(Math.random() >= 0.5) resolve('BabelCoder!')
      else reject(new Error('Less than 0.5!'))
    }, 2000)
  })
}

doAsync().then((text) => {
  console.log(text)
}).catch((error) => {
  console.error(error.message)
})

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

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

Object.getOwnPropertyDescriptors

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

// แม้เราจะสร้าง property ชื่อ foo และ bar อย่างเรียบง่าย
let obj = { foo: 1, bar: 2 }

// แต่ความเป็นจริง foo ยังมีการตั้งค่าเป็นแบบนี้
{
  "configurable": true,
  "enumerable": true,
  "value": 1,
  "writable": true
}

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

let obj = {}

Object.defineProperties(obj, {
  foo: {
    value: 'foo value',
    enumerable: true
  },
  bar: {
    value: 'bar value',
    enumerable: true
  },
  zoo: {
    value: 'zoo value'
  }
})

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

let obj = {}

Object.defineProperties(obj, {
  foo: {
    value: 'foo value',
    enumerable: true
  },
  bar: {
    value: 'bar value',
    enumerable: true
  },
  zoo: {
    value: 'zoo value'
  }
})

Object.getOwnPropertyDescriptor(obj, 'foo')

// ผลลัพธ์
Object {
  "configurable": false,
  "enumerable": true,
  "value": "foo value",
  "writable": false
}

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

let obj = {}

Object.defineProperties(obj, {
  foo: {
    value: 'foo value',
    enumerable: true
  },
  bar: {
    value: 'bar value',
    enumerable: true
  },
  zoo: {
    value: 'zoo value'
  }
})

Object.getOwnPropertyDescriptors(obj)

// ผลลัพธ์
Object {
  "bar": Object {
    "configurable": false,
    "enumerable": true,
    "value": "bar value",
    "writable": false
  },
  "foo": Object {
    "configurable": false,
    "enumerable": true,
    "value": "foo value",
    "writable": false
  },
  "zoo": Object {
    "configurable": false,
    "enumerable": false,
    "value": "zoo value",
    "writable": false
  }
}

String Padding

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

const prices = [
  '1,000,000',
  '999',
  '38,900',
  '64,111'
]

prices.forEach(price => console.log(price))

// ผลลัพธ์
"1,000,000"
"999"
"38,900"
"64,111"

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

" 1,000,000"
"       999"
"    38,900"
"    64,111"

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

const prices = [
  '1,000,000',
  '999',
  '38,900',
  '64,111'
]

prices.forEach(price => console.log(price.padStart(10)))

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

'babel coder'.padStart(15) // "    babel coder"
'babel coder'.padEnd(15) // "babel coder    "

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

'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


แสดงความคิดเห็นของคุณ


No any discussions