Đọc và hiểu về prototype trong Javascript trong 10 phút
Trong bài này chúng ta sẽ tìm hiểu prototype trong Javascript, đây là một phần kiến thức quan trọng, nó giúp các bạn làm việc với các object và class hiệu quả hơn.
Qua bài này bạn sẽ hiểu được khái niệm prototype là gì? Cách sử dụng prototype hiệu quả trong quá trình làm việc với Javascript. Chỉ mất 5 phút là bạn sẽ hiểu được thuật ngữ này.
Lưu ý: Bạn cần đọc bài viết prototype trong javascript là gì để hiểu về nó rồi mới xem bài này nhé.
1. Hiểu về prototype trong javascript
Trong Javascript, prototype được đề cập đến một hệ thống dữ liệu của đối tượng, qua đó cho phép bạn xác định các thuộc tính và phương thức trên các đối tượng có thể truy cập được qua các phiên bản của đối tượng (instance).
Bài viết này được đăng tại [free tuts .net]
Prototype có liên quan chặt chẽ với lập trình hướng đối tượng, nên nếu bạn đã từng học qua các ngôn ngữ như C++ hay Java thì sẽ là một lợi thế rất lớn. Còn nếu bạn chưa hiểu thì mình xin giải thích một chút như sau.
Lập trình hướng đối tượng là kỹ thuật đưa các đối tượng trong thực tế vào trong chương trình máy tính thông qua những dòng code. Tùy vào các chương trình mà có các đối tượng khác nhau. Ví dụ bạn đang xây dựng chương trình quản lý sinh viên thì chúng ta có các đối tượng như: Sinh viên, Khoa, Lớp, Trường, Giáo Viên ...
Quay trở lại vấn đề chính là javascript prototype nhé.
Giả sử mình có các mảng như sau:
const array = ['one', 'two', 'three']; // Hoặc sử dụng lệnh new Array() const array = new Array('one', 'two', 'three');
Bây giờ mình sẽ sử dụng lệnh console.log để xem biến array có những gì nhé.
console.log(array);
Như bạn thấy, ngoài các giá trị mà mình đã gán vào thì không có một phương thức hay thuộc tính nào khác, nhưng ta vẫn có thể sử dụng các phương thức như concat
, slice
, filter
, và map
trên đối tượng array này.
Tại sao lại như vậy?
Bởi vì các phương thức trên nằm trong phần prototype của array. Bạn có thể xem nó bằng cách mở rộng mục __proto__ mà mình đã chụp hình ở trên.
Trên là danh sách các thuộc tính và phương thức có sẵn trong prototype của Array. Vì vậy, mỗi khi bạn tạo một array mới thì nó đều được tích hợp sẵn, và array mới này ta gọi là một thể hiện (instance) của đối tượng Array.
Khi bạn gọi array.map thì javascript sẽ tìm phương thức map trong danh sách thuộc tính của nó. Nếu không tồn tại thì nó sẽ dò tìm trong phần prototype.
Vì vậy, định nghĩa chính xác của prototype đó là: Prototype là một đối tượng chứa các thuộc tính và phương thức, qua đó các instance sẽ tìm kiếm trong trường hợp thuộc tính cần tìm không tồn tại trong instance.
2. Sơ đồ hoạt động của prototype trong javascript
Trong javascript, khi bạn truy cập vào một thuộc tính / phương thức thì nó sẽ hoạt động theo các bước như sau:
Bước 1: JavaScript sẽ kiểm tra xem thuộc tính có sẵn bên trong đối tượng hay không. Nếu có, JavaScript sử dụng thuộc tính này và kết thúc, nếu không có thì nhảy qua bước 2.
Bước 2: Javascript sẽ kiểm tra trong prototype của đối tượng đó có hay không? Nếu có thì sử dụng và kết thúc, không có thì nhảy qua bước 3.
Bước 3: Nếu đến bước 2 mà vẫn không tìm thấy thì sẽ có hai trường hợp xảy ra:
- Trả về undefined nếu bạn đang truy cập thuộc tính.
- Thông báo lỗi nếu bạn truy cập vào phương thức.
Bây giờ ta sẽ làm một ví dụ để mô phỏng cho sơ đồ hoạt động của prototype trong javasript nhé.
Mình sẽ sử dụng Number Constructor để tạo ra một con số, sau đó in ra xem nó có những gì nhé.
let number = new Number(12); console.log(number);
Như bạn thấy trong hình, đối tượng number không tồn tại một thuộc tính nào cả, nhưng prototype của nó lại có rất nhiều phải không nào?
Bây giờ mình sẽ sử dụng phương thức toString để chuyển đổi number này thành một chuỗi.
let number = new Number(12); let string = number.toString(); console.log(typeof string); // String
Vì phương thức toString
không tồn tại trong number object
nên nó sẽ tìm trong prototype
, và trong prototype
có phương thức toString
nên nó sử dụng luôn, kết quả là ta đã chuyển được number sang chuỗi string.
Bây giờ mình sẽ định nghĩa một phương thức toString vào đối tượng bằng cách sau:
let number = new Number(12); number.toString = function(){ console.log("Bạn đang gọi đến phương thức number của object"); }; console.log(number);
Đoạn code này sẽ in ra kết quả như sau:
Như bạn thấy, ta có cả hai phương thức toString, một là của đối tượng, một là nằm trong prototype của đối tượng. Nếu như xét về độ ưu tiên khi gọi đến phương thức toString thì javascript sẽ sử dụng cái đầu tiên. Ta hãy thử xem ngay bây giờ nhé.
let number = new Number(12); number.toString = function(){ console.log("Bạn đang gọi đến phương thức number của object"); }; // Kết quả: Bạn đang gọi đến phương thức number của object var string = number.toString(); console.log(typeof string); // Undefined
Kết quả đúng như ta mong đợi.
Lưu ý: Đây là quy tắc quan trọng và mọi đối tượng đều áp dụng quy tắc này nhé các bạn.
3. Prototype chain trong Javascript
Prototype chain là một thuật ngữ về quá trình truy xuất đến các thuộc tính giữa các lớp kế thừa với nhau.
Nếu bạn chưa biết khái niệm kế thừa thì xem trong bài viết extends trong javascript nhé.
Prototype của lớp không có kế thừa.
Giả sử mình có lớp Developer như sau:
class Developer { constructor(name){ this.name = name; } code (thing) { console.log(`${this.firstName} coded ${thing}`) } }
Bây giờ mình tạo mới một instance của đối tượng Developer, và sau đó console.log xem nó có gì.
const cuong = new Developer('Cuong') console.log(cuong);
Kết quả:
Giải thích:
- Instance cuong thuộc đối tượng Developer,
- Nó không có thuộc tính nào cả,
- Nó prototype và trong prototype này có một function code.
- Trong prototype lại có thêm một prototype khác, và đó là một object mặc định của javascript. Nếu lớp Developer có kế thừa từ lớp
A
thì object này sẽ được thay thế bằng lớpA
đó.
Prototype của lớp có kế thừa
Giả sử mình sẽ định nghĩa một lớp Human
, sau đó tạo thêm một lớp Developer
kế thừa từ lớp Human
.
- Lớp
Human
có phương thứcsayHello
. - Lớp
Developer
có phương thứccode
.
Đây là code cho lớp Human.
class Human { constructor(firstName, lastName) { this.firstName = firstName this.lastname = lastName } sayHello () { console.log(`Hi, I'm ${this.firstName}`) } }
Và đây là code cho lớp Developer.
class Developer extends Human { code (thing) { console.log(`${this.firstName} coded ${thing}`) } }
Vì lớp Developer
được kế thừa từ lớp Human
nên nó tồn tại hai phương thức sayHello
và code
.
const cuong = new Developer('Cuong', 'Nguyen') cuong.sayHello() // Hi, I'm Cuong cuong.code('website') // Cuong coded website
Thử console.log xem có gì trong đối tượng cuong
này nhé.
console.log(cuong);
Giải thích kết quả như sau: Instance cuong
sẽ có ..
- Đây là một đối tượng
Developer
, có hai thuộc tính làfirstName
vàlastName
. - Có prototype, trong prototype này có:
- Một hàm tên là
code
. - Một prototype khác trỏ tới lớp Human.
- Prototype này có hàm
sayHello
- Prototype này có hàm
- Một hàm tên là
Khi bạn gọi một thuộc tính thì nó sẽ tìm lần lượt theo thứ tự như trong hình sau:
Nó sẽ áp dụng quy tắc mà chúng ta đã học ở phần 2:
- Đầu tiên là tìm trong thuộc tính của đối tượng
- Tiếp theo tìm trong prototype của đối tượng
- Nếu trong prototype p1 có thêm một prototype (p2) khác nữa thì nó sẽ tiếp tục áp dụng quy tắc để tìm kiếm trên p2 này.
4. Có nên sử dụng prototype trong Javascript?
Mỗi đối tượng trong javascript được chia làm hai phần, thứ nhất là thuộc tính của đối tượng, thứ hai là prototype của đối tượng.
Khi bổ sung một thuộc tính cho đối tượng thì bạn có thể chọn một trong hai cách trên, cụ thể như sau:
let domain = new String("freetuts.net"); domain.show = function(){ // Code }; // Hoặc String.prototype.show = function(){ // code };
Tuy nhiên, mỗi cách lại có công dụng khác nhau, và tùy thuộc vào từng trường hợp mà bạn chọn cách cho phù hợp.
Trường hợp 1: Thêm trực tiếp vào đối tượng
Cách này thì thuộc tính thêm sử dụng được trong instance mà bạn đã thêm.
let domain_1 = new String("freetuts.net"); domain_1.show = function(){ console.log(this.valueOf()); }; // Kết quả: freetuts.net domain_1.show(); let domain_2 = new String("techtuts.net"); // Kết quả: Lỗi do domain_2 không tồn tại method show domain_2.show();
Trường hợp 2: Thêm vào prototype
Cách này thì thuộc tính thêm có thể sử dụng trong mọi instance.
String.prototype.show = function(){ console.log(this.valueOf()); }; let domain_1 = new String("freetuts.net"); domain_1.show(); // Kết quả: freetuts.net let domain_2 = new String("techtuts.net"); domain_2.show(); // Kết quả: techtuts.net
Lời kết: Có một số mẫu test đã chứng minh răng khi sử dụng prototype thì sẽ cho tốc độ nhanh hơn. Tuy nhiên, chỉ trường hợp bạn chạy trên 1 triệu toán tử thì mới ảnh hưởng. Vì vậy, việc áp dụng prototype hay không là không quan trọng.
Như vậy là chúng ta đã tìm hiểu xong phần prototype trong Javascript. Đây là kiến thức rất khó và quan trọng, nó giúp bạn hiểu làm việc với các đối tượng dễ dàng hơn. Nếu thấy bài viết hay thì hãy chia sẻ đến mọi người nhé.