φυβλαςのβλογ
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
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



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

  查看日志

  推荐日志

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