Prototype là gì?
Prototype là cơ chế cốt lõi của JavaScript để thực hiện kế thừa (inheritance). Mọi object trong JavaScript đều có một prototype, và có thể kế thừa properties/methods từ prototype đó.
JavaScript không có Class truyền thống
JavaScript là prototype-based, không phải class-based như Java hay C++. Classes trong ES6+ chỉ là syntactic sugar trên prototype.
// "Class" trong JS thực chất là function
class Person {
constructor(name) {
this.name = name;
}
}
// Tương đương với:
function Person(name) {
this.name = name;
}
Prototype Chain
Mỗi object có một internal property [[Prototype]] (truy cập qua __proto__):
const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null (end of chain)
Tìm kiếm property
JavaScript tìm property theo chuỗi prototype:
const person = {
name: 'Alice'
};
console.log(person.name); // 'Alice' (tìm thấy trên chính object)
console.log(person.toString()); // '[object Object]' (tìm thấy trên Object.prototype)
console.log(person.notExist); // undefined (không tìm thấy trên chain)
Constructor Functions
Cách cũ (ES5)
function Person(name, age) {
this.name = name;
this.age = age;
}
// Thêm methods vào prototype
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
Person.prototype.getAge = function() {
return this.age;
};
// Tạo instance
const alice = new Person('Alice', 25);
alice.sayHi(); // "Hi, I'm Alice"
// Kiểm tra
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
Lợi ích: Methods chỉ tạo 1 lần trên prototype, không duplicate cho mỗi instance.
// ❌ BAD: Mỗi instance có 1 copy của method
function Person(name) {
this.name = name;
this.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
}
// ✅ GOOD: Tất cả instances share 1 method
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
ES6 Classes
Cú pháp đẹp hơn nhưng vẫn dùng prototype:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHi() {
console.log(`Hi, I'm ${this.name}`);
}
getAge() {
return this.age;
}
}
const bob = new Person('Bob', 30);
bob.sayHi();
// Vẫn dùng prototype
console.log(bob.__proto__ === Person.prototype); // true
console.log(typeof Person); // "function"
Kế thừa (Inheritance)
Cách ES5
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating`);
};
function Dog(name, breed) {
Animal.call(this, name); // Gọi parent constructor
this.breed = breed;
}
// Kế thừa prototype
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Thêm method riêng
Dog.prototype.bark = function() {
console.log('Woof!');
};
const dog = new Dog('Buddy', 'Golden');
dog.eat(); // "Buddy is eating" (từ Animal)
dog.bark(); // "Woof!" (từ Dog)
Cách ES6 (extends)
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Gọi parent constructor
this.breed = breed;
}
bark() {
console.log('Woof!');
}
// Override method
eat() {
super.eat(); // Gọi parent method
console.log('Dog food yummy!');
}
}
const dog = new Dog('Buddy', 'Golden');
dog.eat();
// Output:
// "Buddy is eating"
// "Dog food yummy!"
dog.bark(); // "Woof!"
Prototype Methods
Object.create()
Tạo object với prototype cụ thể:
const personPrototype = {
sayHi() {
console.log(`Hi, I'm ${this.name}`);
}
};
const alice = Object.create(personPrototype);
alice.name = 'Alice';
alice.sayHi(); // "Hi, I'm Alice"
console.log(alice.__proto__ === personPrototype); // true
Object.getPrototypeOf()
Lấy prototype của object:
const proto = Object.getPrototypeOf(alice);
console.log(proto === personPrototype); // true
Object.setPrototypeOf()
Thay đổi prototype (⚠️ không khuyến khích - performance):
const newProto = {
sayBye() {
console.log('Bye!');
}
};
Object.setPrototypeOf(alice, newProto);
alice.sayBye(); // "Bye!"
hasOwnProperty()
Kiểm tra property có phải của chính object không (không phải từ prototype):
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
console.log('Hi!');
}
}
const person = new Person('Alice');
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('sayHi')); // false (trên prototype)
instanceof
Kiểm tra object có phải instance của constructor không:
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true
Shadowing
Property trên object “che” property trên prototype:
const parent = {
value: 10
};
const child = Object.create(parent);
console.log(child.value); // 10 (từ prototype)
child.value = 20; // Tạo property mới trên child
console.log(child.value); // 20 (từ chính object)
console.log(parent.value); // 10 (không thay đổi)
delete child.value;
console.log(child.value); // 10 (lại lấy từ prototype)
Built-in Prototypes
Array.prototype
const arr = [1, 2, 3];
// Methods từ Array.prototype
arr.push(4);
arr.map(x => x * 2);
// Thêm method cho tất cả arrays
Array.prototype.first = function() {
return this[0];
};
console.log([1, 2, 3].first()); // 1
⚠️ Cảnh báo: Không nên modify built-in prototypes trong production!
String.prototype
// Polyfill cho includes() (ES6)
if (!String.prototype.includes) {
String.prototype.includes = function(search) {
return this.indexOf(search) !== -1;
};
}
Ví dụ thực tế
1. Plugin System
class Editor {
constructor() {
this.content = '';
}
setText(text) {
this.content = text;
}
}
// Plugin: Spell checker
Editor.prototype.checkSpelling = function() {
console.log('Checking spelling...');
// Logic here
};
// Plugin: Auto save
Editor.prototype.autoSave = function() {
console.log('Auto saving...');
// Logic here
};
const editor = new Editor();
editor.setText('Hello world');
editor.checkSpelling();
editor.autoSave();
2. Mixin Pattern
// Mixin để thêm functionality
const canFly = {
fly() {
console.log(`${this.name} is flying`);
}
};
const canSwim = {
swim() {
console.log(`${this.name} is swimming`);
}
};
class Bird {
constructor(name) {
this.name = name;
}
}
// Thêm mixins
Object.assign(Bird.prototype, canFly);
class Duck extends Bird {
constructor(name) {
super(name);
}
}
// Duck vừa bay vừa bơi
Object.assign(Duck.prototype, canSwim);
const duck = new Duck('Donald');
duck.fly(); // "Donald is flying"
duck.swim(); // "Donald is swimming"
3. Factory Pattern với Prototype
const carPrototype = {
start() {
console.log(`${this.brand} ${this.model} started`);
},
stop() {
console.log('Car stopped');
}
};
function createCar(brand, model) {
const car = Object.create(carPrototype);
car.brand = brand;
car.model = model;
return car;
}
const car1 = createCar('Toyota', 'Camry');
const car2 = createCar('Honda', 'Civic');
car1.start(); // "Toyota Camry started"
car2.start(); // "Honda Civic started"
4. Memoization với Prototype
class Calculator {
constructor() {
this.cache = {};
}
fibonacci(n) {
if (n in this.cache) {
return this.cache[n];
}
if (n <= 1) return n;
const result = this.fibonacci(n - 1) + this.fibonacci(n - 2);
this.cache[n] = result;
return result;
}
}
// Thêm shared method
Calculator.prototype.clear = function() {
this.cache = {};
};
const calc = new Calculator();
console.log(calc.fibonacci(10)); // 55
Class vs Prototype: Khi nào dùng gì?
Dùng Class khi:
- Code dễ đọc, gần với OOP truyền thống
- Cần extends và super
- Team quen với class-based languages
class User {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}`;
}
}
Dùng Prototype trực tiếp khi:
- Cần performance tối ưu
- Dynamic thêm methods
- Tạo objects không cần constructor
const userMethods = {
greet() {
return `Hello, ${this.name}`;
}
};
function createUser(name) {
const user = Object.create(userMethods);
user.name = name;
return user;
}
Best Practices
- Ưu tiên Class syntax (ES6+)
// ✅ Dễ đọc
class Animal {
constructor(name) {
this.name = name;
}
}
- Không modify built-in prototypes
// ❌ Nguy hiểm
Array.prototype.myMethod = function() {};
// ✅ Tạo utility function
function myArrayMethod(arr) {}
- Sử dụng Object.create() thay vì proto
// ❌ Deprecated
obj.__proto__ = prototype;
// ✅ Standard
const obj = Object.create(prototype);
- Luôn set constructor khi kế thừa (ES5)
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // ✅ Đừng quên
Kết luận
Prototype là trái tim của JavaScript OOP:
- Hiểu prototype chain giúp debug tốt hơn
- Classes ES6 là syntax sugar trên prototype
- Prototype cho phép kế thừa linh hoạt
- Dùng đúng pattern cho từng use case
Nắm vững prototype sẽ giúp bạn hiểu sâu JavaScript và viết code hiệu quả hơn!