คอนสตรักเตอร์และอินสแตนซ์
จะเห็นว่าในจาวาสคริปต์นั้นมีออบเจ็กต์ชนิดต่างๆหลายอย่าง เช่น ออบเจ็กต์ธรรมดา
(object), ออบเจ็กต์แถวลำดับ (array), ออบเจ็กต์วันเวลา (date),
ออบเจ็กต์เรกูลาร์เอ็กซ์เพรชชัน (regex) เป็นต้น
แต่ละอย่างนั้นที่จริงแล้วก็เป็นออบเจ็กต์เหมือนกัน
แต่ตอนที่สร้างขึ้นมานั้นมีคุณสมบัติพิเศษเฉพาะตัว เช่นมีพรอเพอร์ตีต่างๆในตัว
มีเมธอดที่สามารถใช้ได้ต่างกันออกไป
โดยพื้นฐานแล้วออบเจ็กต์ชนิดต่างๆจะสร้างขึ้นจากสิ่งที่เรียกว่า
"คอนสตรักเตอร์" (constructor)
คือฟังก์ชันที่เป็นตัวสร้าง
และออบเจ็กต์ที่ถูกสร้างจากคอนสตรักเตอร์จะเรียกว่าเป็น
"อินสแตนซ์" (instance) ของคอนสตรักเตอร์นั้น
เช่นออบเจ็กต์ชนิดวันเวลาถูกสร้างขึ้นมาจากคอนสตรักเตอร์ Date
var wanwela = new Date("2019-8-1");
แบบนี้ออบเจ็กต์ wanwela ก็จะเป็นอินสแตนซ์ของคอนสตรักเตอร์ Date
ออบเจ็กต์ทุกตัวจะมีพรอเพอร์ตีติดตัวคือ .constructor
เป็นตัวบอกว่าสร้างมาจากคอนสตรักเตอร์ตัวไหน เช่น
alert(wanwela.constructor);
ได้
function Date() {
[native code]
}
นอกจากนี้ยังอาจใช้คำสั่ง instanceof
เพื่อทดสอบว่าออบเจ็กต์ตัวนั้นเป็นอินสแตนซ์ของคอนสตรักเตอร์นั้นหรือไม่
alert(wanwela instanceof Date); // ได้ true
ออบเจ็กต์ทุกชนิดล้วนมีคอนสตรักเตอร์ แม้ว่าจะไม่ได้สร้างขึ้นมาจาก new
โดยตรงก็ตาม
เช่นออบเจ็กต์แถวลำดับนั้น สามารถสร้างขึ้นมาได้ง่ายๆจากการใช้วงเล็บเหลี่ยมคร่อม
[ ] หรือจะสร้างจากคอนสตรักเตอร์ Array โดยตรงก็ได้
var arr = new Array("ก", "ข", "ค");
var arr = ["ก", "ข", "ค"];
ไม่ว่าจะสร้างแบบไหนก็ตาม เมื่อมาหาคอนสตรักเตอร์ก็จะได้ว่าเป็น Array
alert(arr.constructor + "\n" + (arr instanceof Array));
ได้
function Array() {
[native code]
}
true
ฟังก์ชันเองจริงๆแล้วก็ถือเป็นออบเจ็กต์ชนิดหนึ่งเหมือนกัน
แม้ว่าจะพิเศษหน่อยตรงที่เวลาใช้ typeof แล้วได้ผลออกมาเป็น function ไม่ใช่
object ก็ตาม
ดังที่ได้กล่าวถึงใน
บทที่ ๑๐
แล้ว ฟังก์ชันเองก็มีคอนสตรักเตอร์คือ Function
แต่โดยทั่วไปมักไม่ได้สร้างจากคอนสตรักเตอร์โดยตรง แต่สร้างโดยใช้คำสั่ง
function() {}
แต่แม้ว่าจะสร้างด้วยวิธีไหนก็ตาม ก็ถือเป็นอินสแตนซ์ของคอนสตรักเตอร์
Function
var f = function() {};
alert(f.constructor + "\n" + (f instanceof Function)); // ได้ true
ได้
function Function() {
[native code]
}
true
คอนสตรักเตอร์ของสายอักขระ ตัวเลข และ บูล
ข้อมูลชนิดอื่นเองก็มีคอนสตรักเตอร์เช่นกัน คอนสตรักเตอร์ของข้อมูลชนิดสายอักขระ,
ตัวเลข และ บูล คือ String, Number และ Boolean
alert("a".constructor.toString().split(" ")[1]); // ได้ String()
alert((1).constructor.toString().split(" ")[1]); // ได้ Number()
alert(true.constructor.toString().split(" ")[1]); // ได้ Boolean()
ดังนั้นจึงถือว่าข้อมูลชนิดสายอักขระทั้งหมดเป็นอินสแตนซ์ของคอนสตรักเตอร์ String
ตัวเลขทั้งหมดเป็นอินสแตนซ์ของ Number
แม้ว่าเวลาที่เราสร้างข้อมูลชนิดสายอักขระกับตัวเลขนั้นจะไม่ได้ทำผ่านฟังก์ชัน
String หรือ Number เลยก็ตาม
เพียงแต่มีข้อควรระวัง คือเวลาที่ใช้ โดยทั่วไปจะไม่ใช้ new แต่เป็นการใส่
String, Number, Boolean เฉยๆ นั่นคือใช้ในลักษณะเป็นฟังก์ชันธรรมดา
ไม่ได้ใช้ในฐานะเป็นคอนสตรักเตอร์ตรงๆ
var str = String({});
var n = Number("0xff");
var b = Boolean(0);
alert(typeof str); // ได้ string
alert(typeof n); // ได้ number
alert(typeof b); // ได้ boolean
alert(str); // ได้ [object Object]
alert(n); // ได้ 255
alert(b); // ได้ false
อันที่จริงแล้วจะใช้ new ก็ได้เช่นกัน แต่ผลที่ได้จะต่างกัน
คือกลายเป็นได้ออบเจ็กต์มาแทน
ซึ่งออบเจ็กต์เหล่านั้นก็ทำงานได้คล้ายกับเป็นสายอักขระ ตัวเลข หรือบูล จริงๆ
แต่มีข้อแตกต่างอยู่ เช่น พอใช้ typeof แล้วจะได้เป็น object
var str = new String({});
var n = new Number("0xff");
var b = new Boolean(0);
alert(typeof str); // ได้ object
alert(typeof n); // ได้ object
alert(typeof b); // ได้ object
alert(str); // ได้ [object Object]
alert(n); // ได้ 255
alert(b); // ได้ false
อีกทั้งการใช้ออบเจ็กต์พวกนี้อาจให้ผลที่ไม่คาดคิด เช่นข้อมูลชนิดบูล
แม้จะเป็นออบเจ็กต์ของ false แต่หากนำมาหาค่าความจริงจะพบว่าได้ true
พอกลับด้านด้วย ! กลับจะได้ false
var b = new Boolean(false);
alert(b); // ได้ false
alert(!b); // ได้ false
alert(!!b); // ได้ true
ดังนั้นโดยทั่วไปแล้วจะไม่ใช้ new กับ String Number Boolean
เพื่อสร้างเป็นออบเจ็กต์
การสร้างคอนสตรักเตอร์
หลังจากเข้าใจว่าคอนสตรักเตอร์คืออะไรแล้ว
ต่อมาก็จะมาดูวิธีการที่จะสร้างมันขึ้นมา
นอกจากคอนสตรักเตอร์ที่มีอยู่แล้วในตัวโปรแกรม
เราสามารถสร้างขึ้นใหม่เองได้ตามที่ต้องการ
ที่จริงแล้วคอนสตรักเตอร์ก็เป็นฟังก์ชันชนิดหนึ่ง
ดังนั้นแค่สร้างฟังก์ชันขึ้นมาก็เป็นคอนสตรักเตอร์ได้แล้ว
ลองทดสอบด้วยการสร้างฟังก์ชันเปล่าๆขึ้น
แล้วนำมาใช้เป็นคอนสตรักเตอร์เพื่อสร้างออบเจ็กต์ได้เลย
function Klongplao() {}
var klong = new Klongplao();
alert(klong); // ได้ [object Object]
จะเห็นว่าฟังก์ชันเปล่าๆที่ถูกสร้างขึ้นมาง่ายๆนี้ทำหน้าที่เป็นคอนสตรักเตอร์ได้จริง
คือเมื่อใช้ new แล้วสร้างออบเจ็กต์ออกมาได้
และเมื่อดูพรอเพอร์ตี .constructor หรือใช้ instanceof ตรวจสอบก็จะพลว่า
Klongplao เป็นคอนสตรักเตอร์ของ klong และ klong เป็นอินสแตนซ์ของ Klongplao
จริงๆ
alert(klong.constructor); // ได้ function Klongplao() {}
alert(klong instanceof Klongplao); // ได้ true
ในตัวอย่างนี้จะเห็นว่าสร้างคอนสตรักเตอร์โดยใช้โครงสร้างที่ใช้ function
ขึ้นต้นเพื่อสร้างฟังก์ชันแบบมีชื่อ แต่ว่าจะใช้โครงสร้าง = function()
สร้างฟังก์ชันแบบไร้ชื่อก็ได้เช่นกัน
Klongplao = function() {};
var klong = new Klongplao();
alert(klong.constructor); // ได้ function() {}
alert(klong instanceof Klongplao); // ได้ true
ต่างกันตรงที่จะได้เป็นคอนสตรักเตอร์ที่ไม่มีชื่อติดมาด้วย
แต่ว่าก็ใช้งานได้เหมือนกัน
อนึ่ง โดยทั่วไปชื่อคอนสตรักเตอร์จะขึ้นต้นด้วยตัวพิมพ์ใหญ่
แต่นี่เป็นแค่ธรรมเนียมปฏิบัติ ไม่ใช่กฎตายตัว
ต่อให้ตั้งชื่อเป็นอย่างอื่นก็ไม่ได้ผิดกฎทางไวยากรณ์
แค่โดยทั่วไปจะใช้ตัวพิมพ์ใหญ่ เพื่อให้รู้ว่านี่ไม่ใช่ฟังก์ชันธรรมดา
แต่เป็นคอนสตรักเตอร์
จากนี้ไปก็จะตั้งชื่อฟังก์ชันที่เป็นคอนสตรักเตอร์ด้วยตัวพิมพ์ใหญ่ตลอดเหมือนกัน
เพื่อให้แยกชัด
การสร้างพรอเพอร์ตีให้อินสแตนซ์
หลังจากที่เข้าใจพื้นฐานของการสร้างคอนสตรักเตอร์จากการสร้างคอนสตรักเตอร์เปล่าๆขึ้นมาแล้ว
ต่อไปมาดูวิธีสร้างเนื้อหาข้างใน
เราสามารถกำหนดพรอเพอร์ตีที่จะมีในอินสแตนซ์ของคอนสตรักเตอร์นี้ได้โดยกำหนดลงภายในโครงสร้างภายในวงเล็บปีกกา
{} ที่สร้างฟังก์ชันที่เป็นคอนสตรักเตอร์นั้น
ภายใน {} นั้น ตัวแปร this จะแทนอินสแตนซ์ที่ถูกสร้างขึ้น
ดังนั้นการสร้างพรอเพอร์ตีตั้งต้นให้อินสแตนซ์ก็ทำได้ง่ายๆด้วยการใส่พรอเพอร์ตีให้
this
เช่น สร้างออบเจ็กต์แมวขึ้นมา ทุกตัวต้องมี ๒ ตา ๔ ขา
function Maeo() {
this.ta = 2;
this.kha = 4;
}
var aria = new Maeo();
alert(aria.ta + " " + aria.kha); // ได้ 2 4
var haru = new Maeo();
alert(haru.ta + " " + haru.kha); // ได้ 2 4
แบบนี้จะสร้างออบเจ็กต์ขึ้นมาจากคอนสตรักเตอร์ Maeo
กี่ตัวก็มีพรอเพอร์ตีแบบเดียวกันนี้ติดตัวมา
ค่านี้จะเปลี่ยนแปลงเมื่อไหร่ก็ได้ และแต่ละตัวมีค่าแยกกัน
คอนสตรักเตอร์นั้นในที่นี้เปรียบเสมือนเป็นแม่พิมพ์ที่สร้างออบเจ็กต์ที่มีลักษณะแบบเดียวกันออกมาเท่านั้น
แต่ออบเจ็กต์ที่สร้างขึ้นมานั้นก็เป็นคนละตัวกัน
เหมือนพิมพ์กุญแจขึ้นมาจากแม่พิมพ์เดียวกัน กุญแจ ๒
อันอาจเหมือนกันเป๊ะตอนออกจากแม่พิมพ์
แต่ถ้าเราเผลอทำกุญแจอันนึงตกจนบิดงอไปมันก็ไม่เหมือนกับอีกอันแล้ว
ดังนั้นการเปลี่ยนแปลงพรอเพอร์ตีของออบเจ็กต์ตัวหนึ่งจะไม่ส่งผลต่ออีกตัว
aria.ta = 3;
alert(aria.ta); // ได้ 3
alert(haru.ta); // ได้ 2
คอนสตรักเตอร์เมื่อไม่ใช้กับ new
คอนสตรักเตอร์นั้นที่จริงแล้วก็เป็นฟังก์ชัน
ดังนั้นหากเรียกใช้ในฐานะฟังก์ชันธรรมดาก็ทำได้เช่นกัน คือเรียกโดยเติม () ไปเฉยๆ
โดยไม่มี new นำหน้า แบบนี้มันก็จะไม่ใช่คอนสตรักเตอร์
อีกทั้งเมื่อเรียกในฐานะฟังก์ชันธรรมดา แบบนี้ this ก็จะหมายถึงตัว global
เพราะไม่ได้อยู่ในโครงสร้างออบเจ็กต์ไหน แบบนี้เท่ากับว่าเป็นการสร้างตัวแปรลงใน
global
เช่น
function Ma() {
this.kha = 4;
}
var uma = Ma();
alert(uma); // ได้ undefined
alert(kha); // ได้ 4
ซึ่งเป็นผลที่อาจไม่คาดคิด
เพียงแต่ว่าก็มีวิธีที่ทำให้คอนสตรักเตอร์ทำงานได้โดยไม่ต้องใช้ new โดยตรงก็ได้
โดยเขียนแบบนี้
function Ma() {
if (!(this instanceof Ma)) {
return new Ma();
}
this.kha = 4;
}
var uma = Ma();
alert(uma); // ได้ [object Object]
alert(uma.kha); // ได้ 4
แบบนี้ถ้าเรียกตัว Ma โดยตรงเหมือนเป็นฟังก์ชันมันก็จะไปเรียก new
ข้างในเพื่อสร้างออบเจ็กต์แล้วคืนตัวออบเจ็กต์มาให้
ดังนั้น
ด้วยการเขียนแบบนี้จะทำให้สามารถป้องกันไม่ให้คอนสตรักเตอร์ถูกใช้ในฐานะฟังก์ชันธรรมดาได้
เพียงแต่ว่าโดยทั่วไปก็ไม่ได้จำเป็นจะต้องทำแบบนี้
ให้จำไว้ว่าถ้าเป็นคอนสตรักเตอร์ก็ควรใช้คู่กับ new เสมอดีกว่า
พารามิเตอร์ในคอนสตรักเตอร์
หากใส่พารามิเตอร์ให้กับคอนสตรักเตอร์ตอนที่สร้างขึ้นก็จะสามารถสร้างคอนสตรักเตอร์ที่มีพรอเพอร์ตีต่างกันไปตามค่าที่ป้อนให้ได้
ตัวอย่าง
function Phanakngan(chue, ayu) {
this.chue = chue;
this.ayu = ayu;
}
var aoba = new Phanakngan("อาโอบะ", 18);
var hifumi = new Phanakngan("ฮิฟุมิ", 20);
alert("ชื่อ" + aoba.chue + " อายุ " + aoba.ayu); // ได้ ชื่ออาโอบะ อายุ 18
alert("ชื่อ" + hifumi.chue + " อายุ " + hifumi.ayu); // ได้ ชื่อฮิฟุมิ อายุ 20
วิธีนี้จะทำให้ออบเจ็กต์ที่สร้างขึ้นจากคอนสตรักเตอร์ตัวเดียวกันมีพรอเพอร์ตีตั้งต้นเหมือนกัน
แค่ค่าต่างกัน ขึ้นกับค่าที่ป้อนเข้าไปตอนแรก
(
สึซึกาเซะ อาโอบะ)
การสร้างเมธอดให้อินสแตนซ์
เมธอดก็คือพรอเพอร์ตีชนิดหนึ่งที่เป็นฟังก์ชัน เมื่อสร้างคอนสตรักเตอร์ขึ้นมา
สามารถสร้างเมธอดขึ้นมาด้วยพร้อมกับพรอเพอร์ตีอื่นๆ
ตัวอย่าง
function Phanakngan(chue, namsakun) {
this.chue = chue;
this.namsakun = namsakun;
this.naenamtua = function() {
alert("ฉันชื่อ" + this.namsakun + " " + this.chue);
};
}
var aoba = new Phanakngan("อาโอบะ", "สึซึกาเซะ");
var hifumi = new Phanakngan("ฮิฟุมิ", "ทากิโมโตะ");
aoba.naenamtua(); // ได้ ฉันชื่อสึซึกาเซะ อาโอบะ
hifumi.naenamtua(); // ได้ ฉันชื่อทากิโมโตะ ฮิฟุมิ
เพียงแต่ว่า ในทางปฏิบัติแล้ว การทำแบบนี้มีข้อเสียอยู่คือ
ทุกครั้งที่สร้างออบเจ็กต์ขึ้นจากคอนสตรักเตอร์นี้จะต้องมีการสร้างเมธอดเหมือนกันขึ้นมาซ้ำ
แต่เมธอดนั้นต่างจากพรอเพอร์ตีทั่วไปตรงที่สามารถใช้ร่วมกันในทุกอินสแตนซ์ได้
ไม่จำเป็นต้องสร้างใหม่แยกกันทั้งหมด
ดังนั้นแทนที่จะนิยามเมธอดขึ้นภายในฟังก์ชันที่ทำเป็นคอนสตรักเตอร์
วิธีที่ดีกว่าคือการนิยามเมธอดลงใน
โพรโทไทป์ของคอนสตรักเตอร์นั้น
ในบทถัดไปจะอธิบายเรื่องโพรโทไทป์