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



javascript เบื้องต้น บทที่ ๒๐: คอนสตรักเตอร์
เขียนเมื่อ 2019/07/31 23:31
แก้ไขล่าสุด 2021/09/28 16:42


คอนสตรักเตอร์และอินสแตนซ์

จะเห็นว่าในจาวาสคริปต์นั้นมีออบเจ็กต์ชนิดต่างๆหลายอย่าง เช่น ออบเจ็กต์ธรรมดา (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(); // ได้ ฉันชื่อทากิโมโตะ ฮิฟุมิ

เพียงแต่ว่า ในทางปฏิบัติแล้ว การทำแบบนี้มีข้อเสียอยู่คือ ทุกครั้งที่สร้างออบเจ็กต์ขึ้นจากคอนสตรักเตอร์นี้จะต้องมีการสร้างเมธอดเหมือนกันขึ้นมาซ้ำ

แต่เมธอดนั้นต่างจากพรอเพอร์ตีทั่วไปตรงที่สามารถใช้ร่วมกันในทุกอินสแตนซ์ได้ ไม่จำเป็นต้องสร้างใหม่แยกกันทั้งหมด

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

ในบทถัดไปจะอธิบายเรื่องโพรโทไทป์




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

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

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

หมวดหมู่

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

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

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

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



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

  ค้นหาบทความ

  บทความแนะนำ

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

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

2024年

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

2023年

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

2022年

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

2021年

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

2020年

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

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

ไทย

日本語

中文