Prototype Inheritance (hay kế thừa prototype) trong JavaScript là cơ chế mà các đối tượng có thể “thừa hưởng” các thuộc tính và phương thức từ các đối tượng khác thông qua chuỗi prototype. Đây là một tính năng quan trọng trong mô hình lập trình hướng đối tượng (OOP) của JavaScript.
Cách hoạt động của Prototype Inheritance
Trong JavaScript, mỗi đối tượng có một thuộc tính ẩn gọi là [[Prototype]]
, nó tham chiếu đến một đối tượng khác, thường được gọi là prototype. Khi bạn truy cập một thuộc tính hoặc phương thức của đối tượng, JavaScript sẽ kiểm tra xem đối tượng có thuộc tính hoặc phương thức đó không. Nếu không, nó sẽ tìm kiếm trong đối tượng prototype mà đối tượng này liên kết đến. Quá trình tìm kiếm này tiếp diễn cho đến khi tìm thấy thuộc tính hoặc phương thức, hoặc cho đến khi prototype cuối cùng (thường là null
) được kiểm tra.
Ví dụ về Prototype Inheritance
1. Tạo constructor function và prototype
function Person(name, age) {
this.name = name;
this.age = age;
}
// Thêm một phương thức vào prototype của Person
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
Trong ví dụ trên:
Person
là một hàm khởi tạo (constructor function).
- Phương thức
sayHello
được thêm vào prototype của Person
, nghĩa là tất cả các đối tượng được tạo từ Person
sẽ có quyền truy cập vào phương thức này thông qua kế thừa prototype.
2. Tạo một đối tượng và sử dụng kế thừa prototype
const person1 = new Person("Alice", 25);
person1.sayHello(); // "Hello, my name is Alice and I am 25 years old."
Ở đây:
- Khi
person1.sayHello()
được gọi, JavaScript sẽ tìm kiếm phương thức sayHello
trong đối tượng person1
. Vì person1
không có phương thức này trực tiếp, JavaScript sẽ kiểm tra trong prototype của person1
, đó chính là prototype của Person
. Tại đây, nó sẽ tìm thấy sayHello
và thực thi nó.
3. Liên kết chuỗi prototype
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
person1.__proto__
(hoặc [[Prototype]]
) trỏ tới Person.prototype
.
Person.prototype.__proto__
trỏ tới Object.prototype
, vì mọi đối tượng trong JavaScript cuối cùng đều kế thừa từ Object
.
- Chuỗi kế thừa tiếp tục cho đến khi prototype của
Object
là null
.
Một ví dụ khác về Prototype Inheritance
function Animal(type) {
this.type = type;
}
Animal.prototype.getType = function() {
return this.type;
};
function Dog(name) {
this.name = name;
}
// Kế thừa từ Animal
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log("Woof! My name is " + this.name);
};
const myDog = new Dog("Buddy");
myDog.type = "Dog";
myDog.bark(); // "Woof! My name is Buddy"
console.log(myDog.getType()); // "Dog"
Giải thích:
- Hàm khởi tạo
Animal
có một thuộc tính type
và một phương thức getType
trong prototype.
- Hàm khởi tạo
Dog
có một thuộc tính name
. Nó kế thừa từ Animal
bằng cách sử dụng Object.create(Animal.prototype)
, nghĩa là Dog
có quyền truy cập vào tất cả các phương thức của Animal
.
- Phương thức
bark
được thêm trực tiếp vào prototype của Dog
.
Lưu ý về constructor
:
- Sau khi sử dụng
Object.create
để kế thừa từ Animal
, giá trị của Dog.prototype.constructor
sẽ bị thay đổi. Do đó, ta cần gán lại giá trị Dog.prototype.constructor = Dog
để đảm bảo đối tượng được tạo từ Dog
sẽ có constructor đúng.
Cơ chế Prototype Chain
Khi bạn truy cập một thuộc tính hoặc phương thức của một đối tượng, JavaScript sẽ duyệt qua chuỗi prototype (prototype chain). Quá trình này diễn ra như sau:
- JavaScript kiểm tra đối tượng trực tiếp để tìm thuộc tính hoặc phương thức.
- Nếu không tìm thấy, nó kiểm tra prototype của đối tượng đó (thông qua
__proto__
hoặc [[Prototype]]
).
- Quá trình tiếp tục kiểm tra prototype của prototype, và cứ thế tiếp tục cho đến khi gặp
null
hoặc tìm thấy thuộc tính/method.
Ưu điểm của Prototype Inheritance
- Tiết kiệm bộ nhớ: Các phương thức chung của nhiều đối tượng chỉ được lưu một lần trong prototype. Điều này giúp tối ưu hóa việc sử dụng bộ nhớ.
- Chia sẻ phương thức: Các đối tượng có thể chia sẻ các phương thức và thuộc tính thông qua prototype mà không cần phải sao chép chúng cho từng đối tượng riêng lẻ.
- Tính mở rộng: Bạn có thể dễ dàng thêm phương thức mới vào prototype của hàm khởi tạo, và tất cả các đối tượng được tạo từ hàm khởi tạo đó đều có thể truy cập vào phương thức mới này.
Nhược điểm của Prototype Inheritance
- Khó hiểu cho người mới bắt đầu: Mô hình kế thừa qua prototype có thể khó hiểu hơn so với các ngôn ngữ lập trình hướng đối tượng khác sử dụng lớp (class-based inheritance).
- Không có “private”: Các thuộc tính trong prototype có thể được truy cập và sửa đổi từ các đối tượng khác, dẫn đến khả năng bị thay đổi không mong muốn.
- Phức tạp khi xử lý các chuỗi prototype dài: Nếu prototype chain quá dài, việc truy cập thuộc tính sẽ tốn nhiều thời gian hơn do phải duyệt qua nhiều prototype.
Tổng kết
Prototype Inheritance trong JavaScript là cơ chế cho phép các đối tượng kế thừa thuộc tính và phương thức từ các đối tượng khác thông qua prototype. Điều này giúp tối ưu hóa bộ nhớ và cho phép chia sẻ phương thức giữa các đối tượng. Tuy nhiên, nó cũng có những nhược điểm liên quan đến tính dễ hiểu và khả năng bảo mật của dữ liệu.