พื้นฐาน ES2015 (ES6) สำหรับการเขียน JavaScript สมัยใหม่
ยุคของ ES2015 มาถึงแล้ว ผมเชื่อว่าเพื่อนๆหลายคนน่าจะกำลังใช้ ES2015 ในการเขียน JavaScript ปัจจุบันอยู่ สำหรับบทความนี้ผมจะแนะนำการใช้ ES2015 ในแบบฉบับที่เพื่อนๆจะได้พบเห็นในโลกของ JavaScript สมัยใหม่ พร้อมทั้งแนะนำจุดบอดหรือมุมมองที่เพื่อนๆอาจยังไม่เข้าใจหรือใช้ผิดกันอยู่ อย่าเสียเวลาแล้วลุยกันเลย!
ลาก่อน semicolon เพื่อนยาก
สำหรับ ES2015 นั้นไม่มีความจำเป็นที่คุณต้องใส่ semicolon หรือ ; อีกต่อไป ที่ผมพูดว่าไม่ต้องใส่นี้ไม่ได้หมายความว่า JavaScript เป็นภาษาที่สลัด semicolon ทิ้งเสียแล้ว แท้ที่จริงนั้น JavaScript ยังต้องการ semicolon อยู่ เพียงแต่ ES2015 นั้นฉลาดพอที่จะเติม semicolon ให้เราอัตโนมัติ (automatic semicolon insertion) ไร้ซึ่ง semicolon โค๊ดของเราจึงดูสวยขึ้น นี่ไม่ใช่สิ่งมหัศจรรย์ถ้าคุณไปเห็นโค๊ดของไลบรารี่ของใครซักคนในโลก React ที่ไม่ปิดท้ายด้วย ;
1// โค๊ดที่เปลือยเปล่าไร้ semicolon ของฟังก์ชัน applyMiddleware ใน Redux2export default function applyMiddleware(...middlewares) {3 return (createStore) => (reducer, initialState, enhancer) => {4 var store = createStore(reducer, initialState, enhancer)5 var dispatch = store.dispatch6 var chain = []78 var middlewareAPI = {9 getState: store.getState,10 dispatch: (action) => dispatch(action),11 }12 chain = middlewares.map((middleware) => middleware(middlewareAPI))13 dispatch = compose(...chain)(store.dispatch)1415 return {16 ...store,17 dispatch,18 }19 }20}
หมายเหตุ: ASI มีมาก่อน ES6 ออก แต่ สมัย ES5 แม้เราจะละ semicolon ออกได้ แต่เราก็จะไม่ทำกันนั่นเพราะเครื่องมือหลายตัวจะทำเอาเจ๊งถ้าเอา semicolon ออก เช่นพวก compressors
การมาของ ES6 มาพร้อมกับเครื่องมือที่ทำให้ความหนักใจของการเอา semicolon ออกลดลง จนเราเริ่มนิยมเอา semicolon ออกสำหรับโค้ด ES6 และยังคง semicolon ไว้สำหรับโค้ด ES5 ครับ
จาก var สู่ let และ const
ผมว่าเพื่อนๆคงเริ่มใช้ทั้ง let และ const กันแล้ว แต่ทราบหรือไม่ว่า let/const นั้นมีขอบเขตการมีตัวตนหรือ scope ที่แตกต่างจาก var กล่าวคือ var นั้นเป็น function-scoped หรือพูดง่ายๆคือเราใช้ var ประกาศตัวแปรตรงจุดไหนก็ตาม แต่เมื่อ JavaScript เริ่มทำงาน การประกาศตัวแปรด้วย var นั้นจะกระดึ๊บๆตัวเองไปอยู่ใกล้กับจุดประกาศฟังก์ชันที่ใกล้ที่สุดหรือที่เราเรียกกันว่า hoisting
1function foo(isValid) {2 if (isValid) {3 var x = 14 return x5 }67 return x8}910// มีค่าเท่ากับ11function foo(isValid) {12 var x // ดีดตัวเองมาอยู่ใกล้จุดประกาศฟังก์ชัน13 if (isValid) {14 var x = 115 return x16 }1718 return x19}
สำหรับ let และ const นั้นเป็น block-scoped หมายถึงประกาศตัวแปรอยู่ในบลอคไหนก็จะอยู่แค่ในบลอคนั้น ไม่เสนอหน้าดีดตัวเองออกไปใกล้ฟังก์ชันแบบที่ var ทำ
1// พิมพ์ 2 ออกมาทั้งสองครั้ง2for (var i = 0; i < 2; i++) {3 // เนื่องจากในจังหวะที่ console.log ทำงาน loop ได้วนไปจนครบแล้ว4 // ทำให้ในขณะนั้่นค่า i มีค่าเป็น 25 // อย่าลืมว่า i ประกาศโดยใช้ var มันจึงผลักตัวเองออกจาก for6 // หรือพูดง่ายๆคือ i นั้นเป็นตัวแปรตัวเดียวทุกครั้งที่วนลูปก็เพิ่มค่าใส่ตัวแปรเดิม7 setTimeout(function () {8 console.log(i)9 }, 100)10}1112// ผลลัพธ์ได้ 0 และ 113// ใช้ let ประกาศตัวแปรทำให้ตัวแปรเป็น block-scoped14// พูดง่ายๆคือ i จะเกิดขึ้นทุกครั้งที่วนลูป15// และไม่ซ้ำกับ i ก่อนหน้า16// เนื่องจากเป็น block-scoped มันจึงมีช่วงชีวิตอยู่แค่ใน {}17for (let i = 0; i < 2; i++) {18 setTimeout(function () {19 console.log(i)20 }, 100)21}
ความต่างของ let และ const คือ let นั้นหลังประกาศตัวแปรแล้วสามารถเปลี่ยนค่าได้ แต่ const ใช้สำหรับประกาศค่าคงที่ ทำให้หลังประกาศแล้วไม่สามารถเปลี่ยนแปลงค่าได้ การใช้ const นั้นมีข้อพึงระวังลองดูตัวอย่างครับ
1let a = 02a = 134const PI = 3.145PI = 1 // "PI" is read-only เปลี่ยนค่าไม่ได้อีกแล้วนะ67// obj เก็บ address หรือที่อยู่ของ { a: 1 }8// เราเปลี่ยน address ไม่ได้ เช่น obj = {} แบบนี้คือเปลี่ยน memory address ทำไม่ได้9const obj = { a: 1 }10// แต่การเปลี่ยนค่าภายใน object ไม่ได้ทำให้ memory address เปลี่ยนไป11obj.a = 212console.log(obj) // { a: 2 }
เพื่อนๆที่เคยใช้ Redux จะทราบว่าเรานิยมใช้ switch/case กันใน reducer
1export default (state = initialState, action) => {2 switch (action.type) {3 case LOAD_PAGES_SUCCESS:4 // ลดการเรียกชื่อยาวๆด้วยการประกาศตัวแปรมารองรับ5 // มั่นใจ 100% ว่า pages นี้ไม่เปลี่ยนแปลงแน่นอนเลยใช้ const ซะเลย6 const pages = action.result.entities.pages7 return { ...state, loading: false, items: pages }8 default:9 return state10 }11}
การเขียนแบบข้างบนฟังดูดีนะครับ เราอาจจะใช้ pages ไปทำสารพัดอย่างก่อน return ออกไปข้างนอก เราเลยสร้างตัวแปรมารองรับมัน แต่ถ้ามี case เพิ่มมาอีกอันแบบนี้หละ?
1export default (state = initialState, action) => {2 switch (action.type) {3 case LOAD_PAGES_SUCCESS:4 const pages = action.result.entities.pages5 return { ...state, loading: false, items: pages }6 case LOAD_PAGE_SUCCESS:7 // ประกาศแบบนี้พังทันที นั่นเป็นเพราะ const เป็น block-scoped8 // ทำให้ scope ของมันอยู่ภายใต้ {} ของ switch9 // ใน case ก่อนหน้าเราประกาศ pages ไปแล้วจึงชนกัน10 // ควรย้ายการประกาศตัวแปรที่ทำซ้อนแบบนี้ออกไปข้างนอก11 const pages = action.result.entities.pages12 return { ...state, loading: false, items: [pages[0]] }13 default:14 return state15 }16}
อย่าคิดว่าเรื่องแบบนี้ไม่เคยเกิดขึ้นนะครับ เพราะมันมีคำถามใน stackoverflow มาแล้ว
ES2015 Module
JavaScript ไม่เคยจัดการโมดูลได้ด้วยตัวเองมาก่อนต้องทำผ่านไลบรารี่อย่าง CommonJS หรือ AMD การมาของ ES2015 มาพร้อมกับการสนับสนุนการทำงานกับโมดูลในตัว ตอนนี้คุณสามารถใช้ ES2015 เพื่อ import/export ของจากไฟล์หนึ่งไปอีกไฟล์หนึ่งได้แล้ว ดังนี้
1// dog.js2export const DEFAULT_COLOR = 'white'3export function walk() {4 console.log('Walking...')5}67// main.js8// เลือกนำเข้าเฉพาะ DEFAULT_COLOR9import { DEFAULT_COLOR } from './dog.js'1011// main.js12// นำเข้าทุกสรรพสิ่งที่ export จาก dog13// แล้วตั้งชื่อใหม่ให้ว่า lib14import * as lib from './dog.js'
ถ้าหากโมดูลนั้นมีแค่สิ่งเดียวที่อยาก export ทำได้ดังนี้
1// circle.js2// สังเกตคำว่า default3export default class Circle {4 area() {}5}67// main.js8import Circle from './circle.js'
ES2015 module นั้นฉลาด มันมีการตรวจสอบว่าเรา import อะไรเข้ามาบ้าง ถ้าสิ่งไหนไม่ได้ import มันจะไม่นำมารวม ทำให้ไฟล์มีขนาดเล็กกว่าการใช้ CommonJS module เนื่องจาก CommonJS จะ import ทุกสรรพสิ่งที่โมดูลนั้น export ออกมา
1// dog.js2export const DEFAULT_COLOR = 'white'3export function walk() {4 console.log('Walking...')5}67// main.js8// เลือกนำเข้าเฉพาะ walk9import { walk } from './dog.js'10walk()1112// ผลลัพธ์สุดท้ายจะเป็น...13// สังเกตว่าไม่มี DEFAULT_COLOR ติดมาด้วย14function walk()15 console.log('Walking...')16}17walk()
เรื่องที่ต้องพึงระวังในการใช้ ES2015 module มีดังนี้
- ให้ import เฉพาะสิ่งที่จำเป็นต้องใช้จริงๆ
1import _ from 'lodash'23_.map([1, 2, 3], (item) => item * 2)45// จากตัวอย่างนี้เรา import ทุกสิ่งที่ lodash มีมาใส่ตัวแปร _6// ทั้งๆที่ความจริงเราใช้แค่ map7// วิธีนี้จะทำให้ไฟล์ผลลัพธ์ของเรามีขนาดใหญ่ เพราะอุดมไปด้วยของที่ไม่ใช้8// วิธีต่อไปนี้จึงเหมาะสมกว่า910import map from 'lodash/map'11map([1, 2, 3], (item) => item * 2)
นอกจากนี้เพื่อนๆควรตรวจสอบให้แน่ใจว่าไลบรารี่ที่เรานำมาใช้นั้นมีอะไรใน JavaScript ที่ใช้ทดแทนได้หรือไม่ เพื่อจะได้ไม่ทำให้ไฟล์ผลลัพธ์มีขนาดใหญ่จากการใช้ไลบรารี่เหล่านั้น เช่น จากตัวอย่างข้างบนพบว่าสามารถใช้ Array#map ใน JavaScript แทนได้โดยไม่ต้องพึ่ง lodash
ตรวจสอบให้แน่ใจว่าขั้นตอนการทำงานของ build tool ของคุณ แอบแปลงโค๊ดคุณเป็น CommonJS ก่อนหรือไม่ ถ้ามีการแปลงนั่นหมายความว่า แม้คุณจะ import บางสิ่งเข้ามา แต่ด้วยความเป็น CommonJS มันจะ import ทุกสรรพสิ่งแม้คุณไม่ต้องการ ตัวอย่างเช่นการใช้ import/export ใน Webpack1
1// ถ้าเพื่อนๆใช้ Webpack1 แม้เราจะเลือกเฉพาะสามตัวนี้ให้ import เข้ามา2// แต่ด้วยความที่ Webpack1 แปลงเป็น CommonJS ก่อน3// import ที่เราทำจึงกลายเป็นการ import ทุกสิ่งเข้ามาในไฟล์อยู่ดี4// เพียงแต่มี map, filter และ reduce ที่เรียกใช้งานได้5import { map, filter, reduce } from 'lodash'67// ควรเปลี่ยนแปลงเป็นสิ่งนี้8import map from 'lodash/map'9import filter from 'lodash/filter'10import reduce from 'lodash/reduce'
สำหรับเพื่อนๆคนไหนที่เผลอเรียก import _ from 'lodash' ไปแล้วก็ไม่ต้องเสียใจ ผมมี babel plugin ตัวนึงมาฝาก ที่จะทำแปลงการ import ของเพื่อนๆให้เป็นตามที่ผมแนะนำ รออะไรอยู่เล่า โหลดเลยซิ
ที่กล่าวไปทั้งหมดในหัวข้อนี้เป็นการอิมพอร์ตแบบ static แต่ถ้าเราต้องการอิมพอร์ตแบบ dynamic หรืออิมพอร์ตในช่วย runtime หละ? โดยปกติเรามักใช้ require ในการอิมพอร์ตกันใช่ไหมครับ แต่มันมีข้อเสียอยู่คือถ้าอิมพอร์ตไม่สำเร็จเราก็ไม่สามารถจัดการกับข้อผิดพลาดที่เกิดขึ้นได้ ใน ES2015 มีของเล่นใหม่ดังนี้
1System.import('module_name')2 .then(module => { ... })3 // จัดการ error ในนี้4 .catch(error => ...)
Destructuring
Destructuring เป็นฟีเจอร์สำหรับการดึงส่วนของข้อมูลออกมาทำให้เราเขียนโค๊ดได้ง่ายขึ้น
1let person = {2 age: 24,3 gender: 'male',4 name: {5 firstName: 'firstName',6 lastName: 'lastName',7 },8}910// ถ้าเราต้องการค่าเหล่านี้ออกจากอ็อบเจ็กต์ ต้องมาประกาศตัวแปรแบบนี้11let age = person.age12let genger = person.gender13let name = person.name14let firstName = name.firstName15let lastName = name.lastName1617// หากใช้ Destructuring จะเหลือแค่นี้18let { age, gender, name } = person19let { firstName, lastName } = name2021// แต่ในความเป็นจริงแล้ว name เป็นเพียงแค่ทางผ่าน22// เราไม่ต้องการมัน เราต้องการแค่ firstName และ lastName23// จึงใช้ Destructuring ซ้อนเข้าไปอีกครั้ง24// เพียงเท่านี้ตัวแปร name ก็จะไม่เกิดขึ้นมาให้รำคาญใจ25let {26 age,27 gender,28 name: { firstName, lastName },29} = person
รู้จักกับ Spread Operator
Spread Operator หรือผมขอเรียกมันง่ายๆว่าเครื่องหมายแตกตัวแล้วกัน เป็นจุดไข่ปลาสามจุด (...) ที่เอาไปวางหน้าอาร์เรย์หรืออ็อบเจ็กต์แล้วมีผลทำให้เครื่องหมายที่ครอบมันอยู่หลุดออก ดูตัวอย่างกันเลย
1let obj1 = { a: 1, b: 2 }2let obj2 = { c: 3, d: 4 }3console.log({ ...obj1, ...obj2 }) // {"a":1,"b":2,"c":3,"d":4}45let arr1 = [1, 2, 3]6let arr2 = [4, 5, 6]7console.log([...arr1, ...arr2]) // [1,2,3,4,5,6]
สารพัดวิธีแบบใหม่ในการจัดการกับฟังก์ชัน
ES2015 มาพร้อมกับความสามารถในการจัดการฟังก์ชันที่มากขึ้นดังนี้
Arrow Function
จากเดิมที่เราต้องประกาศฟังก์ชันด้วยการใช้คีย์เวิร์ด function
ใน ES2015 เราลดรูปการประกาศให้เหลือเพียงลูกศรสองเส้นหรือ fat arrow เหมือนในภาษา CoffeeScript ดังนี้
1// ES52function(arguments) {34}56// ES20157(arguments) => {89}
Arrow function ไม่ได้ต่างเพียงแค่ไวยากรณ์ที่เปลี่ยนไป แต่ arrow function นั้นยังเข้าถึง this
จาก scope ที่ครอบมันอยู่ (Lexical binding) ดังนี้
1function Dog() {2 this.color = 'white'34 setTimeout(function () {5 // this ตัวนี้หมายถึง this ใน context ของฟังก์ชันนี้6 // จึงไม่มีการพิมพ์อะไรออกไป เพราะในฟังก์ชันนี้ this ไม่มีค่าของ color7 console.log(this.color)8 }, 100)9}1011new Dog()1213// ถ้าต้องการให้พิมพ์ค่า color ออกมาต้องแก้ไขใหม่เป็น14function Dog() {15 this.color = 'white'16 let self = this1718 setTimeout(function () {19 // เรียกผ่านตัวแปร self แทน20 console.log(self.color)21 }, 100)22}2324// หรือใช้ arrow function ดังนี้25function Dog() {26 this.color = 'white'2728 setTimeout(() => {29 // this ของ arrow function นี้จะหมายถึง30 // this ตัวบน31 console.log(this.color)32 }, 100)33}
กรณีของ Arrow Function ถ้าตัว body ของฟังก์ชันไม่ครอบด้วย {} และมี statement เดียว จะถือว่าค่านั้นคือค่าที่ return ออกจากฟังก์ชัน
1const fn = () => 323console.log(fn()) // 3
นอกจากนี้เราสามารถละ () ได้ถ้าพารามิเตอร์นั้นมีเพียงตัวเดียว
1const arr = [1, 2, 3]2// มีค่าเท่ากับ arr.map((x) => x * x)3arr.map((x) => x * x)
Default Values
ใน ES5 เราตรวจสอบค่าของพารามิเตอร์ที่ส่งเข้ามาในฟังก์ชัน หากไม่มีการส่งค่าเข้ามาเราอาจตั้งค่าเริ่มต้นของตัวแปรไว้ภายในฟังก์ชัน สำหรับ ES2015 เราสามารถกำหนดค่าเริ่มต้นของพารามิเตอร์ได้เลยด้วยการประกาศไว้ในส่วนประกาศฟังก์ชัน ดังนี้
1// ES52function foo(genger) {3 gender = gender || 'male;4}56// ES20157function foo(gender = 'male') {89}
Named Parameters
ใน ES5 บางครั้งเราส่ง object เข้าไปในฐานะของ options พร้อมทั้งมีการตรวจสอบค่าต่างๆในอ็อบเจ็กต์ ถ้าค่าไหนไม่มีก็จะกำหนดค่าเริ่มต้นให้ ใน ES2015 เราสามารถใช้ destructuring เพื่อทำสิ่งเดียวกันกับที่ทำใน ES5 ได้ดังนี้
1// ES52function request(options) {3 var method = options.method || 'GET'4 var ssl = options.ssl || false5 console.log(method)6}7request({})89// ES201510function request({ method = 'GET', ssl = false }) {11 console.log(method)12}13request({})
นอกจากนี้เราอาจต้องการตรวจสอบค่าที่อ็อบเจ็กต์ที่ส่งเข้ามาก่อนด้วยว่ามีค่าหรือไม่ ถ้าไม่มีให้กำหนดค่าเริ่มต้นเป็น {} ดังนี้
1// ES52function request(options) {3 options = options || {}4 var method = options.method || 'GET'5 var ssl = options.ssl || false6 console.log(method)7}8request()910// ES201511// กำหนดค่าเริ่มต้นเป็น {}12function request({ method = 'GET', ssl = false } = {}) {13 console.log(method)14}
Rest Parameters
ไม่ใช่พารามิเตอร์ทั้งหมดที่เราสนใจตั้งชื่อให้ ลองดูตัวอย่างต่อไปนี้ครับ เราจะทำการบวกเลขกันโดยผมต้องการรับพารามิเตอร์แค่สองค่า ค่าแรกเป็นค่าเริ่มต้นยัดใส่ตัวแปรชื่อ initial ส่วนที่เหลือจะมีกี่ตัวเลขไม่สน ยัดใส่ตัวแปรชื่อ rest ดังนี้
1// ไม่ว่าจะส่งตัวเลขเข้ามากี่ตัว ตัวแรกจะเป็น initial2// ส่วนตัวอื่นจะเก็บอยู่ในอาร์เรย์ชื่อ rest3function sum(initial, ...rest) {4 return rest.reduce((prev, cur) => prev + cur, initial)5}67console.log(sum(10, 1, 2, 3)) // 16
ลดความซ้ำซ้อนด้วยการเขียนให้สั้นลง
ใน ES2015 นั้นเพื่อนๆสามารถลดรูปการประกาศฟังก์ชันในอ็อบเจ็กต์ โดยละคีย์เวิร์ด function ดังนี้
1// ES52const obj = {3 foo: function () {},4}56// ES20157const obj = {8 foo() {},9}
นอกจากนี้ถ้า key ของอ็อบเจ็กต์มีชื่อตรงกับตัวแปลที่จะใส่เข้าไปเป็น value แล้ว เพื่อนๆสามารถละรูปได้เช่นกันดังนี้
1// ES52const foo = 'foo'3const bar = 'bar'45const obj = {6 foo: foo,7 bar: bar,8}910// ES201511const foo = 'foo'12const bar = 'bar'1314const obj = {15 foo,16 bar,17}
Template String
เมื่อก่อนเราใช้ + เพื่อต่อข้อความ แต่สำหรับ ES2015 นั้น Template String ทำให้การต่อข้อความเป็นเรื่องง่ายขึ้น ใช้ `` ครอบข้อความที่จะทำเป็น template string จากนั้นใช้ ${} สำหรับส่วนที่ต้องการแทรกส่วนของ JavaScript
1const name = 'Nuttavut Thongjor'2console.log(`สวัสดีชาวโลก ผมชื่อ${name}`)34// นอกจากนี้ template string ยังเอื้อต่อการทำ multiline string ด้วย5const longString = `6 Lorem Ipsum is simply dummy text of the printing7 and typesetting industry.8 Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,9 when an unknown printer took a galley of type and scrambled10 it to make a type specimen book.11`
การใช้งานคลาสใน ES2015
เป็นที่ทราบกันดีว่า JavaScript นั้นเป็นภาษาแบบ prototype-based เราจำลองฟังก์ชันให้เป็นคลาสและสร้างเมธอดผ่าน prototype นั่นเป็นสิ่งที่เราทำมากันเสมอ ใน ES2015 ได้นำการสร้างคลาสเข้ามาสู่ไวยากรณ์ทางภาษา แต่นั่นไม่ได้หมายความว่า prototype-based แบบเดิมๆจะหายไป
1// ประกาศคลาสผ่านคีย์เวิร์ด class2class Person {3 constructor(name, age) {4 this.name = name5 this.age = age6 }78 static species = 'Homo sapiens sapiens'910 walk() {11 console.log("I'm walking...")12 }1314 print() {15 console.log(`My name is ${this.name}`)16 console.log(`I'm ${this.age}'`)17 }18}1920const person = new Person('MyName', 99)21person.walk() // I'm walking...22person.print() // My name is MyName \n I'm 99'2324// static method เรียกตรงผ่านคลาสได้เลย25console.log(Person.species) // Homo sapiens sapiens2627// สำหรับการทำ inheritance สามารถใช้คีย์เวิร์ด extends ดังนี้28class Female extends Person {}
Promise
เพื่อไม่ให้เป็นการเขียนบทความทับซ้อน เชิญเพื่อนๆอ่านบทความที่ผมเคยเขียนแล้วในเรื่องกำจัด Callback Hell ด้วย Promise และ Async/Await
Lodash และ ES2015
ผมค่อนข้างมันใจเลยทีเดียวว่าเพื่อนๆในที่นี้รู้จักและใช้ Lodash กันอยู่ ข่าวดีคือการมาของ ES2015 ทำให้คุณอาจไม่จำเป็นต้องใช้ Lodash อีกต่อไป เนื่องจากมีหลายๆฟังก์ชันที่สามารถทดแทน Lodash ได้แล้ว เช่น
1;[1, 2, 3].includes(1)2'Hello World'.startsWith('Hello')
บทความนี้ต้องการนำเสนอ ES2015 ที่น้อยที่สุดที่ควรรู้ ความจริงแล้ว ES2015 เป็นเรื่องค่อนข้างใหญ่ มีหลายหัวข้อที่เปิดมิติการเรียนรู้ใหม่ๆในโลก JavaScript เพื่อนๆที่สนใจสามารถอ่านเพิ่มเติมได้ครับ
สารบัญ
- ลาก่อน semicolon เพื่อนยาก
- จาก var สู่ let และ const
- ES2015 Module
- Destructuring
- รู้จักกับ Spread Operator
- สารพัดวิธีแบบใหม่ในการจัดการกับฟังก์ชัน
- ลดความซ้ำซ้อนด้วยการเขียนให้สั้นลง
- Template String
- การใช้งานคลาสใน ES2015
- Promise
- Lodash และ ES2015