ใน
บทที่ ๒๐ และ
บทที่ ๒๑ ได้เขียนถึงเรื่องคอนสตรักเตอร์และโพรโทไทป์ ซึ่งเป็นสิ่งที่เสมือนกับสิ่งที่เรียกว่า "คลาส" (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 ซึ่งช่วยให้การสร้างคลาสทำได้ง่ายกว่าแบบเดิม