Definition

Mọi thứ trong JS đều là object. Mỗi object luôn có một thuộc tính __proto__ trỏ đến một đối tượng khác, thuộc tính đó có tên là prototype. Đối tượng prototype được trỏ đến đó sẽ lại có một thuộc tính __proto__ của riêng nó, hình thành nên prototype chain. Prototype có giá trị __proto__ = null là object cuối cùng có trong chuỗi các prototype.

Xét ví dụ sau:

const obj = {}
 
console.log(obj.__proto__) // Object {}
console.log(obj.__proto__.__proto__) // null
 
// Prototype chain: obj --> Object --> null

Có thể thấy, đối tượng obj có prototype là Object. Tương tự, đối tượng Object cũng có prototype nhưng giá trị của prototype là null nên nó là phần tử cuối cùng trong prototype chain.

Xét một ví dụ khác:

function Cat() {}
 
const cat = new Cat()
 
console.log(cat.__proto__) // Cat {}
console.log(cat.__proto__.__proto__) // Object {}
 
// Prototype chain: cat ---> Cat --> Object ---> null

Property Inheritance

Giả sử cho một object như bên dưới:

const obj = {
  a: 1,
  b: 2,
  __proto__: {
    b: 3,
    c: 4,
  },
}

Ta truy cập vào thuộc tính a của đối tượng obj sẽ thu được giá trị là 1:

console.log(obj.a) // 1

Tương tự với thuộc tính b:

console.log(obj.b) // 2

Tại sao không phải là 3 mà là 2? Lý do là vì bản thân đối tượng obj đã có thuộc tính b nên sẽ không cần truy cập vào prototype để lấy ra thuộc tính. Cơ chế này có tên là property shadowing.

Bản thân đối tượng obj không tồn tại thuộc tính c, do đó thuộc tính c có trong prototype sẽ được truy xuất.

console.log(obj.c) // 4

Cuối cùng, nếu ta truy cập đến một thuộc tính không có ở trong obj và cả ở prototype của nó, kết quả thu được sẽ là undefined.

console.log(obj.d) // undefined

Có thể nói, việc một đối tượng truy cập đến thuộc tính của prototype giống như việc một lớp con truy cập đến thuộc tính của lớp cha có ở các ngôn ngữ lập trình Object Oriented Programming khác. Nói cách khác, đây chính là sự kế thừa (Inheritance), mà cụ thể là kế thừa các thuộc tính

Method Inheritance

Một phương thức trong JS được định nghĩa là một hàm đóng vai trò là một thuộc tính. Do là một thuộc tính, các phương thức cũng có thể có tính kế thừa. Ngoài ra, ở trong JS ta cũng có thể sử dụng method overriding tương tự các ngôn ngữ khác nhờ cơ chế property shadowing.

Khi một phương thức kế thừa được thực thi, từ khóa this sẽ trỏ đến đối tượng kế thừa.

Giả sử cho đối tượng parent dưới đây:

const parent = {
  value: 1,
 
  method() {
    return this.value + 1
  },
}
 
console.log(parent.method()) // 2

Nếu có một đối tượng child kế thừa, phương thức method sẽ cho ra kết quả như dưới đây:

const child = {
  __proto__: parent,
}
 
console.log(child.method()) // 2

Bản thân một phương thức cũng là một thuộc tính, khi không tìm thấy phương thức method ở đối tượng child, phương thức method của đối tượng parent sẽ được sử dụng. Từ khóa this hiện tại trỏ vào đối tượng child thay vì đối tượng parent.

Tuy nhiên, do không có thuộc tính valuechild, thuộc tính value của parent sẽ được sử dụng. Đó là lý do mà giá trị trả về của method2.

Nếu chúng ta thêm vào thuộc tính value ở đối tượng child, giá trị trả về của phương thức method sẽ khác đi:

child.value = 2
 
console.log(child.method()) // 3

Lý do là vì: từ khóa this hiện tại đã trỏ đến đối tượng child và vì đối tượng child cũng đã có thuộc tính value của riêng nó, do đó, giá trị value của đối tượng child sẽ được sử dụng.

Resources