JS Basic: EP1. พื้นฐานแบบเน้นๆ

เชื่อว่าหลายๆคนคงเขียน Javascripts มากันพอสมควรแล้ว แต่น่าจะมีหลายคนที่อาจจะมองข้ามพื้นฐานเล็กๆน้อยๆไป ซึ่งผมก็เป็นหนึ่งในนั้น แต่หลังจากที่ได้ไปศึกษาพื้นฐานในระดับลึกลงไปแล้ว ผมคิดว่าส่วนนี้สำคัญมากการในทำงาน ซึ่งคิดว่าน่าจะช่วยให้การเขียนโค้ดของเราดีขึ้น เนื่องจากเราจะได้เห็นภาพอะไรบางอย่างที่แตกต่างออกไป ซึ่งในบทความนี้เราก็จะมาเริ่มตั้งแต่การประกาศตัวแปร ไปจนถึงเรื่องใหม่ๆบน ES6 ด้วยครับ

ก่อนอื่นมารู้คำศัพท์ที่เกี่ยวข้องกันก่อน เผื่อไปเจอในที่อื่นๆจะได้ไม่งงกันนะครับ

คำศัพท์

Ecmascript เป็นมาตรฐานของภาษา Javascripts โดยมีการพัฒนาพื้นฐานมาเรื่อยๆจนถึงปัจจุบัน โดยในปัจจุบันที่รองรับในบราวเซอร์ทั่วไป คือ ES5 และเมื่อปีที่แล้วได้ทำการประกาศมาตรฐานรุ่นใหม่ขึ้นมาชื่อ ES6 หรือ ES2015 ซึ่งมีการปรับปรุงและเพิ่มเติมฟีเจอร์หลายๆอย่างซึ่งช่วยให้เราสามารถเขียน Javascripts ได้ง่ายขึ้น แล้วก็ยังมีที่มาตรฐานในยุคถัดๆที่ยังร่างกันอยู่ เช่น ES7 ES8

ตัวอย่างบราวเซอร์ที่รองรับการประกาศตัวแปรแบบ let บน es6 จากเว็บ caniuse.com

Let ES6 Browser Support

Browser และ Node.js เวอร์ชันใหม่ได้เริ่มพัฒนาให้รองรับ ES6 กันแล้ว แต่เราไม่สามารถใช้กับบราวเซอร์เก่าๆที่อาจจะยังใช้กันส่วนมากได้ แต่ก็มีวิธีแก้ครับ

วิธีการแรก คือ การแปลงโค้ดที่เราเขียนในรูปแบบของ ES6 ให้เป็นโค้ดในรูปแบบของ ES5 โดยใช้สิ่งที่เรียกว่า Transpiler ซึ่งมีอยู่หลายตัวด้วยกัน เช่น babel เป็นต้น

วิธีการที่ 2 คือการเขียนฟังก์ชันขึ้นมาโดยใช้โค้ดที่ ES5 รองรับ แต่สามารถทำงานแทนฟังก์ชันที่ build-in มากับ ES6 ได้ เช่น

Number.isInteger(0);

ใช้สำหรับตรวจสอบว่าเป็นค่า int หรือไม่ หากใช้ในบราวเซอร์ที่ไม่รองรับ ES6 ก็สามารถเพิ่ม Polyfill ให้รองรับได้

Number.isInteger = Number.isInteger || function(value) {
  return typeof value === 'number' && 
    isFinite(value) && 
    Math.floor(value) === value;
};

Variable

การประกาศตัวแปรใน Javascripts นั้น เป็นการประกาศแบบไม่ระบุ type ของตัวแปร โดยใช้คำสั่งง่ายๆคือ var

var a = 69;
var b = "hello world";

หลายๆคนคงคุ้นชินอยู่แล้ว แต่จริงๆ แล้วตัวแปรบน Javascripts นั้นมี type ของมันเอง ซึ่งแบ่งเป็น 2 ประเภทหลัก คือ

Primitive Types

เป็นตัวแปรประเภทค่าเดียว ได้แก่ String Number Boolean null undefined โดยสามารถประกาศตัวแปรได้ง่ายๆ

var a = "hello";
var b = 2;
var c = true;
var d = null;
var f = undefined;

ซึ่งจะเป็นคุณสมบัติคร่าวๆดังนี้

Pass by Value

การเก็บข้อมูลของตัวแปรจะเก็บเป็น value และหากทำการสร้างตัวแปรใหม่ จะ copy value เดิมแล้วชี้ไปที่ memory ที่ใหม่ เช่น

var a = 10;
var b = a;
b += 5;

a 	// 10
b 	// 15

Immutable

หรือไม่สามารถเปลี่ยนแปลงค่าได้ ทำแบบนี้ไม่ได้นะ ถึงจะไม่ error อะไรก็เถอะ

var a = "test"
a[1] = "4"
a 	// "test"
a = "test2"
a 	// "test2"

การเปลี่ยนแปลงค่าสามารถทำได้ด้วยการเปลี่ยนค่าผ่านการชี้ references ไปยัง memory ที่ใหม่เท่านั้น โดยผ่านเครื่องหมาย = นั่นเอง

Object Types

หรือตัวแปรที่มีค่าซับซ้อน เช่น Object Array สามารถประกาศตัวแปรได้ดังนี้

Object => {} / Array => []

var a = [];		// ประกาศอาเรย์เปล่า
var b = [1,2,3];	// อาเรย์แบบมีค่าตั้งต้น

var c = {};		// ออปเจ็คเปล่า
var d = {		// ออปเจ็คแบบมีค่าตั้งต้น
	x: 2
};

ตัวแปรประเภทนี้มีคุณสมบัติดังนี้

Shared Value

ตัวแปรใน Javascripts นั้นจะเป็น Pass by value ทั้งหมด แต่ในค่าจำพวก Object นั้นตัว Value จะเก็บเป็น References ที่ชี้ไปยัง memory ที่เก็บ Object อีกที ดังนั้นการประกาศตัวแปรใหม่ก็จะเป็นคนละตำแหน่งใน memory เช่นเดียวกับ Primitive Types จากนั้นก็จะได้ค่า References ที่ชี้ไปยังที่เก็บ Object อีกที แต่ตัว References นั้นจะเป็นตัวเดียวกันนั่นเอง ค่าของ Object จึงเป็นค่าเดียวกัน

อาจจะมองเป็นภาพง่ายๆแบบนี้

Variable Point to Memory
a 1
b 2
Memory Index Value
1 0001
2 0001

ค่า Value ชี้มาที่ Object ตัวเดียวกัน

Object Value
0001 { c: 2 }

ผลลัพธ์จึงเป็นการที่ค่านั้นสามารถเปลี่ยนแปลงได้หากทำการเปลี่ยนค่าในตัวแปรอื่นที่ใช้ค่าร่วมกัน

var a = [0, 1, 2];
var b = a;
b.push(3)

a // [0, 1, 2, 3]
b // [0, 1, 2, 3]

Object

Object บน Javascript นั้นอยู่ในรูปแบบ [key]:[value] โดยสามารถเรียกใช้งานได้ทั้งแบบ key a['key'] และแบบ property a.key โดยข้อดีของแบบ key คือเราสามารถใส่ String แปลกๆได้

var a = {
	['b']: 2,
	c: 3
	'd': 4
}

a['c'] 	// 3
a.b 	// 2

a['test-2'] = 4;
a.f = 5

และรูปแบบ key ยังสามารถใช้ operator ได้ด้วย ซึ่งจะช่วยให้ง่ายหากเราต้องใช้ key ในรูปแบบของ dynamic key

var prefix = "test"
var a = {
	[prefix + "1"]: "a"
}

Array

อาเรย์หรือการเก็บข้อมูลแบบชุด ไม่จำกัดว่าค่านั้นจะเป็นประเภทอะไร โดยเก็บค่าเป็น [index]:[value] โดย index มีค่าเป็น positive integer

var a = [1,2,3,"4",true];

a[0] 	// 1
a[3] 	// "4"
a[4] 	// true

แต่จริงแล้วก็สามารถใช้อาเรย์ในรูปแบบของ [key]:[value] เหมือนกับ object ได้ เพราะจริงๆแล้วอาเรย์นั้นมีพื้นฐานคล้ายๆกับ object โดยเปรียบเสมือน objecy ที่มี key เป็น index นั้นเอง

var a = ["a", "b", "c"];
var b = { 0: "a", 1: "b", 2: "c" };

a[0] // "a"
b[0] // "a"

a["a"] = 3

a // [0: 0, 1: 1, 2: 2, a: 3, length: 3]

แต่เราควรหลีกเลี่ยงหรือห้ามใช้งานแบบนี้ไปเลยนะครับ เนื่องจาก array ได้ถูก optimize มาสำหรับใช้เก็บค่าแบบ index อยู่แล้ว จะทำให้เกิดการทำงานที่ผิดพลาดขึ้นได้

ตัวอย่าง Build-In Method

var a = [1,2,3,4,5];

a.length 		// 5
a.length = 10 		// [1,2,3,4,5,,,,,]
a.push(6) 		// [1,2,3,4,5,6]
a.pop() 		// [1,2,3,4]
a.shift() 		// [2,3,4,5]

Other

และยังมี Type อื่นๆ เช่น Function Date Error RegExp

Hoist

การประกาศและ assign ค่าให้กับตัวแปรใน Javascript นั้น จะมีด้วยกัน 2 ขั้นตอน คือการ Declare หรือการประกาศตัวแปร และ Assign หรือการชี้ตัวแปรไปยังค่านั้น เช่น

var a = 10;

แต่ในการรัน แยกออกเป็น 2 step โดยยกการ declare ขึ้นไปบนสุดของ scope นั้นๆ ( อ่านเรื่อง scope ได้ในบล็อค EP.2 นะครับ )

var a;		// Declare
a = 10;		// Assign

เราจึงเห็นได้ว่า บางทีเราสามารถเรียกใช้ตัวแปรนั้นได้ก่อนการประกาศตัวแปร

a 	// undefined
var a = 10;
a 	// 10

เพราะในการรันจริงๆ var a; จะขึ้นมาอยู่ด้านบนสุดของ scope นั้นนั่นเอง

var a;
a 	// undefined
a = 10;
a 	// 10

Type Casting

การเปลี่ยน type ของค่าใน Javascripts นั้นมีอยู่ 2 รูปแบบ

Explicit Cast

หรือการเปลี่ยนแปลงค่าจากคำสั่งโปรแกรม เช่น แปลงค่าจาก String เป็น Number โดยใช้คำสั่ง parseInt

var a = parseInt("2");		// 2 (Number)
a.toString();			// "2" (String)

Implicit Cast

เป็นการเปลี่ยนแปลงค่าที่เกิดจากตัว JS Engine ทำการเปลี่ยนให้เพื่อให้โปรแกรมสามารถทำงานต่อได้ ซึ่งจะเกิดขึ้นภายในการรันโปรแกรม เช่น การใช้ operator กับค่าที่เป็นคนละ Type กัน

var a = 2 + "2"		// Implicit Cast 2 >> "2"
var b = true + 1	// Implicit Cast true >> 1

a 	// "22"
b 	// 2

Mini Quiz

true + 2 + (2 + 2 + "2" + ( true + null + 1 ) + false) = ?

ได้เท่าไหร่กันนะ …… คำตอบคือ “3422false”


Object Wrapper

ในการประกาศตัวแปรใน Javascripts นั้น เราอาจจะเห็นว่าเราสามารถ assign ค่าที่เป็น type อะไรก็ได้เข้าไปในตัวแปรนั้นได้เลย แต่จริงๆแล้วยังมีวิธีการประกาศตัวแปรอีกแบบ คือ การสร้าง object ของ type นั้นขึ้นมาใหม่ โดยใช้คำสั่ง new object เช่น

var a = new String("Hello World");
var b = new Number(2);

a 	// String { .... }
b 	// Number { .... }

ซึ่งจะเป็นการประกาศตัวแปรแบบระบุ type โดยจะมี object ของ type นั้นครอบอยู่ด้วย โดย Object พวกนี้ก็จะมี Built-in Method ให้เราเรียกใช้ตามค่านั้นๆ เช่น .toString() .toUppercase() เป็นต้น

ย้อนกลับมาดูการประกาศตัวแปรแบบไม่ระบุ type

var a = "hello world";

a 				// "hello world"
a.toUpperCase() 		// "HELLO WORLD"

แต่เดี๋ยวก่อน ทั้งที่เราไม่ได้ประกาศตัวแปรเป็น object type นั้น แล้วทำไมเราถึงยังสามารถใช้ build-in method ได้อยู่ละ

นั่นก็เพราะว่าจริงๆแล้วตัวแปรที่ประกาศใน Javascripts นั้น จะมีส่วนของ Object Wrapper หรือการครอบ Object Type ของตัวแปรนั้นๆให้ หากค่านั้นเป็น String ก็จะมี String object ครอบอยู่ และเราก็สามารถเรียกใช้งาน build-in method ได้นั้นเอง

Expression & Operators

Comparison

Loose Equality

ใช้เครื่องหมาย == != > >= < <= ในการเปรียบเทียบค่าว่าสองค่าทั้งสองฝั่ง

2 == 2			// true
3 != 2			// true
8 == 2			// false
[1,2] == '1,2'		// true
true == 1		// true
false == "1" 		// false
2 < "3"			// true
2 > true		// true

และหาก Type ทั้งสองฝั่งไม่เหมือนกัน ตัว JS Engine จะทำการ cast ค่าให้เป็น common type ซึ่งอาจเกิดการเปลี่ยนแปลงค่าได้สองฝั่งของการเปรียบเทียบ และการเปรียบเทียบที่มี Arithmetic มาเกี่ยวข้อง ก็จะ cast เป็น Number เพื่อทำการเปรียบเทียบ

เราสามารถดูการเปรียบเทียบค่าแบบ Loose Equality ว่าเปรียบเทียบค่าไหนที่จะให้ผลลัพธ์เท่ากัน หรือไม่เท่ากันบ้าง แบบเข้าใจง่ายๆ ได้ที่ Javascripts Equality Table

JavaScript Equality Table

 

Strict Equality

ใช้เครื่องหมาย === และ !== ในการเปรียบเทียบ ซึ่งในกรณีนี้ ตัว Javascripts Engine จะไม่ทำการ Cast Type แล้ว หากคนละ Type ก็จะไม่เท่ากันไปเลย

2 === "2"		// false
2 === 2			// true

จะเห็นได้ว่าเราควรใช้การเปรียบเทียบแบบ Strict Equality มากกว่า เพื่อป้องกันความผิดพลาดของโปรแกรมที่เกิดจากการแปลงค่าของตัว Javascripts Engine แต่ในบางกรณีการเปรียบเทียบแบบ Abstract ก็ช่วยให้เราเขียนโค้ดได้สวยงามกว่า ดังนั้นต้องเลือกใช้ให้ดีๆ

Arithmetic operators

หรือเครื่องหมายทางคณิตศาสตร์ + - * / % ++ -- และเครื่องหมาย + ยังสามารถใช้ร่วมกับ String ได้ด้วย เป็นการ concat

false + 1		// 1
true + "2"		// "true2"

เมื่อมีการใช้งาน Arithmetic Operator หากมี type อื่นๆที่ไม่ใช่ number ก็จะถูก cast ไป number ก่อน เช่น true >> 1 , false >> 0 , null >> 0 , undefined >> NaN แต่หากมีการใช้ + กับ String ก็จะถูก cast เป็น String ทันที เช่น

true + 1  		// 2
false + 3 		// 3
4 + undefined 		// NaN
"3" - 1 		// 2
"3" + 1 		// "31"

Logical operators

&& => AND , || => OR และ ! => Not

true && true		// true
true || false		// true
false && true		// false
!true  			// false
(1 == 2) && isNumber(a)
// เนื่องจาก 1 == 2 ให้ผลเป็น false แล้วต่อให้เมื่อเจอกับ && ต่อให้ข้างหลังจากนั้นได้ค่าเป็นอะไร ผลลัพธ์โดยรวมก็จะเป็น false อยู่ดี ดังนั้นฟังก์ชัน isNumber จะไม่ถูกรันขึ้นมา

(1 == 1) || isNumber(2)
// เช่นเดียวกับ ยังไงผลลัพธ์ก็เป็น true ตัว isNumber ก็ไม่รัน

ดังนั้นเราสามารถใช้ order ของการ compare logic ช่วยให้โค้ดของเราดูสั้นลงได้ เช่น object หากเป็น undefined หากทำการเรียกใช้ property ก็จะ error บางครั้งอาจจะใช้ if ซ้อน if เช่น

if(a !== undefined) {
	if(a.value === 3) {
		// a is 3
	}
}

แต่ด้วย Order ก็ช่วยให้เราสามารถเขียน expression รวมกันได้ โดยเรียง order ให้ถูกต้อง

if(a !== undefined && a.value === 3) {
	// a is 3
}

เมื่อ a = undefined ก็จะให้ค่า false กลับมาทันที จะไม่รัน a.value ให้เกิด error ใดๆขึ้นมา

และ Operators อื่นๆ สามารถดูได้ที่ MDN: Expressions and operators

Object Comparison

ในการเปรียบเทียบค่าของ Object หรือ Array นั้นจะไม่สามารถเปรียบเทียบกันได้ด้วยการใช้ Comparison Operator ทั่วไป เนื่องจากหากเราจำได้ในต้นๆของบทความ ตัวแปรที่เก็บค่า Object นั้น ไม่ได้เก็บตัว Object แต่เป็นการเก็บตัว Reference ซึ่งในการสร้าง Object แต่ละครั้งนั้นจะไม่เหมือนกัน การใช้เครื่องหมาย == === จึงเป็นการเปรียบเทียบค่า Reference เท่านั้น ไม่ได้เปรียบเทียบค่า Object

var a = { z: 1 }
var b = { z: 1 }

// เนื้อหาของออปเจ็คเดียวกันแต่เป็นค่าที่ละที่กัน
a == b 		// false

var c = a;	// ใช้ references ตัวเดียวกับ a
a == c 		// true

หากเราต้องการจะเปรียบเทียบค่า Object นั้นมีค่าเท่ากันหรือไม่ จึงจำเป็นต้องเข้าไปดูเนื้อหาของในของ Object และเปรียบเทียบทีละตัว เช่น มี key เท่ากันหรือไม่ ค่าของแต่ละ key เท่ากันหรือไม่

แต่เราไม่ต้องไปเขียนเองนะครับ แน่นอนว่ามี Library ที่มีคนทำไว้อยู่แล้วให้เลือกใช้กันเยอะอยู่แล้ว แถมยังผ่านการทดสอบและปรับปรุงประสิทธิภาพมาอย่างดีกว่าด้วย เช่น Underscore.js ก็มีฟังก์ชัน _.isEqual(a, b) ให้เรียกใช้งานได้เลย

Function

ฟังก์ชัน ใน Javascripts สามารถประกาศได้ง่ายๆโดยใช้คำสั่ง function โดยสามารถประกาศได้หลายแบบ

function add(x,y) {
	return x + y;
}

var a = add(10,20); // 30

หรือฟังก์ชันที่ไม่มีชี่อ เราสามารถใช้ตัวแปรชี้ไปที่ฟังก์ชันนั้น และสามารถนำไปเรียกใช้ได้ หรือใช้ในการส่งผ่านเข้าไปเป็นตัวแปรในฟังก์ชันอื่นๆ

var add = function(x,y) {
	return x + y;
}

var a = add(10,20); // 30
setTimeout(function() {			// <= Anonymous Function
	var a = 1;
}, 100)

หรือการประกาศฟังก์ชันที่รันทันที

(function echo() {
	console.log("Hello")
})();

// Hello

Strict Mode

การรัน Javascripts นั้นมีด้วยกัน 2 โหมดคือ ธรรมดา และ Strict Mode ซึ่ง ข้อแตกต่างระหว่างสองอย่างนี้คือ

การออกแบบ Javascripts นั้น ในยุคก่อนหน้านี้ไม่ได้ให้ความสำคัญกับ error อะไรมาก ดังนั้นในการรันการทำงานหลายๆอย่างนั้น อาจจะไม่มี error ออกมา ทั้งๆที่โดย common sense แล้วมันควรจะ error แต่พอรันด้วย strict mode แล้ว error หลายๆจะถูกปรับให้แสดงผลออกมา เพื่อช่วยให้เราเขียนโค้ดได้ง่ายและโปรแกรมรันได้ถูกต้องมากขึ้น ( ที่ไม่สามารถปรับให้โหมดธรรมดารันแล้วแสดงผล error ขึ้นมาได้ เพราะต้องการให้ compatible กับเวอร์ชันก่อนๆ ) ยกตัวอย่างเช่น

การไม่ประกาศ var ก่อนหน้าการประกาศตัวแปร หากเราในโหมดธรรมดาก็จะรันได้ปกติ (ทำไมมันไม่ error ฟร่ะ ยังไม่ได้ประกาศซักหน่อย)

a = 2
a 	// 2

หากรันบน strict mode ด้วยการคำสั่ง use strict ด้านบน ก็จะขึ้น error ด้วยประกาศฉะนี้แล

'use strict';

a = 2		// ReferenceError a not defined

ก็จะช่วยให้เราหลีกเลี่ยงความผิดพลาดที่อาจจะเกิดจากการโค้ดไม่ระวังได้ง่ายขึ้น

เนื่องจาก Engine หลายๆตัวได้ทำการ Optimize การรัน JS บน Strict Mode ไว้แล้ว ช่วยให้สามารถรันโค้ดบางอย่างได้เร็วกว่าโหมดธรรมดา

Strict Mode สามารถประกาศในส่วนบนสุดของ Scope เช่น

'use strict';			// Strict Mode ทั้งไฟล์
function foo() {
	var b = 0;
}
var a = 0;
function foo() {
	'use strict'; 		// Strict Mode เฉพาะในฟังก์ชัน foo
	var b = 0;
}
var a = 0;

ES6

การประกาศตัวแปรบน ES6 นั้น มีฟังก์ชันที่เพิ่มขึ้นมา โดยมีตัวเลือกให้สองแบบคือ let และ const

Let

let คือสิ่งที่มาแทน var นั่นเอง โดยการใช้ let แทน var จะช่วยให้แก้ปัญหาการเรียกใช้งานตัวแปรโดยที่ยังไม่ได้ประกาศค่า (TDZ) แต่เรียกใช้ได้เพราะ hoist นะครับ เช่น

a 	// undefined
var a = 10;

b 	// ReferenceError
let b = 10;

และมีคุณสมบัติพิเศษเรื่อง scope ซึ่งจะอธิบายในบทถัดไปครับ

Const

คือตัวแปรที่ไม่สามารถเปลี่ยนแปลงค่าได้ ซึ่งเมื่อทำการ assign ค่าให้แล้ว จะไม่สามารถ assign ค่าใหม่ได้อีก ซึ่งหากเราพยายามทำการเปลี่ยนค่า จะเกิด Error แสดงทันที

const a = 10;
a = 2		// TypeError

ข้อควรระวัง หากใช้ const กับ Object และ Array เรายังสามารถเปลี่ยนแปลงค่าได้ แต่ไม่สามารถชี้ไปที่ค่าใหม่ได้ เนื่องจากค่า references ของตัวแปรไม่ได้เปลี่ยน แต่เป็นการเปลี่ยนค่าที่ object นั่นเอง

const b = [1,2,3];
b.push(4);
b 		// [1,2,3,4]
b = [4,5,6];  	// TypeError

References

Author

Anonymous

Web Developer

Nextzy Technology Co,. LTD.