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



javascript เบื้องต้น บทที่ ๓๔: การสร้างคลาส
เขียนเมื่อ 2020/02/02 10:42
แก้ไขล่าสุด 2021/09/28 16:42


ในบทที่ ๒๐ และบทที่ ๒๑ ได้เขียนถึงเรื่องคอนสตรักเตอร์และโพรโทไทป์ ซึ่งเป็นสิ่งที่เสมือนกับสิ่งที่เรียกว่า "คลาส" (class) ในภาษาอื่นๆที่มีการเขียนโปรแกรมเชิงวัตถุ

หากเทียบกับภาษาอื่นแล้ว การสร้างคลาส (โพรโทไทป์) ในจาวาสคริปต์นั้นถือว่ามีวิธีการเขียนที่ค่อนข้างแปลก และเข้าใจยากกว่า

ด้วยเหตุนี้ใน ES6 จึงได้เพิ่มวิธีการสร้างโพรโทไทป์แบบใหม่ ซึ่งมีลักษณะใกล้เคียงกับการสร้างคลาสในภาษาอื่น นั่นคือการใช้คำสั่ง class เพื่อสร้าง

ในบทนี้จะพูดถึงวิธีการสร้างคลาสแบบใหม่นี้




การสร้างคลาสด้วยคำสั่ง class

ในการสร้างคลาสด้วยวิธีดั้งเดิมซึ่งใช้คำสั่ง function ในการสร้างนั้นจะสร้างพรอเพอร์ตีและเมธอดขึ้นมาพร้อมกัน เช่น
function Pokemon(chue, lv) {
  // กำหนดพรอเพอร์ตี
  this.chue = chue;
  this.lv = lv;
  // กำหนดเมธอด
  this.bokKhomun = function(){
    alert(this.chue + " lv " + this.lv)
  };
}

let gardie = new Pokemon("การ์ดี", 19);
gardie.bokKhomun(); // ได้ การ์ดี lv 19



แต่ว่าหากสร้างโดยใช้คำสั่ง class จะแยกส่วนของการสร้างเมธอดมาจากส่วนสร้างคอนสตรักเตอร์ สามารถเขียนได้แบบนี้
class Pokemon {
  constructor(chue, lv) {
    this.chue = chue;
    this.lv = lv;
  }
  bokKhomun() {
    alert(this.chue + " lv " + this.lv)
  }
}

let windie = new Pokemon("วินดี", 49);
windie.bokKhomun(); // ได้ วินดี lv 49



ภายในโครงสร้างภายในวงเล็บปีกกา { } หลังคำสั่ง class ให้เขียนเมธอดทั้งหมดที่ต้องการลงไป โดยมีเมธอดที่ชื่อ constructor จะกลายเป็นคอนสตรักเตอร์ นอกจากนั้นก็เติมเมธอดอื่นๆที่ต้องการด้วยชื่อที่ต้องการก็จะกลายเป็นเมธอดของออบเจ็กต์ในคลาสนั้น

เมื่อเทียบกับวิธีดั้งเดิมแล้วจะเห็นว่าการเขียนแบบนี้ดูใกล้เคียงการสร้างคลาสในภาษาอื่นมากกว่า จึงอาจทำให้ดูเข้าใจได้ง่ายกว่า โดยเฉพาะสำหรับคนที่เคยเขียนภาษาอื่นมา

การสร้างคลาสด้วยวิธีนี้มีข้อแตกต่างจากวิธีดั้งเดิมเล็กน้อยตรงที่ว่าไม่สามารถถูกเรียกใช้เป็นฟังก์ชันได้ ต้องใช้ new เท่านั้น

ลองเรียกใช้แบบฟังก์ชันดูจะเกิดข้อผิดพลาด
Pokemon(); // ได้ TypeError: class constructors must be invoked with 'new'

นอกจากนี้ ยังอาจเขียนในแบบคลาสไร้ชื่อ โดยใช้ ชื่อคลาส = class {} แบบนี้ก็ได้ ได้ผลเหมือนกัน เช่นเดียวกับตอนสร้างด้วย function
const Pokemon = class {
  constructor(chue, lv) {
    this.chue = chue;
    this.lv = lv;
  }
  bokKhomun() {
    alert(this.chue + " lv " + this.lv)
  }
};




การสร้างเมธอดสถิตด้วยการเติม static

ถ้าใส่คำว่า static ลงไปหน้าชื่อเมธอด เมธอดนั้นจะกลายเป็นเมธอดสถิต (static method)

เมธอดสถิตคือเมธอดที่เรียกใข้จากตัวคลาส ต่างจากเมธอดทั่วไปที่เรียกใช้จากตัวออบเจ็กต์ของคลาส

ตัวอย่าง
class Songsiliam {
  constructor(kwang, yao, sung) {
    this.kwang = kwang;
    this.yao = yao;
    this.sung = sung;
  }
  parimat() {
    return this.kwang * this.yao * this.sung;
  }
  static haParimat(kwang, yao, sung) {
    return kwang * yao * sung;
  }
}

// ใช้เมธอดสถิต
alert(Songsiliam.haParimat(4, 5, 6)); // ได้ 120

// สร้างออบเจ็กต์และใช้เมธอดธรรมดา
let ssl = new Songsiliam(5, 6, 8);
alert(ssl.parimat()); // ได้ 240

ในตัวอย่างนี้เป็นการสร้างคลาส Songsiliam (ทรงสี่เหลี่ยม) ซึ่งมีพรอเพอร์ตีคือ ความกว้าง, ความยาว, ความสูง และมีเมธอดธรรมดาคือ parimat ซึ่งเอาไว้คำนวณปริมาตร

นอกจากนี้ยังมีเมธอดสถิต คือ haParimat เอาไว้คำนวณปริมาตรขึ้นจากค่าความกว้าง, ความยาว, ความสูง ทันทีโดยไม่ต้องสร้างออบเจ็กต์ขึ้นมา

จากตัวอย่างนี้จะเห็นว่าเมธอดสถิต haParimat ถูกเรียกใช้จากตัวคลาส คือ Songsiliam โดยตรง ในขณะที่เมธอดธรรมดา parimat ต้องสร้างออบเจ็กต์ขึ้นมาจากคลาสก่อน แล้วจึงเรียกใช้จากตัวออบเจ็กต์




การรับทอดด้วยการเติม extends

ในบทที่ ๒๒ ได้เขียนถึงการรับทอด (inheritance) ในแบบดั้งเดิมไป จะเห็นได้ว่าการรับทอดของจาวาสคริปต์นั้นค่อนข้างยุ่งยากกว่าภาษาอื่น

แต่ถ้าใช้วิธีใหม่ใน ES6 การรับทอดก็จะดูเขียนง่ายและเข้าใจง่ายขึ้น

การรับทอดเมื่อนิยามคลาสด้วยคำสั่ง class นั้นสามารถทำได้โดยเติมคำว่า extends ลงไปหลังชื่อคลาสใหม่ที่จะสร้าง แล้วตามด้วยชื่อคลาสที่ต้องการรับทอดมา

คลาสที่รับทอดมาจะเรียกว่าเป็นซูเปอร์คลาส (super class) ของคลาสนั้น และคลาสที่สร้างขึ้นใหม่จะเรียกว่าเป็นซับคลาส (sub class) ของซูเปอร์คลาสนั้น

ตัวอย่าง
// สร้างซูเปอร์คลาส
class Pokemon {
  // คอนสตรักเตอร์ของซูเปอร์คลาส
  constructor(chue, lv) {
    // กำหนดพรอเพอร์ตีของซูเปอร์คลาส
    this.chue = chue;
    this.lv = lv;
  }
  // เมธอดของซูเปอร์คลาส
  bokKhomun() {
    alert(this.chue + " lv " + this.lv);
  }
}

// รับทอดจากซูเปอร์คลาส
class Zenigame extends Pokemon {
  // คอนสตรักเตอร์ของคลาสใหม่
  constructor(chue, lv, siang = "เซนิ เซนิ") {
    super(chue, lv); // เรียกใช้คอนสตรักเตอร์ของซูเปอร์คลาส
    this.siang = siang; // เพิ่มพรอเพอร์ตีเฉพาะของคลาสใหม่
  }
  // สร้างเมธอดใหม่ให้คลาสใหม่
  rong() {
    alert("~" + this.siang + "~");
  }
}

// สร้างออบเจ็กต์
let zenichan = new Zenigame("เซนิงาเมะ", 10, "เซเซเซนี้");
// ใช้เมธอดของซูเปอร์คลาส
zenichan.bokKhomun(); // ได้ เซนิงาเมะ lv 10
// ใช้เมธอดของตัวคลาสนั้นเอง
zenichan.rong(); // ได้ ~เซเซเซนี้~



ฟังก์ชัน super ในที่นี้มีไว้เรียกใช้คอนสตรักเตอร์ของซูเปอร์คลาส อาร์กิวเมนต์ที่ต้องใส่ในวงเล็บหลัง super ก็เป็นไปตามคอนสตรักเตอร์ของซูเปอร์คลาส

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




การใส่พรอเพอร์ตีแบบ set และ get

ในบทที่ ๒๖ ได้เขียนถึงการสร้างออบเจ็กต์ที่มีพรอเพอร์ตีแบบ set และ get ใน ES5 ไปแล้ว

สำหรับใน ES6 โครงสร้างคลาสที่มีพรอเพอร์ตีแบบนั้นได้ในระหว่างนิยามคลาสขึ้นด้วยคำสั่ง class โดยเมธอดสำหรับดูค่าและรับค่าของพรอเพอร์ตีตัวนี้จะนิยามโดยใช้ get และ set นำหน้าชื่อ

ตัวอย่าง
class Pokemon {
  constructor(chue) {
    this.chue = chue;
  }
  // รับค่า
  get lv() {
    return "**" + this._lv + "**";
  }
  // ตั้งค่า
  set lv(x) {
    this._lv = parseInt(x);
  }
}

// สร้างออบเจ็กต์
let nyoro = new Pokemon("เนียวโรโม");
// ตั้งค่าใส่เข้าไป
nyoro.lv = 14.12;
// รับค่าเพื่อดูค่า
alert(nyoro.lv); // ได้ **14**



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

ส่วนเวลาที่สั่งดูค่า lv ก็จะไปเอาค่า ._lv มาแล้วเติม ** ขนาบมาด้วย ดังที่เห็น

ถ้าต้องการพรอเพอร์ตีที่อ่านค่าได้อย่างเดียวแต่เอาไว้ตั้งค่าไม่ได้ก็จะใส่แค่ get อย่างเดียวก็ได้ เช่น
class Pokemon {
  constructor(chue, lv) {
    this.chue = chue;
    this.lv = lv;
  }
  get chue_lv() {
    return "ชื่อ: " + this.chue + " lv: " + this.lv;
  }
}

let madatsubomi = new Pokemon("มาดัตสึโบมิ", 11);
alert(madatsubomi.chue_lv); // ได้ ชื่อ: มาดัตสึโบมิ lv: 11



หรือในทางกลับกัน จะสร้างพรอเพอร์ตีที่เอาไว้ป้อนค่าอย่างเดียวแต่ดูข้อมูลไม่ได้ก็ได้ โดยใส่แต่ set
class Pokemon {
  constructor(chue) {
    this.chue = chue;
  }
  set nak_sung(ns) {
    let [nak,sung] = ns.split(",");
    this.nak = parseFloat(nak);
    this.sung = parseFloat(sung);
  }
}
let utsudon = new Pokemon("อุตสึดง");
utsudon.nak_sung = "6.4 kg, 1.0 m";
alert(utsudon.nak); // ได้ 6.4
alert(utsudon.sung); // ได้ 1
alert(utsudon.nak_sung); // ได้ undefined



ทั้งหมดนี้คือวิธีการนิยามคลาสแบบใหม่ใน ES6 ซึ่งช่วยให้การสร้างคลาสทำได้ง่ายกว่าแบบเดิม





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

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

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

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> 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月

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

ไทย

日本語

中文