Cảm ơn bạn!
Phân biệt __proto__ vs prototype trong JavaScript
Như chúng ta đã biết trong JavaScript, các object sử dụng một cơ chế là prototype để kế thừa các thuộc tính (properties), methods của nhau. Các bạn đọc thêm: Tìm hiểu về Prototype trong JavaScript để hiểu rõ hơn nhé!
Trong bài viết này, chúng ta cùng phân biệt hai khái niệm __proto__ và prototype (property) trong prototype nhé.
__proto__ vs prototype JavaScript
Thuộc tính prototype
Trong JavaScript, tất cả function đều có một property (thuộc tính) có tên là prototype
, bạn có thể mở Console và xem thử nhé.
function sayHi() {}
console.log('regular function', sayHi.prototype);
const sayHiFromArrowFunction = () => {};
console.log('arrow function', sayHiFromArrowFunction.prototype);
Kết quả ở Console sẽ như hình bên dưới.
👉 Ở đoạn code trên, không quan trọng cách bạn khởi tạo hàm, mỗi function đều có một thuộc tính là prototype
, chỉ trừ một trường hợp là arrow function
sẽ không có thuộc tính prototype khi khởi tạo.
Sau khi thực thi đoạn code trên, chúng ta sẽ thấy được sayHi()
có một thuộc tính mặc định là prototype
là một object nhìn tương tự như sau:
console.log(sayHi.prototype);
👇
{
constructor: ƒ sayHi(),
[[Prototype]]: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
...
}
}
Như vậy, bản thân thuộc tính prototype
là một object, chúng ta có thể thêm vào đó các thuộc tính, ví dụ:
function sayHi() {}
sayHi.prototype.blogName = 'homiedev';
console.log(sayHi.prototype);
Kết quả chúng ta sẽ thấy thuộc tính blogName
ở trong sayHi.prototype
:
{
blogName: "homiedev",
constructor: ƒ sayHi(),
[[Prototype]]: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
...
}
}
Tiếp theo, chúng ta sẽ tạo một instance của sayHi()
, dễ hiểu hơn thì các bạn đang tạo ra một object dựa trên nguyên mẫu - prototype của sayHi()
. Để tạo instance thì các bạn sử dụng từ khóa new
như sau:
function sayHi() {}
sayHi.prototype.blogName = 'homiedev'; // thêm thuộc tính vào prototype
const obj = new sayHi(); // tạo một object
obj.url = 'https://homiedev.com'; // thêm một thuộc tính vào object
console.log(obj);
Khi tạo một instance bằng constructor (sử dụng từ khóa new
), instance này sẽ có thuộc tính với kí hiệu [[Prototype]]
, nó được tham chiếu đến object sayHi.prototype
, các bạn có thể nhìn kết quả dưới đây để dễ hình dung:
{
url: "https://homiedev.com",
[[Prototype]]: {
blogName: "homiedev",
constructor: ƒ sayHi(),
[[Prototype]]: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
}
Các bạn có thể thấy, [[Prototype]]
của obj
là sayHi.prototype
😃, khi nhìn vào [[Prototype]]
bạn có thể biết object này được tạo ra từ đâu.
Tiếp theo, khi chúng ta truy cập một thuộc tính của obj
, JavaScript sẽ tìm thử trong obj
xem có thuộc tính đó hay không. Nếu không tìm thấy, nó sẽ tìm thử trong obj.[[Prototype]]
hay sayHi.prototype
, nếu obj.[[Prototype]]
có thuộc tính cần tìm, thì nó sẽ được sử dụng.
Nếu không tìm thấy thuộc tính cần tìm trong obj.[[Prototype]]
, nó sẽ tiếp tục tìm kiếm ở obj.[[Prototype]].[[Prototype]]
, theo mặc định [[Prototype]]
trong thuộc tính prototype
của một function là Object.prototype
. Nếu không tìm thấy thuộc tính ở obj.[[Prototype]].[[Prototype]]
hay sayHi.prototype.[[Prototype]]
hay Object.prototype
thì JavaScript sẽ tìm tiếp trong chuỗi nguyên mẫu (prototype chain).
Lúc này, nó sẽ tìm kiếm ở obj.[[Prototype]].[[Prototype]].[[Prototype]]
hay Object.prototype.[[Prototype]]
. Tuy nhiên Object.prototype.[[Prototype]]
là null. Nên sau khi xem xét toàn bộ chuỗi nguyên mẫu (prototype chain) của [[Prototype]]
, JavaScript xác nhận rằng thuộc tính không tồn tại và kết quả là undefined.
Cùng thử truy xuất một vài thuộc tính và xem kết quả nhé.
function sayHi() {}
sayHi.prototype.blogName = 'homiedev'; // thêm thuộc tính vào prototype
const obj = new sayHi(); // tạo một object
obj.url = 'https://homiedev.com'; // thêm một thuộc tính vào object
console.log('url', obj.url); // 'https://homiedev.com'
console.log('blogName', obj.blogName); // 'homiedev'
console.log('duanhauditron', obj.duanhauditron); // undefined
Kết quả là url
tìm thấy ở trong obj
, đúng với giá trị đã gán. blogName
là thuộc tính tìm thấy trong sayHi.prototype
hay obj.[[Prototype]]
. Còn duanhauditron
có kết quả là undefined vì JavaScript đã tìm kiếm trong prototype chain nhưng không tìm thấy thuộc tính này.
__proto__
Trong quá trình tìm hiểu thuộc tính prototype
ở trên, các bạn có thể thấy mình đã nhắc khá nhiều về [[Prototype]]
. Đây là một thuộc tính ẩn mà tất cả các object trong JavaScript đều có, nó tham chiếu đến object của thuộc tính prototype
.
Vậy còn __proto__
là gì? Đây là một cách để các bạn có thể truy xuất và chỉnh sửa thuộc tính ẩn [[Prototype]]
. Cùng xem một ví dụ nhé.
function User () {
this.name = 'Minh';
}
// User có một thuộc tính là prototype
// prototype được tạo tự động khi chúng ta khai báo function User.
User.hasOwnProperty('prototype'); // kiểm tra thử => true
// Thêm một method vào User.prototype
User.prototype.myName = function () {
return 'My name is ' + this.name;
}
Nếu chúng ta tạo một object từ User
, object mới tạo sẽ có một liên kết đến prototype của User
chúng ta đã tìm hiểu ở trên. Các bạn có thể truy xuất bằng cách sử dụng __proto__
. Thuộc tính này nằm trong Object.prototype
, vì vậy khi một object dùng thuộc tính này, JavaScript sẽ tìm thấy thuộc tính __proto__
trong chuỗi nguyên mẫu (prototype chain) 😃.
Bạn có thể thấy thuộc tính này trong ví dụ ở đầu bài viết.
let u = new User();
// sử dụng u.[[Prototype]] sẽ không thành công
console.log(u.__proto__ === User.prototype); // true
console.log(u.__proto__.__proto__ === Object.prototype); // true
console.log(User.prototype.__proto__ === Object.prototype); // true
Các bạn không nên sử dụng __proto__
(Object.prototype.__proto__) vì tính năng này có thể sẽ bị xóa khỏi một số trình duyệt. Thay vào đó chúng ta có thể sử dụng Object.getPrototypeOf()
. Bạn có thể làm cho việc sử dụng nó đỡ mất thời gian hơn một chút ^^.
const prototypeOf = Object.getPrototypeOf;
let u = new User();
// prototypeOf(u) === User.prototype; // true
// prototypeOf(prototypeOf(u)) === Object.prototype // true
Trong bài viết này, chúng ta đã cùng nhau tìm hiểu sự khác nhau giữa thuộc tính prototype
và __proto__
. Hi vọng bài viết giúp ích cho các bạn. Nếu có thắc mắc, chúng ta cùng thảo luận bên dưới phần bình luận nhé!