บทความชุด: JavaScript/Fundamental for beginner
รวมเนื้อหาการใช้ภาษา JavaScript สำหรับมือใหม่ ตั้งแต่หลักการ แนวคิด การทำงานกับเว็บทั้งฝั่งclient-server library&frameworkที่น่าสนใจ จนถึงมาตราฐานการเขียนแบบใหม่ ES6 (ECMAScript6)
ภาษาโปรแกรมนั้นมีการปรับปรุงไปเรื่องๆ JavaScript ก็เช่นกัน ถ้าย้อนไปดูประวัติของมันจะพบว่ามันเกิดขึ้นมาเพื่อทำงานง่ายๆ ให้เบราเซอร์มี interactive กับผู้ใช้ได้นิดหน่อยเท่านั้น โดยที่คนสร้างคงไม่ได้คาดว่ามันจะเดินทางมาไกลขนาดนี้ ทำให้ต้องออกเวอร์ชั่นใหม่ๆ ที่มีฟีเจอร์เหมือนภาษาระดับสูงภาษาอื่นมา
ซึ่งตัวที่เราควรจะรู้จักในขณะนี้คือ เวอร์ชั่นที่ 6 หรือ ES6 (ถ้าเรียกตามปีคือ ES2015) ที่ปัจจุบันเบราเซอร์ที่รองรับการเขียน JavaScript แบบ ES6 ก็มีมากขึ้นเยอะแล้ว เพราะมันออกมาตั้งหนึ่งปีแล้ว (จริงๆ ตอนนี้เขากำลังจะไป ES7 กันแล้วล่ะ)
ในบทความนี้จะมาแนะนำฟีเจอร์ใหม่ๆ ที่เพิ่มเข้ามาใน ES6 สำหรับคนที่เคยเขียนแต่แบบ ES5 อาจจะต้องปรับตัวตามซะหน่อยนะ
let กับ const
จากเดิมเวลาเราจะประกาศตัวแปรในภาษา JavaScript เราจะใช้คำสั่ง var
แต่ข้อเสียหลักๆ ของ var คือมันเป็นการประกาศตัวแปรระดับ function-scope
ดัวอย่างเช่น
function dummy(){ var x = true; if(x){ var y = 'test'; for(var i = 0; i<10; i++){ var z = 1; } } }
ใน JavaScript จะมีการทำ Hoisting หรือยกการประกาศตัวแปรทั้งหมดที่เกิดขึ้นใน function นั้นขึ้นไปอยู่บรรทัดแรกเสมอ แปลว่าโค้ดข้างบนความจริงแล้วจะมีหน้าตาแบบนี้
function dummy(){ var x, y, z, i; x = true; if(x){ y = 'test'; for(i = 0; i<10; i++){ z = 1; } } }
แต่สำหรับ ES6 ได้มีการเพิ่มการประกาศตัวแปรแบบใหม่มาอีก 2 ตัว คือ let และ const ซึ่งเป็นการใช้ตัวแปรระดับ block-scope (ระดับ { } เช่น if else for while) คือมีผลแค่ในบล๊อกที่ประกาศมันขึ้นมาเท่านั้น
function dummy(){ let x = true; if(x){ let y = 'test'; for(let i = 0; i<10; i++){ let z = 1; } //ไม่สามารถใช้ z i ได้ } //ไม่สามารถใช้ y z i ได้ }
สรุปคือ let จะใช้แทน var เมื่ออยากให้มันมีผลแค่ block-scope เท่านั้น ส่วน const จะคล้ายๆ กับ let เพียงแต่ว่าเมื่อเซ็ตค่าเข้าไปแล้วจะไม่สามารถแก้ไขค่าได้อีก (ย่อมาจาก constant ค่าคงที่ยังไงล่ะ)
แล้วถ้าโค้ดมีการใช้ตัวแปรที่แชร์ข้ามกันหรือมีการทำ async แล้ว let ก็เก่งกว่า var เช่นกัน เช่น
for(var i = 1; i<10; i++){ setTimeout(function(){ console.log(i); }, 10); } //10 10 10 10 10 10 10 10 10 //เพราะว่าคำสั่ง setTimeout ถูกหน่วงเวลาเอาไว้ ลูป for ทำงานเสร็จก่อน ค่า i เลยจบที่ 10 for(let i = 1; i<10; i++){ setTimeout(function(){ console.log(i); }, 10); } //1 2 3 4 5 6 7 8 9 //ถ้าใช้ let ตัวแปร i จะใช้ค่าของรอบนั้นจริงๆ
Arrow Function
เป็น short-hand ของการเขียน function อีกรูปแบบหนึ่ง โดยใช้ => ตามชื่อ ไม่ได้มีอะไรแปลกใหม่ แค่ทำให้เขียนสั้นลงเฉยๆ คล้ายๆ Java8 (แต่ภาษานั้นจะใช้ -> แทน)
//จากเดิมที่เราสามาระสร้าง function ได้แบบนี้ plus = function(x,y){ return x + y; } //เขียนย่อได้เป็นแบบนี้ plus = (x,y) => x + y //ทั้งสองแบบสามารถเรียกใช้ได้เหมือนกัน plus(1, 2) //output: 3 //หรือจะใช้กับ callback function ก็ได้ //เช่นใน express.js app.get('/', function(req, res) { res.send('Hello World'); }); //ก็เขียนย่อได้เป็น app.get('/', (req, res) => { res.send('Hello World'); });
สรุปการเขียน arrow function
- x => x + 1 คือ function(x){ return x + 1; } (ไม่ต้องเติม return)
- (x,y) => x + y ในกรณีที่มี parameter เกิน 1 ตัวจะต้องใส่ ( ) ครอบให้ด้วย
- (x,y) => { x++; return x + y;} ถ้าคำสั่งใน function มีมากกว่า 1 คำสั่งจะต้องใส่ { } ครอบให้ด้วย และคราวนี้ต้องเติม return เอง
สำหรับ function ที่ประกาศภายใน object (หรือ method นั่นแหละ) ก็สามารถใช้ท่านี้ได้ด้วย
//แบบเดิม obj = { fn: function(x){ return x + 1; } } //ใช้ Arrow Function obj = { fn: (x) => x + 1 } //หรือย่อลงไปอีกแบบนี้ก็ได้ obj = { fn (x) { return x + 1; } }
Destructing
เป็นฟีเจอร์สำหรับแกะ object ออกมาเป็นตัวแปร คล้ายกับคำสั่ง list ในภาษา php
student = { id: 12, name: 'Annie', major: { course: 101, name: 'Database' } } //ถ้าเขียนแบบเดิม การจะเอาค่าออกมาจาก object ก็ต้อง var id = student.id; var name = student.name; var major_course = student.major.course; var major_name = student.major.name; //ถ้าเป็น ES6 เราจะใช้แบบนี้ได้ let {id, name, major: {course, name}} = student;
Parameter
ใน ES6 มีการเพิ่มลูกเล่นกับ parameter เข้ามาหลายอย่าง ซึ่งทำให้เราเขียนโค้ดได้ง่ายขึ้น
default value
เราสามารถกำหนดค่าเริ่มต้นให้ parameter ได้ถ้าคนเรียกใช้ไม่ส่งมา เช่นเดียวกับภาษา php
//เขียนแบบเดิม function dummy( x ){ //เช็กว่าถ้าไม่ได้ส่งค่า x มาให้มีค่าเป็น 1 นะ if(typeof x === 'undefined'){ x = 1; } //หรือย่อเป็นแบบนี้ก็ได้ x = x || 1; } //แบบ default value function dummy( x = 1 ){ }
named parameter
เป็นฟีเจอร์ต่อเนื่อง โดยการเอาฟีเจอร์ Destructing มาผสมด้วย
//แบบเดิม function fn( options ){ var a = options.a || 1; var b = options.b || 2; } //รับ object เข้ามาด้วย Destructing function fn( {a=1, b=2} ){ } //เวลาเรียกใช้ก็ส่ง object เข้าไปเลย fn({a: 100, b: 200}); //แต่ถึงจะเป็นอย่างนั้น ถ้าไม่ส่งค่าอะไรเข้าไปเลย ก็พังอยู่ดี fn(); //ดังนั้นถ้าจะให้ชัวร์ก็กำหนด Default Value เพิ่มด้วย ดังนี้ function fn( {a=1, b=2} = {} ){ }
rest parameter
คล้ายๆ กับ argsvar ในภาษา Java คือรับตัวแปรหลายๆ ตัวเข้ามา แล้วมองมันเป็น array คำสั่งที่จะคือ ...
ใน JavaScript แบบเดิมน่ะมันมี arguments ให้ใช้อยู่แล้ว แต่ถ้าใช้ในรูปแบบ rest การแยกตัวแปรออกเป็นชุดๆ ก็จะสะดวกขึ้น
function sum(init, ...other){ return other.reduce( (x,y) => x + y, init); } sum(1, 2, 3) //6 //โดยค่าที่ส่งเข้าไป init = 1, other = [2,3]
for of
เป็นการวนลูปแบบใหม่ สำหรับ array โดยเป็นการวนลูปเหมือนกับ foreach ใน Java
เพราะจากเดิมเราต้องใช้ for in ในการวนลูป แต่สิ่งที่ได้ออกมาไม่ใช่ value แต่เป็น key
data = [10, 20, 30, 40]; //แบบ for in for(i in data){ console.log(data[i]); } //10 20 30 40 //แบบ for of for(ech of data){ console.log(ech); } //10 20 30 40
ข้อควรระวังนิดหน่อยสำหรับการใช้ for of คือมันเป็นการวนลูปแบบ iterator ตามลำดับใน array ดังนั้นมันจะใช้กับ object ไม่ได้ (แต่ for in ทำได้)
class
ใน JavaScript ES5 นั้นมี object และ Build-in class ให้ใช้แล้ว แต่ดันไม่สามารถสร้างเองได้ ต้องไปทำผ่าน prototype ของ function ทำให้มันยังไม่เป็นภาษา OOP แบบเต็มตัว
มาถึง ES6 เราสามารถสร้าง class กันเองได้เลย แถมมีทั้ง constructor และการ extends ให้ใช้แบบภาษาระดับสูงอื่นๆ แล้วด้วย
//สร้างกันผ่าน function prototype แบบเดิมๆ function User(name) { this.name = name; } User.prototype.display = function() { console.log('user is ' + this.name); } //สร้างคลาสกันตรงๆ ได้เลยใน ES6 class User { constructor(name) { this.name = name; } display() { console.log('user is ' + this.name); } } let u = new User('user1'); u.display(); class Member extends User{ display() { console.log('member is ' + this.name); } } let m = new Member('member1'); m.display();
และแน่นอนว่า parameter ของ class และ method ก็ใช้ฟีเจอร์ต่างๆ ของ function parameter ได้ด้วยเช่นกัน
String
สตริงหรือสายอักขระก็เป็นอีกอย่างที่มีฟีเจอร์ใหม่เพิ่มเข้ามา โดยแยกเป็น 2 เรื่องคือ
Interpolation String
คือการแทรกตัวแปรลงไปใน String-Template ได้เลย เหมือนกับ ภาษา Ruby หรือ Swift
แต่การจะใช้ฟีเจอร์นี้ได้จะต้องใช้ quote แบบ ` ` เท่านั้น (ใครใช้ปุ่มนั้นเป็นปุ่มเปลี่ยนภาษา ก็กดยากหน่อยละนะ)
var name = 'Annie'; console.log(`my name is ${name}.`);
Multi-line
โดยปกติแล้ว JavaScript อ่านคำสั่งทีละบรรทัด เวลาเราเขียน string ยาวๆ เลยไม่สามารถขึ้นบรรทัดใหม่ได้ แต่ถ้าเราใช้ ` ` ก็จะอนุญาตให้ขึ้นบรรทัดใหม่ได้แล้ว
//แบบเดิม var str = 'this\n' + 'is\n' + 'a\n' + 'book\n' ; //แบบใช้ ` var str = `this is a book`;
module
โมดูลคือการยกโค้ดออกเป็นไฟล์ๆ แล้วโหลดโค้ดเข้ามาประกอบๆ กัน เช่น #include ในภาษาซี, import ในภาษาจาว่า
JavaScript ไม่เคยจัดการการโหลดไฟล์พวกนี้ได้ด้วยตัวเอง ต้องให้ภาษาอื่นช่วย หรือถ้าจะใช้แต่ JavaScript จริงๆ ก็อาจจะต้องใช้ไลบราลี่อย่าง CommonJS
แต่พอมาถึง ES6 มันก็ทำเองได้แล้วเช่นกัน
โดยส่วนไหนจะให้ไฟล์อื่นโหลดเอาไปใช้ได้ต้องเติมคำสั่ง export ไว้ข้างหน้า จะให้เอาออกไปกี่ตัว ก็ต้องใส่ให้ครบ
ส่วนการเรียกมาใช้จะใช้คำสั่ง import from
// helper.js export const MAX_USER = 25; export function getUserInfo() { return ...; } // main app //โหลดค่า MAX_USER มาใช้ import { MAX_USER } from './helper.js' //โหลดทั้ง MAX_USER และ getUserInfo มาใช้ import { MAX_USER, getUserInfo } from './helper.js' //โหลดมันทั้งหมดนั่นแหละ มีไรบ้างขอหมด แล้วเรียกพวกนี้รวมๆ ว่า helpers import * as helpers from './helper.js'
แต่ก็มีหลายๆ ครั้งที่เราต้องการจะโหลดของเพียงอย่างเดียวเข้ามาใช้ สามารถประกาศได้ด้วยการกำหนด default ให้มัน เพื่อบอกว่าเจ้าสิ่งนี้นะ เป็นตัวหลักของโมดูลนี้
// User.js export default class User { } // main app //สังเกตว่าครั้งนี้ไม่ต้องใส่ {} แล้ว import User from './User.js'
Spread operator
หรือเรียกง่ายๆ ว่าเครื่องหมายตัวแตก คำสั่งคือ ... เอาไว้ใช้กับ array หรือ object มีผลทำให้เครื่องหมายที่คลุมมันอยู่หายไป
ฟังแล้วน่าจะงง ไปดูตัวอย่างดีกว่า
var arr1 = [1,2]; var arr2 = [3,4]; //...arr1 จะมีค่าเป็น 1,2 //ดังนั้นถ้าเราอยากต่อ array เราก็สั่งให้เครื่องหมด [ ] หลุดออกไปได้ var arr3 = [ ...arr1, ...arr2 ]; //จะได้เป็น [ 1,2 , 3,4]
Promise
"โพรมิส" เป็นท่าสำหรับการเขียโปรแกรมแบบ asynchronous (ไม่ประสานเวลา) โดยส่วนมากแล้วเราจะพบมันในรูปแบบของ callback หรือฟังก์ชันที่เตรียมไว้สำหรับทำงานเมื่อเกิดเหตุการณ์บางอย่าง
ตัวอย่างเช่น
function doSomething(callback){ setTimeout(function(){ callback('hello world'); }, 1000); } doSomething(function(val){ console.log(val); });
มีฟังก์ชันอะไรบางอย่างที่นับถอยหลัง 1000 millisec แล้วจะทำการเรียก callback function ที่เราส่งเข้าไปให้ทำงาน
สำหรับ ES5 วิธีที่นิยมคือส่วนตัวแปร function ผ่านตัว parameter เข้าไป
แต่สำหรับ ES6 เรามี native promise ให้ใช้แล้ว สามารถเปลี่ยนได้ดังนี้
function doSomething(){ return new Promise( (resolve, reject) => { setTimeout(function(){ resolve('hello world'); }, 1000); }); } doSomething().then( (val) => { console.log(val) } );
โดยฟังก์ชันที่สามารถทำ asynchronous ได้จะต้อง return ตัวแปรของ Promise
การสร้างตัวแปร Promise จะใช้รูปแบบของการ new Promise() โดยที่มันจะรับ parameter เป็นฟังก์ชันที่โดยปกติจะใช้รูปแบบ (resolve, reject) => { ... }
และหน้าที่ของเราคือการสั่งรัน resolve เมื่อโค้ดเราทำงานเสร็จ หรือ reject เมื่อเกิด Error ขึ้น
สำหรับ resolve เราจะใช้ .then รับ แต่ถ้าเกิด reject ขึ้นเราจะใช้ .catch รับ
เช่น
let p = new Promise( (resolve, reject) => { if( success ){ resolve(1); } else { reject(2); } }); p.then((val)=>{ console.log(); }).catch((val)=>{ console.log(); });
---
ความจริง JavaScript เวอร์ชั่น ES6 ยังมีอีกหลายเรื่องที่ยังไม่ได้พูดถึง แต่นี่เลือกมาเฉพาะหัวข้อหลักๆ ที่น่ารู้นะ