this, bind và context trong TypeScript & JavaScript
Bài 15 – this, bind và cách kiểm soát context trong TypeScript & JavaScript
this, bind và context trong TypeScript & JavaScript
this là một trong những khái niệm dễ gây nhầm lẫn nhất trong JavaScript. Nắm chắc cách hoạt động của this và cách “cố định” context bằng bind sẽ giúp bạn tránh nhiều lỗi khó hiểu, đặc biệt trong class, callback và môi trường bất đồng bộ.
TypeScript giúp bạn bắt lỗi this ngay khi code, giúp an toàn hơn nhiều so với JavaScript thuần.
Nội dung chính
thislà gì và phụ thuộc vào ngữ cảnh nàothistrong object, class, function, arrow function- Mất context trong callback và cách khắc phục
bind,call,apply– khác nhau thế nào- Cách TypeScript giúp kiểm soát lỗi liên quan đến
this
Ví dụ chi tiết
1. this trong object
const user = {
name: "Alice",
greet() {
console.log("Hello", this.name);
}
};
user.greet(); // Output: "Hello Alice"
2. Mất context khi tách method
const user = {
name: "Alice",
greet() {
console.log("Hello", this.name);
}
};
const greetFn = user.greet;
// greetFn(); // Lỗi: Cannot read property 'name' of undefined
3. bind để cố định context
const user = {
name: "Alice",
greet() {
console.log("Hello", this.name);
}
};
const greetBound = user.greet.bind(user);
greetBound(); // Output: "Hello Alice"
4. this trong class
class Counter {
constructor() {
this.count = 0;
}
increase() {
this.count++;
console.log(`Count: ${this.count}`);
}
}
const c = new Counter();
const inc = c.increase;
// inc(); // Lỗi: Cannot read property 'count' of undefined
const incFixed = c.increase.bind(c);
incFixed(); // Output: "Count: 0"
5. Arrow function giữ nguyên context
class Counter2 {
constructor() {
this.count = 0;
}
increase = () => {
this.count++;
console.log(`Count: ${this.count}`);
};
}
const c2 = new Counter2();
const fn = c2.increase;
fn(); // Luôn hoạt động đúng vì arrow function không tạo this mới
6. call và apply
function say(message) {
console.log(message, this.name);
}
const user = { name: "Bob" };
// call - truyền tham số dạng liệt kê
say.call(user, "Hi"); // Output: "Hi Bob"
// apply - truyền tham số dạng array
say.apply(user, ["Hello"]); // Output: "Hello Bob"
7. Ví dụ thực tế với callback
class Button {
constructor(label) {
this.label = label;
this.clicked = false;
}
// Method thường - dễ mất context
handleClick() {
this.clicked = true;
console.log(`Button "${this.label}" clicked: ${this.clicked}`);
}
// Arrow function - giữ context
handleClickArrow = () => {
this.clicked = true;
console.log(`Button "${this.label}" clicked: ${this.clicked}`);
};
}
const button = new Button("Submit");
// Giả lập event listener
const simulateClick = (handler) => {
setTimeout(handler, 1000);
};
// Sẽ gây lỗi vì mất context
// simulateClick(button.handleClick);
// Hoạt động đúng với bind
simulateClick(button.handleClick.bind(button));
// Hoạt động đúng với arrow function
simulateClick(button.handleClickArrow);
Kiến thức trọng tâm
1. this phụ thuộc vào cách hàm được gọi, không phải nơi hàm được khai báo
Đây là nguyên nhân chính gây lỗi “this is undefined”. Trong JavaScript, giá trị của this được xác định tại thời điểm gọi hàm, không phải khi khai báo hàm.
2. Dùng bind, call, apply để điều khiển context
bind→ Tạo hàm mới với context cố định, không gọi hàm ngay lập tứccall→ Gọi hàm với context và tham số dạng liệt kêapply→ Gọi hàm với context và tham số dạng array
3. Arrow function không tạo this mới
Luôn dùng this của nơi nó được khai báo → giải pháp an toàn trong class, callback, event handler. Đây là cách hiện đại và được khuyến nghị để tránh lỗi về context.
4. TypeScript giúp phát hiện lỗi this
// TypeScript có thể phát hiện lỗi this nếu bật strict mode
class SafeCounter {
count = 0;
increase(this: SafeCounter) {
this.count++;
}
}
const counter = new SafeCounter();
const method = counter.increase;
// method(); // TypeScript sẽ báo lỗi compile-time
Bài tập thực hành
Bài tập 1: Object và method
// Tạo object car có method start() in ra this.brand
const car = {
brand: "Toyota",
start() {
console.log(`Starting ${this.brand}...`);
}
};
// Tách method ra và sửa bằng bind
const startMethod = car.start;
const boundStart = startMethod.bind(car);
boundStart(); // Output: "Starting Toyota..."
Bài tập 2: Class với async method
// Tạo class Timer có method tick()
class Timer {
constructor(name) {
this.name = name;
this.ticks = 0;
}
tick() {
this.ticks++;
console.log(`${this.name} tick: ${this.ticks}`);
}
}
const timer = new Timer("MyTimer");
// Gọi tick trong setTimeout và sửa lỗi mất context
// Cách 1: Dùng bind
setTimeout(timer.tick.bind(timer), 1000);
// Cách 2: Dùng arrow function
setTimeout(() => timer.tick(), 1000);
// Cách 3: Chuyển method thành arrow function trong class
class BetterTimer {
constructor(name) {
this.name = name;
this.ticks = 0;
}
tick = () => {
this.ticks++;
console.log(`${this.name} tick: ${this.ticks}`);
};
}
Bài tập 3: Hàm generic với call
// Viết hàm introduce nhận message, dùng call để in ra message + this.name
function introduce(message) {
console.log(`${message}, I'm ${this.name}!`);
}
const person = { name: "Alice" };
const employee = { name: "Bob" };
// Sử dụng call
introduce.call(person, "Hello"); // Output: "Hello, I'm Alice!"
introduce.call(employee, "Welcome"); // Output: "Welcome, I'm Bob!"
// Sử dụng apply với array
introduce.apply(person, ["Hi there"]); // Output: "Hi there, I'm Alice!"
Sai lầm thường gặp
3. Quên bind khi truyền method làm callback
// ❌ Sai: Mất context
button.addEventListener('click', obj.handleClick);
// ✅ Đúng: Giữ context với bind
button.addEventListener('click', obj.handleClick.bind(obj));
// ✅ Đúng: Dùng arrow function
button.addEventListener('click', () => obj.handleClick());
4. Nhầm lẫn giữa method thường và arrow function
class MyClass {
// ❌ Có thể mất context nếu không bind
handleEvent() {
console.log(this);
}
// ✅ Luôn giữ đúng context
handleEvent = () => {
console.log(this);
};
}
3. Sử dụng this trong nested function
const obj = {
name: "Test",
method() {
// ❌ this trong function thường sẽ mất context
setTimeout(function() {
console.log(this.name); // undefined
}, 0);
// ✅ Dùng arrow function để giữ context
setTimeout(() => {
console.log(this.name); // "Test"
}, 0);
}
};
Kết luận
Hiểu đúng về this, cùng với bind và arrow function, giúp bạn tránh được nhiều lỗi khó chịu trong ứng dụng thực tế. Đây là kỹ năng nền tảng quan trọng khi làm việc với:
- Class và OOP: Đảm bảo method hoạt động đúng khi được gọi từ nhiều nơi
- Event handler: Xử lý sự kiện không bị mất context
- Callback và async: Tránh lỗi bất đồng bộ
- Functional programming: Kết hợp với các pattern như currying, partial application
Với TypeScript, bạn có thêm lớp bảo vệ compile-time giúp phát hiện lỗi this sớm, làm cho code trở nên robust và dễ bảo trì hơn.