φυβλαςのβλογ
บล็อกของ phyblas



javascript เบื้องต้น บทที่ ๓๖: การสร้างและใช้งานมอดูล
เขียนเมื่อ 2020/02/04 13:02


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

ใน 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

ทั้งหมดนี้คือวิธีการสร้างและใช้งานมอดูลในจาวาสคริปต์





-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
python
-- numpy
-- matplotlib

-- pandas
-- pytorch
maya
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
javascript
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
เรียนภาษาจีน
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
หลักการเขียนทับศัพท์ภาษาจีนกวางตุ้ง
การใช้ unix shell เบื้องต้น ใน linux และ mac
หลักการเขียนทับศัพท์ภาษาจีนกลาง
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
บันทึกการเที่ยวสวีเดน 1-12 พ.ค. 2014
แนะนำองค์การวิจัยและพัฒนาการสำรวจอวกาศญี่ปุ่น (JAXA)
เล่าประสบการณ์ค่ายอบรมวิชาการทางดาราศาสตร์โดยโซวเคนได 10 - 16 พ.ย. 2013
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?

บทความแต่ละเดือน

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2019年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2018年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2017年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2016年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文