Prototype trong Javascript - Tại sao nó lại quan trọng
Prototype là một khái niệm quan trọng trong Javascript, nếu bạn muốn nắm vững Javascript thì chắc chắn phải hiểu về khái niệm Prototype. Trong Javascript không kế thừa kiểu class-based mà kế thừa trong Javascript là dựa vào Protype (từ ES5 trở về trước), điều này khiến nó trở nên rất quan trọng.
Để hiểu được Prototype, trước hết bạn cần phải hiểu về Object trong Javascript. Bạn có thể xem bài viết này Tìm hiểu về Object trong JS để hiểu các khái niệm về object trong Javascript trước khi bắt đầu.
Prototype là gì?
Trước hết, bạn phải hiểu rằng bản thân Prototype đã là một đối tượng Object trong Javascript và được gọi là Prototype Object. Mình nhấn mạnh điều này để các bạn chú ý điều này, tránh nhầm lẫn với thuộc tính prototype của function
Tất cả các object trong JS đều có một Prototype và các Object này kế thừa các thuộc tính (Properties) cũng như phương thức (methods) từ prototype của mình.
Nếu bạn vẫn chưa hiểu được ngay thì cũng đừng vội, Javascript vốn dĩ lằng nhằng và khó hiểu rồi, nhưng vượt qua được giai đoạn đầu bạn sẽ thấy dễ chịu hơn rất nhiều.
Một số khái niệm đặc biệt về Javascript
Thứ 1: Trong Javascript, bản thân của một Function cũng được coi là 1 Object, và Function có một thuộc tính (property) gọi là thuộc tính prototype, bản thân thuộc tính prototype này có kiểu dữ liệu là object. Chú ý: một instance object thì không có thuộc tính prototype
Thứ 2: Bởi vì ta dùng function để tạo ra 1 mẫu khởi tạo đối tượng, do đó thuộc tính prototype của function có 1 khả năng đặc biệt: bạn sẽ thêm các thuộc tính (property) hoặc phương thức (method) vào thuộc tính prototype của function khởi tạo để thực hiện kế thừa, tất cả các đối tượng con tạo ra bởi hàm khởi tạo đều mang các giá trị trong thuộc tính prototype của hàm này. Để tạo ra object dùng prototype bạn có thể xem ở đây.
Chính bởi 2 khái niệm trên, mà có thể xem mẫu khởi tạo (constructor function) là 1 đối tượng prototype, bản thân nó có thuộc tính prototype. Kế thừa trong Javascript thuộc kiểu prototype-based (không giống với class-based như các ngôn ngữ OOP khác).
Thứ 3: Các object trong Javascript có một khái niệm gọi là đặc tính prototype của đối tượng (prototype attribute), đặc tính này có giá trị trỏ tới prototype object mà nó kế thừa thuộc tính. Ta dùng thuộc tính “__proto__” để truy cập tới prototype object.
Cách tạo 1 Prototype
Như đã nói tới ở trên, do hàm khởi tạo đối tượng cũng được xem là 1 đối tượng prototype, do đó các đơn giản để tạo ra 1 đối tượng prototype là khai báo một hàm khởi tạo:
function Student(name, email){
this.name = name;
this.email = email;
}
Có thể thêm thuộc tính vào thuộc tính prototype của hàm khởi tạo
Student.prototype.phone = 0942668588
Tạo instance từ Prototype
let studentOne = new Student('Nguyễn Mạnh Hùng', "hunglm@gmail.com");
Xem đối tượng Prototype của instance vừa tạo
for (let attributeName in studentOne) {
console.log(attributeName);
}
Kết quả:
name
email
phone
Đoạn code trên vừa tạo ra một hàm khởi tạo là hàm Student(name, email), thuộc tính Prototype của hàm này lại chứa thuộc tính phone
. Do đó một đối tượng được tạo ra từ hàm khởi tạo này ta sẽ có 3 thuộc tính: name, email và phone.
Mặc dù khi console.log() instance studentOne thì chỉ có email và name như dưới đây
console.log(studentOne)
// Kết quả
Student {
name: "Nguyễn Mạnh Hùng"
email: "hunglm@gmail.com"
__proto__: Object
}
Nhắc lại về cách tạo object
Như đã nhắc tới trong bài viết tìm hiểu về object trong Js, ta có cách cách sau để tạo ra object: dùng object literal, dùng constructor của đối tượng Object, hoặc tự tạo một constructor function (mẫu khởi tạo).
Những cách này về mặt cách thức thì khác nhau, nhưng về bản chất chúng đều dùng tới hàm khởi tạo và các thuộc tính prototype của hàm này để tạo ra một đối tượng mới: object literal và Object constructor sử dụng Object() và Object.prototype, nếu dùng mẫu khởi tạo thì là mauKhoiTao() và mauKhoiTao.prototype.
Tại sao prototype lại quan trọng trong Javascript?
Thuộc tính prototype của function: cơ chế kế thừa trong Javascript
Từ các phiên bản ES5 trở về trước, Javascript không có khái niệm class, và do vậy mà nó không thể thực hiện việc kế thừa để mở rộng ứng dụng như các ngôn ngữ OOP khác. Tuy nhiên, prototype giúp chúng ta thực hiện kế thừa theo một cách gần tương tự như thế: Javascript thực hiện kế thừa theo cơ chế prototype-based.
Nói ngắn gọn, để thực hiện kế thừa trong Js, bạn cần tạo 1 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 khởi tạo này. Các instance tạo ra bởi hàm khởi tạo này sẽ chứa các thuộc tính và phương thức được định nghĩa ở trên. Đoạn code sau minh hoạ cho điều này:
//Tạo ra 1 hàm khởi tạo cơ sở (tựa như lớp cơ sở)
function Animal(_age){
this.age = _age;
}
//Có thể thêm thuộc tính 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 ra 1 hàm khởi tạo con (sẽ dùng để kế thừa lớp cơ sở)
function Bird(_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 Bird
Bird.prototype = new Animal(5);
Bird.prototype.showColor = function(){
console.log( this.color );
};
//Kiểm tra sự kế thừa
var eagle = new Bird('red');
eagle.age = 5;
eagle.showAge(); //5
eagle.showColor(); //red
Ở ví dụ trên, đối tượng eagle sử dụng được hàm showAge() của Animal prototype bởi vì ta đã gán hàm khởi tạo của Animal vào prototype của Bird.
Đây chính là cơ chế kế thừa trong Javascript, đối tượng eagle đã kế thừa những gì có trong prototype của nó (là Bird.prototype), và nó cũng kế thừa luôn những gì có trong thuộc tính prototype của Bird (chính là Animal.prototype).
Đặc tính prototype của object: truy cập vào các thuộc tính của đối tượng
Prototype rất quan trọng trong việc giúp ta truy cập tới các thuộc tính và phương thức của đối tượng. Đặc tính prototype của đối tượng (hay còn gọi là prototype object) là một “object cha” nơi chứa các thuộc tính và phương thức được kế thừa. Vì thế, khi ta gọi tới một thuộc tính của đối tượng (vd: eagle.age), ban đầu Js sẽ tìm trong thuộc tính riêng của đối tượng, nếu không tìm thấy, nó sẽ tiếp tục tìm trong prototype của đối tượng, và lặp lại tiếp với prototype của đối tượng prototype, … Quá trình lặp lại này được gọi là chuỗi prototype trong Javascript. Chính điều này + thuộc tính prototype của function tạo nên cơ chế kế thừa prototype-based cho Javascript.