Prototype Inheritance ใน JavaScript: สืบทอดคุณสมบัติอย่างเข้าใจง่าย
จากบทที่แล้วที่เราได้เรียนรู้เกี่ยวกับ Revealing Module Pattern ซึ่งเน้นเรื่องการจัดการ scope และ encapsulation ใน JavaScript คราวนี้เราจะมาเจาะลึกอีกหนึ่ง concept ที่สำคัญไม่แพ้กัน นั่นคือ Prototype Inheritance หรือการสืบทอดคุณสมบัติผ่าน Prototype ซึ่งเป็นกลไกหลักที่ทำให้ JavaScript สามารถจำลองแนวคิด Object-Oriented Programming (OOP) ได้อย่างมีประสิทธิภาพ
Prototype Inheritance คือกลไกที่ object สามารถสืบทอดคุณสมบัติ (properties และ methods) จาก object อื่นได้ โดย object ที่ถูกสืบทอดเรียกว่า "prototype" และ object ที่สืบทอดเรียกว่า "instance" หรือ "object" นั่นเอง การสืบทอดนี้เกิดขึ้นผ่านสิ่งที่เรียกว่า Prototype Chain ซึ่งเราจะอธิบายในรายละเอียดต่อไป
Prototype Chain คืออะไร?
Prototype Chain เปรียบเสมือนห่วงโซ่ที่เชื่อมต่อ object ต่างๆ เข้าด้วยกัน โดยแต่ละ object จะมี link ไปยัง prototype ของมัน หากเราพยายามเข้าถึง property หรือ method ที่ object นั้นไม่มี JavaScript จะทำการค้นหาใน prototype ของ object นั้น และถ้ายังไม่พบก็จะค้นหาใน prototype ของ prototype ไปเรื่อยๆ จนกว่าจะพบ property หรือ method ที่ต้องการ หรือจนกว่าจะถึงจุดสิ้นสุดของ chain ซึ่งก็คือ null
ลองพิจารณาตัวอย่างต่อไปนี้:
function Animal(name) {
this.name = name;
}
Animal.prototype.sayHello = function() {
console.log("Hello, I'm " + this.name);
};
function Dog(name, breed) {
Animal.call(this, name); // เรียก constructor ของ Animal เพื่อกำหนด name
this.breed = breed;
}
// กำหนด prototype ของ Dog ให้เป็น instance ของ Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // สำคัญ: กำหนด constructor กลับเป็น Dog
Dog.prototype.bark = function() {
console.log("Woof!");
};
const myDog = new Dog("Buddy", "Golden Retriever");
myDog.sayHello(); // Output: Hello, I'm Buddy (สืบทอดมาจาก Animal)
myDog.bark(); // Output: Woof! (มาจาก Dog เอง)
console.log(myDog.name); // Output: Buddy (มาจาก Animal)
console.log(myDog.breed); // Output: Golden Retriever (มาจาก Dog)
console.log(myDog instanceof Animal); // Output: true
console.log(myDog instanceof Dog); // Output: true
ในตัวอย่างนี้:
- เราสร้าง constructor function ชื่อ
Animalซึ่งมี propertynameและ methodsayHello - เราสร้าง constructor function ชื่อ
Dogซึ่งสืบทอดจากAnimalโดยใช้Animal.call(this, name)เพื่อเรียก constructor ของAnimalและกำหนด propertybreedเพิ่มเติม - ที่สำคัญที่สุด เรากำหนด
Dog.prototypeให้เป็น instance ของAnimal.prototypeโดยใช้Object.create(Animal.prototype)ซึ่งเป็นการสร้าง object ใหม่ที่มี prototype เป็นAnimal.prototype - เรากำหนด
Dog.prototype.constructor = Dog;เพื่อให้ constructor ของDog.prototypeชี้กลับไปยังDogซึ่งเป็นสิ่งสำคัญเพื่อให้instanceofทำงานได้อย่างถูกต้อง - เราเพิ่ม method
barkให้กับDog.prototype
เมื่อเราสร้าง instance myDog จาก Dog, myDog จะสามารถเข้าถึง property name และ method sayHello ได้เนื่องจากสืบทอดมาจาก Animal นอกจากนี้ยังสามารถเข้าถึง property breed และ method bark ที่เป็นของ Dog เองได้อีกด้วย
__proto__ property
ทุก object ใน JavaScript (ยกเว้น object ที่สร้างขึ้นโดย Object.create(null)) จะมี property พิเศษชื่อ __proto__ (double underscore proto double underscore) ซึ่งชี้ไปยัง prototype ของ object นั้น แม้ว่า __proto__ จะเป็น property ที่ไม่แนะนำให้ใช้ใน production code (เนื่องจากเป็น non-standard และอาจถูกเปลี่ยนแปลงในอนาคต) แต่การทำความเข้าใจการทำงานของมันจะช่วยให้เราเข้าใจ Prototype Chain ได้ดียิ่งขึ้น
ในตัวอย่างก่อนหน้า:
myDog.__proto__จะชี้ไปยังDog.prototypeDog.prototype.__proto__จะชี้ไปยังAnimal.prototypeAnimal.prototype.__proto__จะชี้ไปยังObject.prototypeObject.prototype.__proto__จะเป็นnullซึ่งเป็นจุดสิ้นสุดของ Prototype Chain
ดังนั้นเมื่อเราพยายามเข้าถึง property หรือ method ที่ myDog ไม่มี JavaScript จะทำการค้นหาใน Dog.prototype จากนั้น Animal.prototype และสุดท้าย Object.prototype ตามลำดับ
Object.create()
Object.create() เป็น method ที่ใช้สร้าง object ใหม่ที่มี prototype เป็น object ที่เรากำหนด วิธีนี้เป็นวิธีที่แนะนำให้ใช้ในการสร้าง object ที่สืบทอดมาจาก object อื่น เนื่องจากมีความยืดหยุ่นและปลอดภัยกว่าการแก้ไข prototype โดยตรง
Syntax: Object.create(proto, [propertiesObject])
proto: object ที่จะเป็น prototype ของ object ใหม่propertiesObject(optional): object ที่กำหนด properties และ descriptors ของ object ใหม่
ตัวอย่างการใช้งาน Object.create():
const person = {
name: "John",
sayHello: function() {
console.log("Hello, my name is " + this.name);
}
};
const student = Object.create(person);
student.name = "Alice";
student.major = "Computer Science";
student.sayHello(); // Output: Hello, my name is Alice (สืบทอดมาจาก person)
console.log(student.major); // Output: Computer Science (มาจาก student เอง)
console.log(student.__proto__ === person); // Output: true
ในตัวอย่างนี้ student สืบทอด properties และ methods จาก person และเราสามารถเพิ่ม properties ใหม่ให้กับ student ได้โดยไม่กระทบกับ person
กรณีใช้งานจริงและข้อผิดพลาดที่พบบ่อย
Prototype Inheritance ถูกนำไปใช้อย่างแพร่หลายในการพัฒนา JavaScript Libraries และ Frameworks ต่างๆ เพื่อสร้าง component ที่สามารถนำกลับมาใช้ใหม่ได้ ตัวอย่างเช่น React, Angular, และ Vue.js ใช้ Prototype Inheritance เพื่อสร้าง component classes ที่สามารถสืบทอดคุณสมบัติและ methods จากกันและกันได้
ข้อผิดพลาดที่พบบ่อย:
- ลืมกำหนด
Dog.prototype.constructor = Dog;: การลืมกำหนด constructor จะทำให้instanceofทำงานไม่ถูกต้อง และอาจทำให้เกิดปัญหาในการ debug - แก้ไข
prototypeโดยตรง: การแก้ไขprototypeโดยตรงอาจส่งผลกระทบต่อ object ที่สร้างขึ้นแล้ว ดังนั้นควรใช้Object.create()แทน - เข้าใจผิดว่า Prototype Inheritance คือ Class Inheritance: แม้ว่า Prototype Inheritance จะสามารถจำลองแนวคิด OOP ได้ แต่ก็มีความแตกต่างจาก Class Inheritance ในภาษาอื่นๆ เช่น Java หรือ C#
สรุป
Prototype Inheritance เป็นกลไกที่สำคัญในการทำความเข้าใจ JavaScript และเป็นพื้นฐานสำหรับ Object-Oriented Programming ใน JavaScript การเข้าใจ Prototype Chain และการใช้งาน Object.create() จะช่วยให้เราเขียนโค้ดที่ยืดหยุ่นและนำกลับมาใช้ใหม่ได้ง่ายขึ้น ในบทต่อไปเราจะมาเรียนรู้เกี่ยวกับ Class Syntax ซึ่งเป็น syntax ใหม่ที่ทำให้การเขียน OOP ใน JavaScript ง่ายขึ้นและคล้ายกับภาษาอื่นๆ มากขึ้น