Hi 🤓 Cảm ơn bạn đã ghé thăm blog này, nếu những bài viết trên blog giúp ích cho bạn. Bạn có thể giúp blog hiển thị quảng cáo bằng cách tạm ngừng ad blocker 😫 và để giúp blog duy trì hoạt động nếu bạn muốn.
Cảm ơn bạn!

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__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.

proto vs prototype in JavaScript

👉 Ở đ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 objsayHi.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]]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. proto vs prototype in JavaScript

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__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é!

Có thể bạn thích ⚡
homiedev
About Me

Hi, I'm @devnav. Một người thích chia sẻ kiến thức, đặc biệt là về Frontend 🚀. Trang web này được tạo ra nhằm giúp các bạn học Frontend hiệu quả hơn 🎉😄.

Chúc các bạn tìm được kiến thức hữu ích trong blog này 😁😁.