Các phương thức với prototype trong JavaScript

Cập nhật ngày 28/01/2022

Trong bài viết Prototype là gì? Prototype trong JavaScript, mình nói rằng __proto__ đã lỗi thời và bạn nên dùng các phương thức Object.getPrototypeOfObject.setPrototypeOf để thay thế.

Vì vậy, bài viết này mình sẽ tập trung vào tìm hiểu về các phương thức với prototype trong JavaScript.

Phương thức của prototype

Các phương thức của prototype trong JavaScript bao gồm:

Ví dụ về các phương thức của prototype:

let animal = {
  eats: true,
};

// tạo đối tượng với với prototype là animal
let rabbit = Object.create(animal);
console.log(rabbit.eats); // true

console.log(Object.getPrototypeOf(rabbit) === animal); // true
Object.setPrototypeOf(rabbit, {}); // thay đổi prototype của rabbit thành {}

Đối tượng mô tả thuộc tính descriptors giống như mình đã giới thiệu trong bài viết về writable, enumerable và configurable.

Bạn có thể sử dụng Object.create để clone object thay vì cách sử dụng vòng lặp for...in:

let clone = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

Câu lệnh trên copy object một cách hoàn toàn, bao gồm tất cả các thuộc tính (enumerable và non-enumberable), getters/setters,... với giá trị chính xác của [[Prototype]].

Object thuần

Như bạn đã biết, object thuần thường dùng để lưu các cặp key-value, với key là string hoặc symbol. Và một điều thú vị, key có thể là bất kỳ string nào, ngoại trừ "__proto__", ví dụ:

let obj = {};
let key = "__proto__";

obj[key] = "some value";
console.log(obj[key].toString()); // "[object Object]" không phải "some value".

Trong ví dụ trên, mình muốn lưu trữ cặp key-value với giá trị của key là "__proto__". Tuy nhiên, key này bị bỏ qua. Bởi vì, "__proto__" là một thuộc tính đặc biệt có giá trị null hoặc một object.

Làm sao để giải quyết vấn đề này?

Cách 1: bạn có thể chuyển qua sử dụng Map thay vì sử dụng object thuần như sau:

let obj = new Map();let key = "__proto__";

obj.set(key, "some value");console.log(obj.get(key).toString()); // "some value"

Cách 2: sử dụng Object.create(null), ví dụ:

let obj = Object.create(null);let key = "__proto__";

obj[key] = "some value";
console.log(obj[key].toString()); // "some value"

Bởi vì, __proto__ không phải một thuộc tính trong object, mà chỉ là getter/setter cho [[Prototype]].

Khi gọi Object.create(null), thực chất là bạn đang tạo một object không có [[Prototype]]. Do đó, __proto__ cũng không trở thành getter/setter.

Vì vậy, __proto__ có thể trở thành thuộc tính bình thường trong object như cách làm trên.

Tổng kết

Các phương thức với prototype trong JavaScript là:

__proto__ thực chất là getter/setter của [[Prototype]] trong object. Vì vậy, bạn không thể lưu key-value với giá trị của key là __proto__.

Để giải quyết vấn đề trên, bạn có thể sử dụng Map thay vì object thuần, hoặc dùng Object.create(null) để tạo ra object không có prototype.

Ngoài ra, Object.create còn được dùng để clone object như sau:

let clone = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

Các phương thức hữu ích khác:

Thực hành

Bài 1

Cho đoạn code sau:

let dictionary = Object.create(null);

// Thêm phương thức toString vào dictionary tại đây

// thêm data vào dictionary
dictionary.apple = "Apple";
dictionary.__proto__ = "test"; // __proto__ là một thuộc tính thường

// chỉ apple và __proto__ xuất hiện trong vòng lặp
for (let key in dictionary) {
  console.log(key); // "apple" rồi đến "__proto__"
}

// gọi dictionary.toString()
console.log(dictionary.toString()); // "apple,__proto__"

Hãy thêm phương thức toString vào dictionary để thỏa mãn đoạn code trên?

let dictionary = Object.create(null);

// Thêm phương thức toString vào dictionary tại đâyObject.defineProperty(dictionary, "toString", {  value: function () {    return Object.keys(this).join(",");  },  enumberable: false,});
// thêm data vào dictionary
dictionary.apple = "Apple";
dictionary.__proto__ = "test"; // __proto__ là một thuộc tính thường

// chỉ apple và __proto__ xuất hiện trong vòng lặp
for (let key in dictionary) {
  console.log(key); // "apple" rồi đến "__proto__"
}

// gọi dictionary.toString()
console.log(dictionary.toString()); // "apple,__proto__"

Bài 2

Cho đoạn code sau:

function Rabbit(name) {
  this.name = name;
}
Rabbit.prototype.sayHi = function () {
  console.log(this.name);
};

let rabbit = new Rabbit("Rabbit");

Các câu lệnh sau có giống nhau hay không?

rabbit.sayHi(); // (1)
Rabbit.prototype.sayHi(); // (2)
Object.getPrototypeOf(rabbit).sayHi(); // (3)
rabbit.__proto__.sayHi(); // (4)
(1) - Rabbit
(2) - undefined
(3) - undefined
(4) - undefined

Câu lệnh (1) thì this=rabbit, bởi vì rabbit là object đứng trước ., nên đáp án đúng như trên.

Các câu lệnh (2), (3), (4) thì this=Rabbit.prototype. Mà trong Rabbit.prototype không có thuộc tính name.

Tham khảo: Prototype methods, objects without __proto__

★ Nếu bạn thấy bài viết này hay thì hãy theo dõi mình trên Facebook để nhận được thông báo khi có bài viết mới nhất nhé:

Native prototypes trong JavaScript
Cú pháp class trong JavaScript cơ bản
Chia sẻ:

Bình luận