Understand JavaScript - Prototype



- Tư tưởng chung của việc kế thừa trong JS dựa vào kế thừa nguyên mẫu (prototype), đối tượng cung cấp sự kế thừa dựa trên properties(tài sản) prototype của nó và một đối tượng khác có thể sử dụng các thuộc tính và phương thức trên prototype của object mà nó kế thừa.

- Điều đặc biệt rằng mỗi prototype bản thân nó là một object nên nó cũng có thể có protype của chính nó điều này tạo nên một định nghĩa gọi là prototype chain, điều này giải thích được lý do tại sao những đối tượng có thể truy cập được vào các phương thức hay thuộc tính không thuộc về nó.


về cơ bản thì bạn cần biết __proto__ là một object được mặc định tạo ra mỗi khi bạn khởi tạo một object, nó trỏ về prototype của cha object vừa được tạo ra (tức là __proto__ trỏ về prototype của object mà object bạn tạo ra kế thừa.).
=> Việc các đối tượng có thể chia sẻ prototype với các đối tượng khác giúp bạn có những đối tượng mà các thuộc tính hay phương thức của chúng cùng trỏ về một vị trí trong bộ nhớ.

Prototype là gì ?

Điều thứ nhất: bạn cần hiểu  Prototype là object mà object khác có thể kế thừa các thuộc tính và phương thức, định nghĩa này chưa đầy đủ nhưng bạn cần phải làm rõ điều này đầu tiên.
Điều thứ 2: bạn cần làm rõ, mọi thứ trong JS đều là primitive hoặc object, mỗi function đều là một object suy ra nó cũng sẽ có những thuộc tính và phương thức của riêng nó.
Điều đặc biệt ở function là khi tạo ra function js sẽ tự động tạo một object rỗng là prototype điều này dẫn đến hệ quả là bạn hoàn toàn có thể thêm các thuộc tính hay phương thức vào prototype.
Điều thứ 3 : Khi bạn sử dụng hàm để làm hàm tạo (constructor function) thì các object được tạo ra từ constructor function này đều có thể sử dụng các thuộc tính và phương thức nằm trong object prototype của contructor function tạo nên nó => Đây chính là tư tưởng cơ bản của kế thừa trong JS, kế thừa dựa trên nguyên mẫu.
Điều thứ 4 : Bất kì đối tượng nào được tạo ra nó mặc định sẽ được kế thừa từ Object (function) đây là một contructor function của javascript native do đó bạn có thể thấy mỗi đối tượng tạo ra đều có chung một số thuộc tính nhất định. 

Điều thứ 5 :  Bạn cần phân biệt thật rõ ràng, object __proto__ trỏ đến đối tượng mà object kế thừa và prototype của đối tượng (object mà đối tượng kế thừa, prototype chỉ có trên function)..nghe có vẻ khó hiểu nhưng khi đọc đến phần phân biệt _proto và prototype bạn sẽ hiểu.

VD: Khi objectA kế thừa objectB thì objectA sẽ có quyền truy cập vào các thuộc tính và phương thức của objectB thông qua chuỗi nguyên mẫu.(prototype chain).
=> Trong JS chúng ta sẽ tiếp cận với một tư tưởng kế thừa mới đó là Prototype Inheritance , nó cực kì linh hoạt đơn giản và cũng rất mạnh mẽ , nhưng trước hết bạn phải biết Prototype là gì đã .

Giải sử chúng ta có một Object trong bộ nhớ , gọi nó là obj .
Như mình đã giới thiệu ở bài trước đối tượng có thể có các properties và method nó được gọi chung là property (tài sản)

Ví dụ Obj có property là Prop1 , mình sử dụng dấu chấm để giúp Obj1tham chiếu đến Prop1


Hãy nghĩ đơn giản rằng proptotype là một object cho phép một hay nhiều object khác truy cập thuộc tính hay phương thức của nó, định nghĩa này chưa đầy đủ nhưng đây là thông tin đầu tiên bạn cần ghi nhớ.
Nhớ lại rằng trong JS mọi thứ đều là object trừ (null và undefined) vậy function cũng không ngoại lệ, một function luôn có một thuộc tính là một object gọi là prototype. Theo mặc định thì prototype luôn là một object rỗng, tất nhiên như một object thì bạn có thể thêm thuộc tính và phương thức cho nó.

Khi bạn sử dụng fucntion để làm một hàm tạo (constructor fucntion) với 'new' keyword thì mọi object được tạo ra từ hàm này có quyền truy cập các thuộc tính và phương thức của prototype constructor function đó.
=> Từ đây suất hiện tư tưởng kế thừa trong JS, kế thừa trong JS đơn giản là một object có quyền truy cập vào thuộc tính và phương thức trong prototype của object khác thông qua prototype chain.


Trong JS mọi đối tượng khi sinh ra đều có sẵn một một property là object , đây chính là proto , chúng ta sẽ gọi tắt nó là proto, bạn cần phân biệt proto vs prototype , proto giống như một con trỏ giúp object tìm đến prototype của object mà nó kế thừa.


Bây giờ bạn có thể thấy rằng :
=> Rõ ràng prototype là một object , vậy mình có thể thêm bất kì một property nào mình muốn cho thằng prototype này , mình ví dụ như là Prop2 chẳng hạn đi.
=> Vậy khi mình sử dụng toán tử dấu (.) với đối tượng Obj , obj.prop2 , toán tử này sẽ đi tìm prop2 trong các property mà obj tham chiếu đến nhưng rõ ràng nó không tìm thấy như vậy nó sẽ đi tìm tiếp trong prototype và nó tìm thấy prop2 trong các property mà proto tham chiếu đến và nó sẽ trả về nó.



Như vậy nhìn có vẻ như Prop2 nằm trong các property của obj nhưng không phải .

Proto bản chất nó là một object nên bản thân nó cũng có prototype của nó.
nó như một chuỗi kéo dài bất tận , khi bạn gọi thuộc tính của obj nó sẽ đi theo chuỗi này để tìm. trong JS cái này được gọi là prototype chain , prototype chain giúp chúng ta có quyền truy cập vào một property khác với điều kiện nó nằm cùng trong một chuỗi các đối tượng.



Sự tiện lợi trong JS đó là bạn không cần phải viết obj.proto.proto.prop3 , JS không cần bạn viết như vậy  , nếu nó không tìm thấy property trong obj nó sẽ tự động đi theo prototype chain để tìm .

Một điều thú vị nữa rằng , giả sử mình có một object mới gọi là obj2 chẳng hạn đi , JS nó cho phép bạn trỏ obj2 đến cùng proto với obj => Các đối tượng có thể chia sẻ tất cả cùng một prototype
=> Suy ra rằng bạn có thể gọi obj2.prop2 và nó trả về một kết quả y hệt với obj.prop2 vì đó là cũng một vị trí trong bộ nhớ.
=> Bạn nhớ rằng rõ ràng prop2 không nằm trên obj2 và obj , đơn giản là JSE theo chuỗi Prototype Chain để tìm kiếm và nó cùng tìm đến đúng một vùng nhớ chứa prop2 mà thôi.



Cùng đến với một ví dụ :


Hãy nhớ rằng bất kì đối tượng nào đều có một tham chiếu đến một object gọi là prototype , trên prototype có mặc định các hàm mà JS thiết lập nhưng bạn cũng có thể thiết lập các property và gán lên prototype.


Mình viết ở dòng 14 như thế kia có nghĩa rằng John bây giờ thừa hưởng (kế thừa) từ object person.
=> Như vậy nếu mình cố gắng truy cập bất kì một property nào mà nó không có trên John thì JSE sẽ tìm property này trong person.

Kết quả :


Chú ý rằng khi hàm getFullName được gọi EC mới được tạo ra và biến This trong bối cảnh này nó sẽ trỏ đến John vì biến this trỏ đến Object gọi hàm chứa nó => biến this trỏ vào John .

Mình thử log ra firstname của john => kết quả cho ta thấy JSE sẽ tìm trong John đầu tiên nếu không tìm thấy nó sẽ đi xuống theo prototype chain còn nếu có thì nó sẽ dừng lại.

Vậy một Object có bao nhiêu proto ???

Trước khi mình set proto cho John.





Bạn có thể thấy rằng sự khác nhau , trước khi mình thêm một prototype và prototype chain của John nó mặc định đã có một proto sẵn do JS tạo ra chứa các hàm (method) JS cung cấp mặc định để dễ dàng thao tác hơn với object.

Mọi thứ trong JS đều là object hoặc Primitive.



Nhận xét

Bài đăng phổ biến