Prototype trong JavaScript: Cơ chế Kế Thừa Đơn Giản và Hiệu Quả

Mở đầu

Prototype là một khái niệm nền tảng trong JavaScript, đóng vai trò quan trọng trong việc hiện thực hóa lập trình hướng đối tượng (OOP) trong ngôn ngữ này. JavaScript là một prototype-based language, có nghĩa là nó không sử dụng các lớp (class) như các ngôn ngữ hướng đối tượng khác. Thay vào đó, nó dựa vào prototype để thực hiện kế thừa. Bài viết này sẽ đi sâu vào cơ chế hoạt động của prototype, giúp bạn hiểu rõ hơn về cách kế thừa (inheritance) được thực hiện trong JavaScript.

Prototype là gì?

Prototype là một cơ chế cho phép các đối tượng (object) trong JavaScript kế thừa các thuộc tính từ một đối tượng khác. Tất cả các đối tượng trong JavaScript đều có một prototype, và chúng kế thừa các thuộc tính (properties) cũng như các phương thức (methods) từ prototype của mình.

Điều quan trọng cần nhớ là bản thân prototype cũng là một đối tượng trong JavaScript, thường được gọi là prototype object (đối tượng prototype). Sự phân biệt này rất quan trọng để tránh nhầm lẫn với thuộc tính prototype của một hàm (function).

Một số điểm cần lưu ý:

  1. Trong JavaScript, một hàm (function) cũng được coi là một đối tượng. Và hàm có một thuộc tính đặc biệt gọi là thuộc tính prototype, bản thân thuộc tính này lại là một đối tượng.
  2. Khi một hàm được sử dụng để tạo ra một khuôn mẫu (template) cho việc khởi tạo đối tượng, bạn có thể thêm các thuộc tính hoặc phương thức vào thuộc tính prototype của hàm khởi tạo. Điều này cho phép bạn thực hiện kế thừa. Tất cả các đối tượng con được tạo ra từ hàm khởi tạo này sẽ tự động kế thừa các giá trị được định nghĩa trong thuộc tính prototype của hàm.
  3. Các đối tượng trong JavaScript có một đặc tính gọi là prototype attribute, đặc tính này trỏ tới prototype object mà đối tượng đó kế thừa. Chúng ta có thể sử dụng thuộc tính __proto__ để truy cập vào prototype object.

Tạo Prototype như thế nào?

Để minh họa cách tạo prototype, chúng ta sẽ khai báo một hàm khởi tạo đơn giản:

// Tạo một khuôn mẫu khởi tạo, đồng thời tạo ra một prototype object
function Person(_age, _name) {
  this.age = _age;
  this.name = _name;
}

// Thêm thuộc tính vào thuộc tính prototype của hàm khởi tạo
Person.prototype.height = 0;

// Tạo một instance của Person
// Instance này sẽ có cả ba thuộc tính: age, name và height
var jack_person = new Person(10, "Jack");

for (var att in jack_person) {
  console.log(att);
}

// Xem đối tượng prototype của instance vừa tạo
jack_person.__proto__;

prototype là gìprototype là gì

Trong đoạn mã trên, chúng ta tạo một hàm khởi tạo Person(_age, _name). Thuộc tính prototype của hàm này chứa thuộc tính height. Do đó, một đối tượng được tạo từ hàm khởi tạo này sẽ có ba thuộc tính: age, nameheight.

Chúng ta có thể truy cập prototype object của đối tượng vừa tạo (instance) để kiểm tra. Kết quả cho thấy object này chứa một hàm khởi tạo và một thuộc tính height.

Tại sao Prototype lại quan trọng trong JavaScript?

Kế thừa trong JavaScript

Prototype đóng vai trò then chốt trong việc hiện thực hóa kế thừa trong JavaScript. Để thực hiện kế thừa, bạn chỉ cần tạo một hàm khởi tạo, sau đó thêm các thuộc tính và phương thức vào thuộc tính prototype của hàm.

Các instance được tạo ra từ hàm khởi tạo này sẽ tự động kế thừa các thuộc tính và phương thức đã được định nghĩa trong prototype. Vì JavaScript không có khái niệm class, prototype là cơ chế chính để thực hiện kế thừa, cho phép mở rộng và tái sử dụng mã một cách hiệu quả tương tự như các ngôn ngữ OOP khác.

// Tạo một hàm khởi tạo cơ sở
function Animal(_age) {
  this.age = _age;
}

// Thêm phương thức vào thuộc tính prototype của hàm khởi tạo
Animal.prototype.showAge = function() {
  console.log(this.age);
};

// Tạo một hàm khởi tạo con (sử dụng để kế thừa hàm cơ sở)
function Dog(_color) {
  this.color = _color;
}

// Thực hiện kế thừa: gán hàm khởi tạo của Animal cho prototype của Dog
Dog.prototype = new Animal();

// Thêm phương thức đặc trưng cho Dog
Dog.prototype.showColor = function() {
  console.log(this.color);
};

// Kiểm tra sự kế thừa
var chophuquoc = new Dog('yellow');
chophuquoc.age = 3;
chophuquoc.showAge(); // 3
chophuquoc.showColor(); // yellow

Trong ví dụ trên, đối tượng chophuquoc có thể sử dụng phương thức showAge() được định nghĩa trong Animal.prototype vì chúng ta đã gán hàm khởi tạo của Animal cho prototype của Dog. Điều này minh họa rõ ràng cơ chế kế thừa trong JavaScript: chophuquoc đã kế thừa mọi thứ từ Dog.prototype, bao gồm cả những thuộc tính và phương thức mà Dog.prototype kế thừa từ Animal.prototype.

Truy cập thuộc tính đối tượng: Prototype chain

Prototype rất quan trọng trong việc giúp chúng ta truy cập các thuộc tính và phương thức của đối tượng. Khi chúng ta cố gắng truy cập một thuộc tính của một đối tượng, JavaScript sẽ tìm thuộc tính đó trước tiên bên trong chính đối tượng đó. Nếu không tìm thấy, nó sẽ tiếp tục tìm lên trên prototype của đối tượng, và cứ tiếp tục như vậy cho đến khi gặp null (kết thúc chuỗi prototype) thì dừng lại. Nếu thuộc tính vẫn không được tìm thấy, kết quả trả về sẽ là undefined.

Quá trình tìm kiếm lặp đi lặp lại này được gọi là chuỗi prototype (prototype chain) trong JavaScript. Chính cơ chế này, kết hợp với thuộc tính prototype của function, tạo nên cơ chế kế thừa dựa trên prototype của JavaScript.

Tham khảo thêm: