Hiểu sâu về Prototype và Class trong JavaScript
8 phút
JavaScript có cơ chế OOP khác biệt so với các ngôn ngữ khác. Thay vì class-based, JS sử dụng prototype-based inheritance. Hãy cùng hiểu sâu về cơ chế này và tại sao ES6 Class chỉ là “syntactic sugar”.
JavaScript không có Class thật sự! 😱
Nghe có vẻ lạ, nhưng JavaScript không có class thật sự như Java, C#. Mọi thứ đều dựa trên Objects và Prototypes.
// ❌ Tưởng là class, nhưng thực ra là...
class Person {
constructor(name) {
this.name = name;
}
}
// ✅ Thực tế JavaScript tạo ra:
console.log(typeof Person); // "function"
console.log(Person.prototype); // Object với constructorPrototype là gì? 🔗
Prototype là một object đặc biệt mà mọi function trong JS đều có. Nó là “template” để tạo ra các objects mới.
Function và Prototype
// Mọi function đều có property 'prototype'
function Person(name) {
this.name = name;
}
console.log(Person.prototype); // {}
console.log(typeof Person.prototype); // "object"
// Thêm method vào prototype
Person.prototype.greet = function() {
return `Hi, I'm ${this.name}`;
};
// Tạo instance
const harry = new Person('Harry');
console.log(harry.greet()); // "Hi, I'm Harry"Prototype Chain
Mỗi object có một internal link [[Prototype]] (accessible qua __proto__):
const harry = new Person('Harry');
console.log(harry.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null (top của chain)
// Prototype chain: harry → Person.prototype → Object.prototype → nullCách hoạt động của Prototype Chain 🔍
Property Lookup
JavaScript tìm properties theo thứ tự:
function Person(name) {
this.name = name;
}
Person.prototype.species = 'Homo sapiens';
Object.prototype.planet = 'Earth';
const harry = new Person('Harry');
harry.age = 25;
// Lookup order:
console.log(harry.age); // 25 (own property)
console.log(harry.name); // "Harry" (own property)
console.log(harry.species); // "Homo sapiens" (từ Person.prototype)
console.log(harry.planet); // "Earth" (từ Object.prototype)
console.log(harry.unknown); // undefined (không tìm thấy)hasOwnProperty vs in operator
const harry = new Person('Harry');
console.log('name' in harry); // true
console.log(harry.hasOwnProperty('name')); // true
console.log('species' in harry); // true (inherited)
console.log(harry.hasOwnProperty('species')); // false
// Chỉ lấy own properties
console.log(Object.keys(harry)); // ['name']
console.log(Object.getOwnPropertyNames(harry)); // ['name']Constructor Functions - Cách cũ tạo “Class” 🏗️
Basic Constructor
function Person(name, age) {
// Instance properties
this.name = name;
this.age = age;
}
// Shared methods trên prototype
Person.prototype.greet = function() {
return `Hello, I'm ${this.name}`;
};
Person.prototype.getAge = function() {
return this.age;
};
// Static method
Person.createAnonymous = function() {
return new Person('Anonymous', 0);
};
// Usage
const harry = new Person('Harry', 25);
console.log(harry.greet()); // "Hello, I'm Harry"
const anon = Person.createAnonymous();Inheritance với Prototypes
// Base constructor
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return `${this.name} is eating`;
};
// Child constructor
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Add child methods
Dog.prototype.bark = function() {
return `${this.name} says Woof!`;
};
// Usage
const buddy = new Dog('Buddy', 'Golden Retriever');
console.log(buddy.eat()); // "Buddy is eating" (inherited)
console.log(buddy.bark()); // "Buddy says Woof!"
// Kiểm tra inheritance
console.log(buddy instanceof Dog); // true
console.log(buddy instanceof Animal); // trueES6 Classes - Syntactic Sugar 🍬
ES6 Class chỉ là cách viết đẹp hơn cho prototype pattern:
Class Declaration
class Person {
// Constructor method
constructor(name, age) {
this.name = name;
this.age = age;
}
// Instance methods (tự động thêm vào prototype)
greet() {
return `Hello, I'm ${this.name}`;
}
getAge() {
return this.age;
}
// Static methods
static createAnonymous() {
return new Person('Anonymous', 0);
}
// Getter/Setter
get info() {
return `${this.name} (${this.age})`;
}
set info(value) {
const [name, age] = value.split(' ');
this.name = name;
this.age = parseInt(age);
}
}
// Usage giống hệt constructor function
const harry = new Person('Harry', 25);
console.log(harry.greet()); // "Hello, I'm Harry"Class Inheritance
class Animal {
constructor(name) {
this.name = name;
}
eat() {
return `${this.name} is eating`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Gọi parent constructor
this.breed = breed;
}
bark() {
return `${this.name} says Woof!`;
}
// Override method
eat() {
return super.eat() + ' dog food';
}
}
const buddy = new Dog('Buddy', 'Golden Retriever');
console.log(buddy.eat()); // "Buddy is eating dog food"
console.log(buddy.bark()); // "Buddy says Woof!"So sánh: Constructor vs Class 🔄
Cùng một kết quả, 2 cách viết khác nhau:
Constructor Function Way
// ✅ Constructor Function (ES5)
function Vehicle(brand) {
this.brand = brand;
this.speed = 0;
}
Vehicle.prototype.start = function() {
return `${this.brand} started`;
};
Vehicle.prototype.accelerate = function(amount) {
this.speed += amount;
return `Speed: ${this.speed}km/h`;
};
function Car(brand, doors) {
Vehicle.call(this, brand);
this.doors = doors;
}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car;
Car.prototype.honk = function() {
return `${this.brand} goes Beep!`;
};Class Way
// ✅ Class Syntax (ES6)
class Vehicle {
constructor(brand) {
this.brand = brand;
this.speed = 0;
}
start() {
return `${this.brand} started`;
}
accelerate(amount) {
this.speed += amount;
return `Speed: ${this.speed}km/h`;
}
}
class Car extends Vehicle {
constructor(brand, doors) {
super(brand);
this.doors = doors;
}
honk() {
return `${this.brand} goes Beep!`;
}
}Kết quả giống hệt nhau!
// Cả 2 cách tạo ra same prototype structure
const car1 = new Car('Toyota', 4);
console.log(car1.__proto__ === Car.prototype); // true
console.log(Car.prototype.__proto__ === Vehicle.prototype); // true
// Same methods
console.log(car1.start()); // "Toyota started"
console.log(car1.honk()); // "Toyota goes Beep!"
console.log(car1 instanceof Car); // true
console.log(car1 instanceof Vehicle); // trueTại sao Class là “Syntactic Sugar”? 🍭
1. Class vẫn tạo ra Functions
class MyClass {}
console.log(typeof MyClass); // "function"
console.log(MyClass.prototype); // Object
console.log(MyClass.prototype.constructor === MyClass); // true2. Methods vẫn trên Prototype
class Person {
greet() { return 'Hello'; }
}
const p = new Person();
console.log(p.hasOwnProperty('greet')); // false
console.log(Person.prototype.hasOwnProperty('greet')); // true3. Inheritance vẫn dùng Prototype Chain
class Parent {}
class Child extends Parent {}
console.log(Child.prototype.__proto__ === Parent.prototype); // true4. Có thể mix Constructor và Class
// Constructor function
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
return 'eating';
};
// Class extends Constructor!
class Dog extends Animal {
bark() {
return 'woof';
}
}
const dog = new Dog('Buddy');
console.log(dog.eat()); // "eating" (từ constructor)
console.log(dog.bark()); // "woof" (từ class)Advanced Prototype Patterns 🎯
1. Dynamic Method Addition
class Calculator {
add(a, b) { return a + b; }
}
// Runtime thêm method
Calculator.prototype.multiply = function(a, b) {
return a * b;
};
const calc = new Calculator();
console.log(calc.multiply(3, 4)); // 12
// Thậm chí thêm vào built-in objects (không khuyến khích)
// String.prototype.reverse = function() {
// return this.split('').reverse().join('');
// };2. Prototype Pollution (Security Issue)
// ❌ Nguy hiểm - Prototype pollution
const userInput = JSON.parse('{"__proto__": {"polluted": true}}');
// Tất cả objects bị ảnh hưởng
console.log({}.polluted); // true
// ✅ An toàn hơn
const safe = Object.create(null); // Không có prototype
safe.__proto__ = { polluted: true };
console.log(safe.polluted); // undefined3. Factory Pattern với Prototypes
function createShape(type) {
const shapes = {
circle: Circle,
square: Square
};
const Shape = shapes[type];
return Shape ? new Shape() : null;
}
function Circle() {
this.type = 'circle';
}
Circle.prototype.area = function(radius) {
return Math.PI * radius * radius;
};
function Square() {
this.type = 'square';
}
Square.prototype.area = function(side) {
return side * side;
};
const circle = createShape('circle');
console.log(circle.area(5)); // 78.54Modern JavaScript OOP Best Practices 💡
1. Prefer Composition over Inheritance
// ❌ Deep inheritance hierarchy
class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}
class Poodle extends Dog {}
// ✅ Composition - Linh hoạt hơn
class Dog {
constructor(name) {
this.name = name;
this.behaviors = {
eating: new EatingBehavior(),
walking: new WalkingBehavior(),
barking: new BarkingBehavior()
};
}
eat() { return this.behaviors.eating.execute(this.name); }
walk() { return this.behaviors.walking.execute(this.name); }
bark() { return this.behaviors.barking.execute(this.name); }
}2. Use Private Fields (ES2022)
class BankAccount {
// Private fields
#balance = 0;
#accountNumber;
constructor(accountNumber) {
this.#accountNumber = accountNumber;
}
// Private method
#validateAmount(amount) {
return amount > 0 && typeof amount === 'number';
}
deposit(amount) {
if (this.#validateAmount(amount)) {
this.#balance += amount;
}
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount('123456');
account.deposit(100);
console.log(account.getBalance()); // 100
// ❌ Không thể access private
// console.log(account.#balance); // SyntaxError3. Static Blocks (ES2022)
class Config {
static #instance;
static #initialized = false;
// Static initialization block
static {
console.log('Config class initialized');
this.#instance = new Config();
this.#initialized = true;
}
static getInstance() {
return this.#instance;
}
}
const config1 = Config.getInstance();
const config2 = Config.getInstance();
console.log(config1 === config2); // true (singleton)Performance Considerations ⚡
1. Method Definition Location
class Person {
constructor(name) {
this.name = name;
// ❌ Tạo function mới cho mỗi instance
this.greet = function() {
return `Hi ${this.name}`;
};
}
}
class PersonOptimized {
constructor(name) {
this.name = name;
}
// ✅ Method trên prototype - share cho tất cả instances
greet() {
return `Hi ${this.name}`;
}
}
// Memory usage
const people = Array.from({length: 1000}, (_, i) => new Person(`Person${i}`));
// vs
const peopleOpt = Array.from({length: 1000}, (_, i) => new PersonOptimized(`Person${i}`));2. Prototype Chain Length
// ❌ Chain quá dài - Chậm lookup
class A {}
class B extends A {}
class C extends B {}
class D extends C {}
class E extends D {} // Quá sâu!
// ✅ Composition thay inheritance
class E {
constructor() {
this.aFeature = new AFeature();
this.bFeature = new BFeature();
}
}Kết luận 📋
Key Takeaways:
- JavaScript = Prototype-based OOP, không phải class-based
- ES6 Class = Syntactic sugar cho constructor functions
- Prototype chain quyết định property lookup
- Inheritance thực sự diễn ra qua
[[Prototype]]links - Performance: Methods trên prototype tốt hơn trong constructor
Khi nào dùng gì:
| Pattern | Khi nào dùng | Ưu điểm |
|---|---|---|
| Constructor Function | Legacy code, cần hiểu sâu | Rõ ràng cơ chế prototype |
| ES6 Class | Code mới, team lớn | Dễ đọc, quen thuộc với dev khác ngôn ngữ |
| Factory Function | Cần flexibility cao | Tránh new, closures, composition |
| Object.create() | Fine-grained control | Kiểm soát prototype chain |
Best Practices:
- ✅ Dùng Class syntax cho code mới (readable)
- ✅ Hiểu prototype mechanism (debugging)
- ✅ Composition > Inheritance (maintainable)
- ✅ Private fields cho encapsulation (ES2022+)
- ❌ Tránh prototype pollution
- ❌ Tránh deep inheritance chains
JavaScript OOP khác biệt nhưng rất mạnh mẽ khi hiểu đúng cách! 🚀
