ยิ่งโปรแกรมมีขนาดใหญ่และมีส่วนประกอบต่างๆมากขึ้น จึงควรแยกส่วนต่างๆไว้ในคนละไฟล์เพื่อไม่ให้ไฟล์หนึ่งใหญ่เกินไป อีกทั้งการแบ่งเป็นส่วนอย่างเป็นระบบจะทำให้เข้าใจได้ง่ายขึ้น
ใน ES6 มีวิธีที่จะนำโค้ดจากไฟล์อื่นเข้ามาใช้ในอีกไฟล์หนึ่งได้ นั่นคือวิธีการทำเป็น
มอดูล (module) ซึ่งจะเขียนถึงในบทนี้
การเตรียมการ
ในบทนี้จะต่างจากบทอื่นที่ผ่านมาตรงที่ไม่สามารถรันผ่านไฟล์ในเครื่องได้
ใน
บทที่ ๒ ได้แนะนำวิธีการรันโค้ดในเบราว์เซอร์แบบง่ายๆในเบื้องต้นเพื่อใช้ในการฝึก
แต่วิธีการง่ายๆนี้จะมีปัญหาเวลาที่จะใช้งานมอดูล เนื่องจากปัญหาในเรื่องของ
CORS (Cross-Origin Resource Sharing) ซึ่งเป็นเรื่องของความปลอดภัยในการใช้เบราว์เซอร์
ปกติเบราว์เซอร์จะมีการป้องกันไว้ไม่ให้ใช้โค้ดที่มาจากแหล่งที่ไม่ได้ขึ้นต้นด้วย http รวมถึงไฟล์ที่อยู่ในเครื่องเองก็ไม่ได้
วิธีการที่แนะนำไปนั้นคือสร้างไฟล์ .html และ .js ในเครื่องโดยตรง จึงไม่สามารถใช้งานมอดูลได้ แม้ว่าโค้ดทั้งหมดจะอยู่ในเครื่องเดียวกันก็ตาม
เพื่อที่จะใช้งานมอดูลได้ จะต้องเอาไฟล์ไปใส่ในเว็บแล้วเปิดเว็บขึ้นมา หรือไม่ก็เปิดเซิร์ฟเวอร์ขึ้นมาในเครื่อง แล้วเปิดแบบ local (เช่นผ่าน
http://0.0.0.0:3000
) ก็ได้
รายละเอียดในส่วนตรงนี้จะขอละไว้ เนื่องจากเป็นเรื่องของความรู้ทางเว็บไซต์ ไม่ใช่เนื้อหาของจาวาสคริปต์โดยตรง
หากใครที่ไล่อ่านมาเรื่อยๆโดยใช้รันไฟล์ในเครื่องผ่านเบราว์เซอร์โดยตรง และไม่สะดวกที่จะเปลี่ยนไปวางในเว็บ ก็อาจข้ามบทนี้ไป แต่จะไม่กระทบต่อเนื้อหาในบทอื่นๆ
ในที่นี้เพื่อการฝึกใช้มอดูลในที่นี้จะต้องใช้ ๓ ไฟล์ ขอตั้งชื่อตามนี้ (จะเปลี่ยนเป็นชื่ออะไรก็ได้ แต่ต้องเปลี่ยนโค้ดในส่วนที่มีชื่อไฟล์ให้ตรงกัน)
mocha.html
ไฟล์ .html หน้าเว็บ
maccha.js
ไฟล์ .js ที่เขียนโค้ดจาวาสคริปต์ตัวหลักที่จะถูกเรียกไปลงในรันในหน้าเว็บ
momo.js
ไฟล์ .js ที่ใส่โค้ดมอดูลที่จะถูกเรียกไปใช้ใน .js อีกไฟล์
ทั้งหมด ๓ ไฟล์นี้วางไว้ที่เดียวกันทั้งหมด
momo.js
export function sawatdi(){
alert("สวัสดีชาวโลก! 你好,世界");
}
maccha.js
import {sawatdi} from './momo.js';
sawatdi();
mocha.html
<meta charset="UTF-8" />
<script src="./maccha.js" type="module"></script>
จากนั้นเปิดหน้า
mocha.html
ขึ้นมาก็จะเด้งขึ้นมาแบบนี้
ชื่อไฟล์จำเป็นต้องมี ./ นำหน้า ถ้าอยู่ในโฟลเดอร์เดียวกัน จะละไม่ได้
ในที่นี้ฟังก์ชัน sawatdi ซึ่งถูกเขียนในไฟล์
momo.js
จะถูกนำไปใช้ได้โดยคำสั่ง
export
ส่วนในไฟล์
maccha.js
ให้ใช้คำสั่ง
import
เพื่อดึงฟังก์ชัน sawatdi มาใช้
สุดท้าย
mocha.html
เขียนเรียกโค้ดของ
maccha.js
ผ่านแท็ก script โดยในที่นี้ในแท็กจำเป็นต้องใส่
type="module"
ด้วย ไม่เช่นนั้นจะเกิดข้อผิดพลาด
ข้อความอธิบายข้อผิดพลาดที่ขึ้นมาอาจต่างกันตามเบราว์เซอร์ที่ใช้ แต่ในความโดยรวมก็คือไม่สามารถใช้คำสั่ง
import
ได้ ถ้าไม่ทำให้ไฟล์หลัก (ไฟล์ที่ถูกเรียกจาก html โดยตรง ในที่นี้คือ maccha.js) เป็นมอดูลด้วย ซึ่งการใส่
type="module"
ก็คือเพื่อกำหนดชนิดให้โค้ดในไฟล์นั้นเป็นมอดูล
หากว่าใส่
type="module"
แล้วเจอข้อผิดพลาด โดยชี้ปัญหาที่เกี่ยวกับ CORS แบบนั้นอาจหมายความว่ากำลังรันผ่านไฟล์ในเครื่อง ไม่ได้เอาลงเว็บดังที่กล่าวข้างต้น ทำให้โดนเบราว์เซอร์ป้องกัน
การ export และ import มอดูล
ดังตัวอย่างสั้นๆที่ได้ยกไป ไฟล์ที่เก็บมอดูลต้องใช้คำสั่ง
export
เพื่อจะนำข้อมูลจากในนั้นไปใช้ในไฟล์อื่น ในขณะที่ไฟล์ที่จะเรียกใช้มอดูลต้องใช้คำสั่ง
export
การ export มอดูลมี ๒ แบบ คือแบบ
export
เฉยๆธรรมดากับ
export default
ในที่นี้จะเริ่มจาก
export
ธรรมดาก่อน
ในส่วนของไฟล์มอดูลจะใช้คำสั่ง
export
นำหน้าฟังก์ชันหรือตัวแปรต่างๆที่ต้องการเอาไปใช้
// ประกาศและ export ค่าคงที่
export const planck = 6.62607004e-34;
// ประกาศและ export ฟังก์ชัน
export function thakthai(){
alert("คนนิจิวะ");
};
// ประกาศฟังก์ชันและค่าก่อนแล้วค่อย export
let matane = "มาตะเน้";
function bokla(){
alert(matane+"~~");
};
export {bokla, matane};
จากตัวอย่างจะเห็นว่าประกาศค่าหรือฟังก์ชันแล้ว export เลย หรือว่าสร้างตัวแปรแล้วค่อยเอามา export อีกทีก็ได้ ในกรณีนี้แค่ใส่ชื่อตัวแปรทั้งหมดที่ต้องการไว้ใน { }
จากนั้นฝั่งไฟล์ที่จะเรียกใช้มอดูลก็สั่ง
import {ชื่อตัวแปร, ...} from ชื่อไฟล์มอดูล
import {planck, matane, thakthai, bokla} from './momo.js';
alert(planck); // ได้ 6.62607004e-34
thakthai(); // ได้ คนนิจิวะ
alert(matane); // ได้ มาตะเน้
bokla(); // ได้ มาตะเน้~~
หรือถ้าต้องการจะเรียกทั้งหมดก็เขียนในรูป
import * as ชื่อที่ต้องการแทนมอดูล from ชื่อไฟล์มอดูล
แล้วเวลาจะใช้ก็ต้องใส่ชื่อที่แทนมอดูลนั้นลงไปนำหน้าแล้วค่อยตามด้วยชื่อตัวแปรหลังจุด
import * as momi from './momo.js';
alert(momi.planck);
momi.thakthai();
alert(momi.matane);
momi.bokla();
นอกจากนี้จะนำเข้ามาโดยตั้งชื่อใหม่ให้ก็ได้ โดยใส่
as
ให้ตัวที่ต้องการเปลี่ยนแล้วตามด้วยชื่อที่ต้องการ ส่วนตัวไหนไม่ได้ต้องการเปลี่ยนชื่อก็ไม่ต้องใส่
as
ก็ได้
import {planck as h, thakthai as tt, matane as mtn, bokla} from './momo.js';
alert(h); // ได้ 6.62607004
tt(); // ได้ คนนิจิวะ
alert(mtn); // ได้ มาตะเน้~
bokla(); // ได้ มาตะเน้~
การใช้ export default
นอกจากวิธีการ
export
ธรรมดาแล้ว อีกวิธีการคือใช้
export default
คือการรวมข้อมูลทั้งหมดอยู่ในออบเจ็กต์เดียวแล้ว export ออกไป
ในไฟล์มอดูล ให้ใช้คำสั่ง
export default
ตามด้วยออบเจ็กต์ที่ต้องการเอาไปใช้
let c = 299792458;
const PI = 3.14159;
export default { c, PI };
ส่วนในไฟล์ที่เรียกใช้มอดูลก็ให้ import แบบนี้
import momi from "./momo.js"
alert(momi) // ได้ [object Object]
alert(momi.c) // ได้ 299792458
alert(momi.PI) // ได้ 3.14159
ในที่นี้ momi คือชื่อออบเจ็กต์ที่จะมาใช้แทนออบเจ็กต์มอดูลนั้น จะตั้งเป็นอะไรก็ได้ ให้ระวังว่าต่างจากการใช้กับมอดูลที่
export
ธรรมดาตรงที่ไม่ต้องใช้ {} คร่อม
export default
นั้นสามารถทำได้แค่ตัวเดียว แต่ว่าโดยทั่วไปสิ่งที่
export default
จะเป็นออบเจ็กต์ที่ใส่ค่าอะไรต่างๆอยู่ข้างใน ดังนั้นผลที่ได้ก็ไม่ต่างจากการ export ตัวแปรทีละตัว แต่ต้องระวังว่าการ import จะต่างกัน
ที่จริงสิ่งที่ใช้
export default
จะไม่ใช่ออบเจ็กต์แต่เป็นอย่างอื่นก็ได้ แม้จะดูไม่ค่อยมีความหมายที่จะทำอย่างนั้นนัก
เช่น เขียน
export default
ค่าตัวเลขแบบนี้ในมอดูล
export default 1000;
แล้วในไฟล์ที่เรียกใช้ก็เอาค่ามาใช้แบบนี้
import momi from "./momo.js"
alert(momi) // ได้ 1000
การใช้ export ทั้ง ๒ แบบไปพร้อมกัน
ทั้ง
export
ธรรมดาและ
export default
สามารถใช้ได้ในเวลาเดียวกัน เช่นทำแบบนี้
const R = 8.31446262;
let u = 1.66053907e-27;
export {R, u};
export default {
e: 1.60217663e-19,
G: 6.6743e-11
};
แล้วในส่วนของไฟล์เรียกใช้ก็เขียนแบบนี้
import momi, { R, u } from "./momo.js"
alert(momi.e) // ได้ 1.60217663e-19
alert(momi.G) // ได้ 6.6743e-11
alert(R) // ได้ 8.31446262
alert(u) // ได้ 1.66053907e-27
จะเห็นว่าต้องแยก
import
เป็น ๒ ส่วน ส่วน momi นี่คือแทนส่วนที่
export default
ส่วน { R, u } แทนส่วนของ R และ u ที่
export
แบบธรรมดามา
ส่วนที่ export ธรรมดาจะเปลี่ยนมาใช้ * as แบบนี้ก็ได้เช่นกัน
import momi, * as momo from "./momo.js"
alert(momi.e)
alert(momi.G)
alert(momo.R)
alert(momo.u)
การใช้ซิมโบลเพื่อสร้างพรอเพอร์ตีส่วนตัวในมอดูล
ใน
บทที่ ๒๕ ได้กล่าวถึงเรื่อง
พรอเพอร์ตีส่วนตัว (private property) ซึ่งหมายถึงพรอเพอร์ตีที่ต้องเข้าผ่านเมธอดของออบเจ็กต์เท่านั้น ไม่สามารถเข้าถึงได้โดยตรง
การใช้โคลเฌอร์เป็นวิธีหนึ่งที่ใช้สร้างพรอเพอร์ตีส่วนตัวขึ้นได้ดังที่แนะนำไปในบทที่ ๒๕ อย่างไรก็ตาม ในกรณีที่ทำเป็นมอดูล มีอีกวิธีหนึ่งที่สามารถสร้างพรอเพอร์ตีส่วนตัว นั่นคือการใช้ข้อมูลชนิดซิมโบลช่วย
โดยอาศัยคุณสมบัติของซิมโบลซึ่งได้เขียนถึงไปใน
บทที่ ๓๕ สามารถใช้สร้างพรอเพอร์ตีส่วนตัวได้เช่นกัน
ตัวอย่างการใช้
let NAK = Symbol();
let SUNG = Symbol();
class Pokemon {
constructor(chue,nak, sung){
this.chue = chue;
this[NAK] = nak;
this[SUNG] = sung;
}
bokKhomun(){
alert(this.chue +" หนัก "+this[NAK] + " กก. สูง "+this[SUNG], "ม.");
}
}
export {Pokemon}
จะเห็นว่า NAK และ SUNG เป็นซิมโบลที่ประกาศในไฟล์มอดูล และถูกใช้เป็นชื่อพรอเพอร์ตีให้ออบเจ็กต์ที่สร้างขึ้นในคอนสตรักเตอร์
แต่ว่าที่ export ไปมีแค่ตัวคลาส Pokemon เท่านั้น NAK และ SUNG ไม่ได้ถูก export ไปด้วย ดังนั้นจึงไม่มีทางที่จะเอาตัวแปรซิมโบล NAK และ SUNG เพื่อมาเรียกใช้พรอเพอร์ตีนี้โดยตรงได้
ดังนั้นพรอเพอร์ตีที่ใช้ซิมโบลเป็นชื่อจะกลายเป็นพรอเพอร์ตีส่วนตัวไป ได้แค่เรียกใช้ผ่านเมธอด bokKhomun เท่านั้น
ในไฟล์ที่เรียกใช้มอดูลก็เขียนแบบนี้เพื่อนำคลาส Pokemon มาใช้ แล้วก็ใช้เมธอดเพื่อดูข้อมูลได้ รวมทั้งพรอเพอร์ตี .chue ซึ่งไม่ได้ใช้ซิมโบลก็สามารถดูค่าได้โดยตรง
import {Pokemon} from "./momo.js";
let kamonegi = new Pokemon("คาโมเนงิ", 15, 0.8);
kamonegi.bokKhomun(); // ได้ คาโมเนงิ หนัก 15 กก. สูง 0.8
alert(kamonegi.chue); // ได้ คาโมเนงิ
เนื่องจากจาวาสคริปต์ไม่มีวิธีสร้างพรอเพอร์ตีส่วนตัวโดยตรง จึงต้องอาศัยวิธีโดยอ้อมแทนเช่นนี้
การใช้ซิมโบลเพื่อสร้างเมธอดส่วนตัวในมอดูล
ทำนองเดียวกันกับพรอเพอร์ตีส่วนตัว
เมธอดส่วนตัว (private method) ก็สามารถสร้างได้เช่นกัน
เมธอดส่วนตัวคือเมธอดที่สามารถเรียกใช้ได้แค่จากเมธอดภายในออบเจ็กต์เท่านั้น ไม่สามารถเรียกใช้โดยตรงได้
เมธอดส่วนตัวสร้างโดยตอนที่นิยามเมธอดให้เขียนเป็น [ชื่อตัวแปรซิมโบล] แทนที่จะใช้ชื่อธรรมดา เช่น
const KHOMUN = Symbol();
class Digimon {
constructor(chue, lv) {
this.chue = chue;
this.lv = lv;
}
// เมธอดส่วนตัว
[KHOMUN]() {
return this.chue + " lv " + this.lv;
}
// เมธอดที่จะเรียกใช้เมธอดส่วนตัว
bokKhomun() {
alert(this[KHOMUN]());
}
}
export { Digimon };
จากนั้นก็นำมาใช้ดู
import {Digimon} from "./momo.js";
let agumon = new Digimon("อากุมอน",11);
agumon.bokKhomun(); // ได้ อากุมอน lv 11
ทั้งหมดนี้คือวิธีการสร้างและใช้งานมอดูลในจาวาสคริปต์