Trong thế giới JavaScript, có một từ khóa nhỏ bé nhưng lại gieo rắc không ít bối rối cho các lập trình viên, từ người mới bắt đầu đến cả những người đã có kinh nghiệm. Nó được gọi là this
. Từ khóa this
hoạt động không giống như ở các ngôn ngữ lập trình khác, và sự "kỳ diệu" của nó nằm ở chỗ giá trị của nó thay đổi tùy thuộc vào ngữ cảnh mà nó được gọi.
Bạn đã bao giờ viết một đoạn code và console.log(this)
chỉ để nhận về một kết quả không mong muốn? 🤔 Đừng lo, bạn không đơn độc. Bài viết này sẽ giúp bạn giải mã hoàn toàn this
, biến nó từ một khái niệm mơ hồ thành một công cụ mạnh mẽ trong tay bạn.
Từ khóa this trong JavaScript là gì?
Nói một cách đơn giản nhất, this
là một tham chiếu đến một đối tượng. Nhưng nó tham chiếu đến đối tượng nào? Câu trả lời là: Đối tượng đó chính là ngữ cảnh thực thi (execution context) của hàm hiện tại.
Đây là điểm mấu chốt: giá trị của this
không được xác định tại thời điểm hàm được viết, mà là tại thời điểm hàm được gọi. Cách bạn gọi một hàm sẽ quyết định this
là gì.
Hiểu rõ this
giúp bạn:
- Viết code hướng đối tượng hiệu quả.
- Thao tác với các sự kiện trong DOM một cách chính xác.
- Tận dụng các phương thức nâng cao như
call
,apply
, vàbind
. - Tránh các lỗi tiềm ẩn khó gỡ rối.
Hãy cùng nhau khám phá 4 quy tắc vàng quyết định giá trị của this
.
🎯 Top 4 "quy tắc vàng" xác định giá trị của this
1. Quy tắc mặc định: Global Context
Khi một hàm được gọi một cách độc lập (không phải là phương thức của một đối tượng), this
sẽ tham chiếu đến đối tượng toàn cục.
- Trong trình duyệt, đó là đối tượng
window
. - Trong môi trường Node.js, đó là đối tượng
global
.
function showMeThis() {
console.log(this)
}
showMeThis() // Trong trình duyệt, this sẽ là đối tượng Window
Lưu ý trong "Strict Mode": Nếu bạn sử dụng 'use strict';
, this
trong trường hợp này sẽ là undefined
để tránh việc vô tình chỉnh sửa đối tượng toàn cục.
2. Quy tắc ngầm định: Object Context
Đây là trường hợp sử dụng phổ biến nhất. Khi một hàm được gọi với tư cách là một phương thức (method) của một đối tượng, this
sẽ tham chiếu đến chính đối tượng gọi phương thức đó.
Hãy nhìn vào đối tượng đứng trước dấu chấm .
. Đó chính là this
!
const person = {
name: 'John Doe',
greet: function () {
console.log(`Hello, my name is ${this.name}.`)
},
}
person.greet() // "Hello, my name is John Doe."
// `this` ở đây chính là đối tượng `person` vì `person` đã gọi hàm `greet`.
3. Quy tắc tường minh: call, apply, và bind
JavaScript cung cấp cho chúng ta cách để "ra lệnh" cho một hàm rằng this
nên là gì, bất kể nó được gọi như thế nào. Đó là lúc call
, apply
, và bind
tỏa sáng. ✨
-
call(thisArg, arg1, arg2, ...)
: Thực thi hàm ngay lập tức, vớithis
được gán bằngthisArg
và các tham số được truyền vào riêng lẻ. -
apply(thisArg, [arg1, arg2, ...])
: Tương tựcall
, nhưng các tham số được truyền vào dưới dạng một mảng. -
bind(thisArg)
: Không thực thi hàm ngay lập tức. Thay vào đó, nó trả về một hàm mới vớithis
đã được "khóa cứng" vàothisArg
.
function introduce(city, country) {
console.log(`I am ${this.name} from ${city}, ${country}.`)
}
const user1 = { name: 'Alice' }
const user2 = { name: 'Bob' }
// Sử dụng call
introduce.call(user1, 'New York', 'USA') // I am Alice from New York, USA.
// Sử dụng apply
introduce.apply(user2, ['Tokyo', 'Japan']) // I am Bob from Tokyo, Japan.
// Sử dụng bind
const introduceAlice = introduce.bind(user1, 'London', 'UK')
introduceAlice() // I am Alice from London, UK.
bind
đặc biệt hữu ích khi làm việc với các hàm callback hoặc xử lý sự kiện, nơi ngữ cảnh của this
rất dễ bị "mất".
4. Quy tắc new: Constructor Context
Khi một hàm được gọi với từ khóa new
ở phía trước (để tạo một instance của một đối tượng), những điều sau sẽ xảy ra:
- Một đối tượng trống hoàn toàn mới được tạo ra.
- Đối tượng trống này được gán làm giá trị cho
this
. - Hàm được thực thi.
- Đối tượng mới này được trả về (trừ khi hàm trả về một đối tượng khác một cách tường minh).
function Car(make, model) {
this.make = make
this.model = model
this.info = function () {
return `${this.make} ${this.model}`
}
}
const myCar = new Car('Toyota', 'Camry')
console.log(myCar.info()) // "Toyota Camry"
// `this` bên trong `Car` tham chiếu đến đối tượng `myCar` vừa được tạo.
💡 Trường hợp đặc biệt: Arrow Functions
Hàm mũi tên (Arrow Function), được giới thiệu trong ES6, có một cách xử lý this
hoàn toàn khác biệt và là một "cứu tinh" trong nhiều trường hợp.
Hàm mũi tên không có this
của riêng nó. Thay vào đó, nó "vay mượn" this
từ ngữ cảnh bao bọc (lexical scope) gần nhất nơi nó được định nghĩa.
Hãy xem ví dụ kinh điển về setTimeout
:
const counter = {
count: 0,
start: function () {
// Cách cũ hay gây lỗi
setTimeout(function () {
// `this` ở đây là `window`, không phải `counter`!
console.log(`Lỗi: ${this.count}`) // NaN hoặc undefined
}, 1000)
// Cách giải quyết với hàm mũi tên
setTimeout(() => {
// `this` ở đây được "thừa hưởng" từ hàm `start`.
// Mà `this` trong `start` chính là `counter`.
this.count++
console.log(`Chính xác: ${this.count}`) // 1
}, 1000)
},
}
counter.start()
Vì sự tiện lợi này, hàm mũi tên là lựa chọn tuyệt vời cho các hàm callback và trình xử lý sự kiện. Tuy nhiên, không nên sử dụng hàm mũi tên cho các phương thức của đối tượng hoặc hàm tạo, vì khi đó chúng sẽ không nhận this
là đối tượng như mong đợi.
Tổng kết: Thứ tự ưu tiên của this
Vậy khi nhiều quy tắc có thể được áp dụng cùng lúc, quy tắc nào sẽ thắng?
new
: Nếu hàm được gọi vớinew
,this
là đối tượng mới được tạo.call
,apply
,bind
: Nếu hàm được gọi với các phương thức này,this
là đối tượng được chỉ định tường minh.- Object Context: Nếu hàm được gọi như một phương thức,
this
là đối tượng chứa nó. - Mặc định: Nếu không thuộc các trường hợp trên,
this
là đối tượng toàn cục (hoặcundefined
trong strict mode).
Từ khóa this
có thể phức tạp, nhưng nó tuân theo một bộ quy tắc logic và nhất quán. Chìa khóa để làm chủ nó là luôn tự hỏi: "Hàm này được GỌI như thế nào?".
Bằng cách hiểu rõ 4 quy tắc vàng và sự khác biệt của hàm mũi tên, bạn không chỉ giải quyết được một trong những vấn đề hóc búa nhất của JavaScript mà còn mở ra cánh cửa để viết code sạch hơn, hiệu quả hơn và dễ bảo trì hơn.
Chúc bạn thành công trên hành trình chinh phục JavaScript!