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 --> nullCó 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 ---> nullProperty 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) // 1Tương tự với thuộc tính b:
console.log(obj.b) // 2Tạ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) // 4Cuố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) // undefinedCó 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()) // 2Nế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()) // 2Bả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 value ở child, thuộc tính value của parent sẽ được sử dụng. Đó là lý do mà giá trị trả về của method là 2.
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()) // 3Lý 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.