# Kế thừa Prototype ngang và dọc

![](/files/-MGnCcWFD8Cwe7SRzsZ_)

Prototype trong JS được coi là khái niệm trọng tâm. Cái thằng JS nó khác người nên nếp áp dụng tư duy của các ngôn ngữ hướng đối tượng class-based như PHP hay Java thì hỏng ngay.

Tiêu đề Prototype ngang và dọc do mình lựa chọn để mô hình hóa cách JS xử lý các hình thức kế thừa. Nếu làm ăn với ES6 thì JS có class, nhưng vấn đề là thằng JS này để lại quá nhiều “di sản” hổ lốn.

Cảm nhận của mình là cách mà JS thiết lập cơ chế kế thừa thực sự mạnh mẽ và linh hoạt, cho dù khó nắm bắt. Bài hơi dài, 1700 chữ.

**Kế thừa dọc**

Kế thừa dọc hiểu là kế thừa theo cấp độ cao thấp từ trên xuống dưới. Hầu như tất cả mọi object trong JavaScript đều là một instance của Object. Object đứng đầu chuỗi prototype chain. Đầu tiên, ta thử kiểm tra thứ tự prototype theo chiều dọc:

| 1234567 | `//Cách 1:` `__proto__` `//Cách 2:` `Object.getPrototypeOf(x)` |
| ------- | -------------------------------------------------------------- |

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-07-16.25.45.png)

Chú ý: ở cách 2 trên, thực ra nên viết là Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(y))) nếu như truy xuất 3 cấp prototype của y.

Trên đây là cách kiểm tra prototype cấp trên của mỗi object. Trong thực tế, chúng ta sẽ khai báo như sau. Chú ý đoạn **Vuong.prototype.b = 10000;**

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-06-04.16.40.png)

Đây là một ví dụ khác:

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-06-04.39.08.png)

Chú ý, nó kế thừa **TỪ THẤP LÊN CAO NHƯNG RETURN NGAY KHI GIÁ TRỊ LÀ KHÔNG NULL.** Do đó, các method hoặc property ở cấp cao hơn không được dùng gọi là **“Property Shadowing”** Vì method trong object cũng là property, nên nó cũng được kế thừa theo kiểu này, gọi là **“Method Overriding”.**<br>

Cùng xem hình minh họa sau về kế thừa prototype dọc trong Js.

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-07-23.36.21-1024x585.png)

**Chú ý 1:** Nếu object tạo bằng let x = new Function; (chú ý Function có “F” viết hoa), x vẫn có thể là một constructor.

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-08-01.26.38.png)

**Chú ý 2:** khi khao báo object dạng let x = {} hay let y = new Object() thì cả hai object dạng này **không phải là Constructor**, và do đó chúng không thể sinh ra object con được như khi khao báo Function hay Class. Xem minh họa:

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-07-23.33.12.png)

Như vậy, khi khai báo let x = {} hoặc let y = new Object thì constructor, tức prototype cha trong chuỗi kế thừa prototype dọc, theo thứ bậc của x chính là Object.prototype luôn. Trong trường hợp này, object nào muốn kế thừa các object này thì sẽ kế thừa ngang thông qua Object.create() hoặc \_\_proto\_\_ sẽ bàn ở dưới.

**Các trường hợp kế thừa dọc**

Ta sẽ thử với prototype chain của object tạo bằng {}, array hay hàm.

| 0102030405060708091011121314151617181920 | `var` `o = {a: 1};` `// Object o sẽ có prototype là Object.prototype. Người ta hay dùng [[Prototype]] để gọi tên.// Bản thân o không có thuộc tính 'hasOwnProperty'// hasOwnProperty là một thuộc tính của Object.prototype.// do đó o sẽ kế thừa hasOwnProperty từ Object.prototype// Object.prototype có null là prototype.// Vậy cấp bậc kế thừa là o \| Object.prototype \| null` `var` `b = ['hello',` `'js.edu.vn',` `'Vượng'];` `// Arrays kế thừa từ Array.prototype gồm các phương thức như indexOf, forEach, vân vân...// Chuỗi prototype là b (cấp 1) \| Array.prototype (cấp 2) \| Object.prototype (cấp 3) \| null` `function` `f() {return` `2;}` `// Hàm kế thừa từ Function.prototype với một số phương thức như call, bind...// Chuỗi prototype là f \| Function.prototype \| Object.prototype \| null` |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

với Constructor

| 0102030405060708091011121314 | `function` `Graph() {this.vertices = [];this.edges = [];}` `Graph.prototype = {addVertex:` `function(v) {this.vertices.push(v);}};` `var` `g =` `new` `Graph();// g là một object với thuộc tính 'vertices' và 'edges'.// g.[[Prototype]] là giá trị của Graph.prototype khi new Graph() được gọi.` |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Với Object.create (Chú ý, đây được xem là kế thừa ngang giữa 2 object trong bài viết này, nó cũng có thể coi là dọc thì giữa các object cũng phân cấp).

| 0102030405060708091011121314 | `var` `a = {a: 1};// a \| Object.prototype \| null` `var` `b = Object.create(a);// b \| a \| Object.prototype \| nullconsole.log(b.a);` `// 1 (inherited)` `var` `c = Object.create(b);// c \| b \| a \| Object.prototype \| null` `var` `d = Object.create(null);// d \| nullconsole.log(d.hasOwnProperty);// undefined, because d doesn't inherit from Object.prototype` |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Với class. Sẽ bàn thêm về kế thừa class sau.

| 0102030405060708091011121314151617181920212223 | `'use strict';` `class` `Polygon {constructor(height, width) {this.height = height;this.width = width;}}` `class` `Square` `extends` `Polygon {constructor(sideLength) {super(sideLength, sideLength);}get area() {return` `this.height *` `this.width;}set sideLength(newLength) {this.height = newLength;this.width = newLength;}}` `var` `square =` `new` `Square(2);` |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Trong Js, bản thân 1 hàm (1 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 mang giá trị là 1 object. Object này chứa các property, method mà ta định nghĩa cho prototye của function đó.

Khi sử dụng function đó như là 1 constructor cho object, thì prototype của object đó sẽ là 1 property có tên là **proto**, **proto** là 1 con trỏ, trỏ đến object prototye của function constructor.

**Kế thừa ngang giữa 2 function**<br>

Trường hợp 1: Thuộc tính giữa 2 function

Dùng call()

Trường hợp 2: Phương thức giữa 2 function.

Cách 1:

F2.prototype = Object.create(F1.prototype);

Cách 2:

F2.prototype = new F1;

Quan sát ví dụ:

| 010203040506070809101112131415161718192021222324 | `function` `Animal(age) {this.age = age;}` `Animal.prototype.showAge =` `function() {console.log(` `this.age );};` `//Tạo ra 1 hàm khởi tạo con (sẽ dùng để kế từ hàm cơ sở)function` `Cat(color) {this.color = color;}` `//Do prototype của 1 function là 1 object, nên ta có thể dùng cách khởi tạo 1 object trong JS để gán giá trị cho nóCat.prototype =` `new` `Animal();Cat.prototype.showColor =` `function(){console.log(` `this.color );};` `//Kiểm tra sự kế thừavar` `kitty =` `new` `Cat('pink');kitty.age = 5;kitty.showAge();` `// Mặc dù kitty không có hàm showAge(), nhưng giống như ví dụ ở trên, JS sẽ tìm kiếm showAge trong prototype của nókitty.showColor();` `// Tương tự với hàm showAge` |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

Ví dụ về kế thừa prototype ngang theo function constructor. Liên quan đến hàm call(), sẽ có bài chi tiết sau.

| 01020304050607080910111213141516171819202122232425262728293031323334353637 | `// Initialize constructor functionsfunction` `Hero(name, level) {this.name = name;this.level = level;}` `function` `Warrior(name, level, weapon) {Hero.call(this, name, level);` `this.weapon = weapon;}` `function` `Healer(name, level, spell) {Hero.call(this, name, level);` `this.spell = spell;}` `// Link prototypes and add prototype methodsWarrior.prototype = Object.create(Hero.prototype);Healer.prototype = Object.create(Hero.prototype);` `Hero.prototype.greet =` `function` `() {return` `` `${this.name} says hello.`;} `` `Warrior.prototype.attack =` `function` `() {return` `` `${this.name} attacks `` `with` ``the ${this.weapon}.`;}`` `Healer.prototype.heal =` `function` `() {return` `` `${this.name} casts ${this.spell}.`;} `` `// Initialize individual character instancesconst hero1 =` `new` `Warrior('Bjorn', 1,` `'axe');const hero2 =` `new` `Healer('Kanin', 1,` `'cure');` |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

**Kế thừa ngang giữa 2 object**

Có 2 cách: \_\_proto\_\_ và Object.create. Quan sát ví dụ:

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-08-02.46.30.png)

```
Tình cờ tìm được minh họa sau từ trang mollypages:

Còn đây là một minh họa khác trên StackOverFlow, minh hoạt cho code này
```

| 01020304050607080910111213141516 | `Object.O1='';Object.prototype.Op1='';` `Function.F1 =` `'';Function.prototype.Fp1 =` `'';` `Cat =` `function(){};Cat.C1 =` `'';Cat.prototype.Cp1 =` `'';` `mycat =` `new` `Cat();o = {};` `// EDITED: using console.dir now instead of console.logconsole.dir(mycat);console.dir(o);` |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

```
 
Một minh họa khác cho code sau:
```

| 010203040506070809101112 | `function` `Gadget(name, color){   this.name = name;   this.color = color;}` `Gadget.prototype.rating = 3` `var` `newtoy =` `new` `Gadget("webcam",` `"black")` `newtoy.constructor.prototype.constructor.prototype.constructor.prototype` `//trả về 3newtoy.__proto__.__proto__.__proto__// trả về null` |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

```
Code trên có sự khác nhau giữa __proto__ và constructor.prototype
```

```
Kết luận đáng chú ý:
```

| 1 | `The most surprising thing for me was discovering that Object.__proto__ points to Function.prototype, instead of Object.prototype.` |
| - | ----------------------------------------------------------------------------------------------------------------------------------- |

**Về sự khác biệt giữa \_\_proto\_\_ và prototype**

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-08-04.20.48.png)

prototype chỉ có trên constructor, ví dụ Object, Function, Class…

\_\_proto\_\_ chỉ có trên object không phải là constructor.

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-08-15.05.20-1.png)

chú ý quan trọng trong code trên:

| 1 | `vuong.__proto__ == JS.prototype;` `// true` |
| - | -------------------------------------------- |

**Tóm tắt mối quan hệ giữa prototype và \_\_proto\_\_:**

**Một:** Thuộc tính prototype được tạo mỗi khi một function được tạo ra. Ví dụ hàm JS trên sẽ có một thuộc tính sinh ra khi tạo là JS.prototype.

Bản thân thuộc tính prototype này lại là một object nên có thể khai báo thêm method hoặc property mới cho hàm JS. Tất cả các instance object tạo từ JS thông qua từ khóa “new” đều mang theo các prototype khai báo từ JS.prototype

**Hai:** Tất cả các instance object tạo từ new JS() đều có một thuộc tính \_\_proto\_\_, thuộc tính này trỏ tới JS.prototype. Ta có thể khai báo method hoặc property mới bằng 2 cách:

Cách 1:

| 123 | `JS.prototype.x = something;` `// thêm thuộc tính cho cả Constructor JS và tất cả các instance tạo từ JS` `JS.prototype.y =` `function(){}` `// thêm phương thức cho cả Constructor JS và tất cả các instance tạo từ JS` |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

Cách 2:

Giả sử ta có let vuong = new JS().

| 123 | `vuong.__proto__.x = something;` `// thêm thuộc tính cho cả Constructor JS và tất cả các instance tạo từ JS` `vuong.__proto__.y =` `function(){}` `// thêm phương thức cho cả Constructor JS và tất cả các instance tạo từ JS` |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

**Ba:** Bản thân mỗi hàm lại là một object nên chúng cũng có \_\_proto\_\_, tức JS.\_\_proto\_\_, trỏ tới cấp cao hơn là Function.prototype. Function.\_\_proto\_\_ lại trỏ tới Object.prototype và Object.\_\_proto\_\_ trỏ tới null. Null không có prototype. Xem ảnh:

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-08-22.43.37.png)

Có một điều đáng chú ý là tuy js.\_\_proto\_\_ === Function.prototype //true và  js.constructor là Function() nhưng:

| 0102030405060708091011 | `x` `instanceof` `js//true` `y` `instanceof` `js//true` `x` `instanceof` `Function//false` `y` `instanceof` `Function//false` `x` `instanceof` `Object//true` `y` `instanceof` `Object//true` |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |

Xem ảnh:

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-08-22.59.44.png)

**Ví dụ về Function constructor và object instance**

Để giải thích kĩ hơn, ta đọc ví dụ sau:

Ta tạo hàm sau:

| 123 | `function` `Vuong (name) {this.name = name;}` |
| --- | --------------------------------------------- |

Khi chạy, ta thêm một thuộc tính có tên “prototype” vào a, thuộc tính này cũng là object, và có 2 properties: constructor và \_\_proto\_\_

Khi chúng ta chạy code a.prototype. Kết quả là

| 12 | `constructor: Vuong` `// constructor là chính function "a" definition__proto__: Object` |
| -- | --------------------------------------------------------------------------------------- |

Trong trường hợp này constructor chính là function và \_\_proto\_\_ trỏ tới Object. Giờ ta sẽ xem chuyện gì xảy ra khi một instance được khởi tạo với từ khóa “new”.

| 1 | `let` `vidu =` `new` `Vuong ('JavaScript');` |
| - | -------------------------------------------- |

Khi JavaScript chạy, có 4 thứ xảy ra:

Bước 1) Nó tạo một object mới, một object rỗng  // {}\
Bước 2) Nó tạo một \_\_proto\_\_ trên vidu và chỉ tới  Vuong.prototype, do đó vidu.\_\_proto\_\_ === Vuong.prototype //true\
Bước 3) Thực thi Vuong.prototype.constructor (định nghĩa của hàm Vuong), với một object tạo ở bước 1 và trong ngữ cảnh này (this), thuộc tính sẽ được pass sang thành “JavaScript”.\
Bước 4) Nó trả lại object mới tạo ở bước một và do đó vidu sẽ được gán với object mới.

Giờ nếu ta gán Vuong.prototype.test= “Học JS tại js.edu.vn”  và gọi vidu.test, kết quả sẽ là “Học JS tại js.edu.vn”. JavaScript sẽ tìm thuộc tính test trên vidu, nếu nó không tìm thấy, nó sẽ tìm trên vidu.\_\_proto\_\_ (cái này lại trỏ tới Vuong.prototype ở bước 2) và trả về “Học JS tại js.edu.vn”; Xem minh họa:

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-09-00.32.09.png)

**Tất cả đều từ Function**

Có một điều lạ là, trong khi: Object.\_\_proto\_\_ === Function.protype//true, nhưng khi viết Function.prototype.x/y/z thì các object kế thừa lại không được mà phải viết Object.prototype.x/y/z.

Quan sát ảnh:

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-08-23.59.59.png)

Còn phía dưới là câu chuyện của Object và Function!!!

![](http://js.edu.vn/wp-content/uploads/2019/01/Screenshot-2019-01-08-04.31.01.png)

Các vấn đề cần bàn thêm ở các bài sau:

1. Kế thừa giữa các function
2. Kế thừa giữa các class.

Các vấn đề trọng tâm trong bài trên:

1. Kế thừa dọc giữa Function và object
2. Kế thừa ngang giữa 2 function
3. Kế thừa ngang giữa 2 object (mà không phải là instance tạo từ với “new” Functionname/Classname


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://javascriptuse.gitbook.io/advanced/ke-thua-prototype-ngang-va-doc.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
