Để thêm property/method **chia sẻ cho tất cả instances** của một constructor, gán vào Constructor.prototype. Đây là cơ chế **Prototype-based Inheritance** của JavaScript.
Cơ chế:
- Mỗi function có property
prototype(object) - Instance tạo bằng
newcó__proto__trỏ đếnConstructor.prototype - Truy cập property: tìm trên instance → không thấy → tìm trên prototype chain
Ví dụ minh họa
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Add Property/Method to Constructor</title>
<style>
body { font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; padding: 20px; }
.result { font-size: 18px; font-weight: 500; margin: 10px 0; color: green; }
button { padding: 8px 16px; cursor: pointer; }
</style>
</head>
<body>
<h1>Thêm Property/Method vào Constructor (Prototype)</h1>
<div class="result" id="result"></div>
<button class="Btn" id="btn">NHẤN VÀO ĐÂY</button>
<h3> Nhấn nút để thêm property/method vào prototype </h3>
<script>
const resEle = document.querySelector("#result");
function Human(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const obj = new Human('Rohan', 'Sharma');
document.querySelector("#btn").addEventListener("click", () => {
// 1. Thêm property vào prototype
Human.prototype.age = 22;
// 2. Thêm method vào prototype
Human.prototype.welcome = function() {
return `Welcome ${this.firstName} ${this.lastName}`;
};
// 3. Instance truy cập được prototype property/method
resEle.innerHTML = 'Thêm property age qua prototype: ' + obj.age + '
';
resEle.innerHTML += 'Thêm method welcome() qua prototype: ' + obj.welcome() + '
';
// 4. Kiểm tra own vs inherited
resEle.innerHTML += '
--- Kiểm tra ---
';
resEle.innerHTML += `obj.hasOwnProperty('firstName'): ${obj.hasOwnProperty('firstName')}
`;
resEle.innerHTML += `obj.hasOwnProperty('age'): ${obj.hasOwnProperty('age')}
`;
resEle.innerHTML += `'age' in obj: ${'age' in obj}
`;
resEle.innerHTML += `Object.getPrototypeOf(obj) === Human.prototype: ${Object.getPrototypeOf(obj) === Human.prototype}
`;
});
</script>
</body>
</html>
Kết quả
Thêm property age qua prototype: 22
Thêm method welcome() qua prototype: Welcome Rohan Sharma
--- Kiểm tra ---
obj.hasOwnProperty('firstName'): true
obj.hasOwnProperty('age'): false
'age' in obj: true
Object.getPrototypeOf(obj) === Human.prototype: true
Prototype Chain Visualization:
obj (instance)
├── firstName: "Rohan" (own)
├── lastName: "Sharma" (own)
└── __proto__ → Human.prototype
├── age: 22
├── welcome: ƒ()
└── __proto__ → Object.prototype
├── toString: ƒ()
├── hasOwnProperty: ƒ()
└── __proto__ → null
Property Shadowing (Che giấu):
Human.prototype.age = 22;
const obj = new Human("Rohan", "Sharma");
obj.age = 25; // Tạo OWN property 'age' trên instance
console.log(obj.age); // 25 (own property)
console.log(obj.hasOwnProperty('age')); // true
delete obj.age; // Xóa own property
console.log(obj.age); // 22 (từ prototype)
Best Practice:
- Property instance-specific (name, id, data) → gán trong constructor (
this.prop = ...) - Method/Shared property (logic, constants, default values) → gán vào
Constructor.prototype - Tránh gán object/array vào prototype (shared reference bug):
// ❌ SAI: Shared reference!
function User() {}
User.prototype.friends = []; // TẤT CẢ instances dùng CÙNG MỘT mảng
const u1 = new User(); u1.friends.push("A");
const u2 = new User(); console.log(u2.friends); // ["A"] - BUG!
// ✅ ĐÚNG: Khởi tạo trong constructor
function User() {
this.friends = []; // Mỗi instance có mảng riêng
}
// Hoặc dùng getter (ES6 class)
class User {
get friends() { return this._friends || (this._friends = []); }
}
ES6 Class Syntax (Tương đương):
class Human {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// Instance method (tự động vào prototype)
welcome() {
return `Welcome ${this.firstName} ${this.lastName}`;
}
// Static method (trên class, không phải instance)
static species() { return "Homo sapiens"; }
// Getter/Setter
get fullName() { return `${this.firstName} ${this.lastName}`; }
set fullName(value) { [this.firstName, this.lastName] = value.split(" "); }
}