Arrow Function vs Regular Function trong JavaScript: Phân biệt và Ứng dụng
Trong JavaScript, việc chọn sai giữa arrow function và regular function có thể khiến bạn gặp lỗi mất this
, dẫn đến bug khó debug và ảnh hưởng đến logic ứng dụng. Hiểu rõ sự khác biệt sẽ giúp bạn viết code rõ ràng hơn, dễ bảo trì và tránh được nhiều “cạm bẫy” không đáng có.
#1. Khái niệm cơ bản
#1.1. Regular Function
1 | greet('Paolo'); // Hello, Paolo |
Regular function có những đặc điểm sau:
- Có thể dùng để tạo constructor (bằng
new
). - Truy cập được biến
arguments
– chứa toàn bộ tham số truyền vào. - Được hoisting – có thể gọi trước khi định nghĩa.
#1.2. Arrow Function
1 | const greet = (name) => console.log(`Hello, ${name}`); |
Arrow function sinh ra để viết function ngắn gọn và giữ nguyên this
của ngữ cảnh bên ngoài (lexical this
). Tuy nhiên, điều này dẫn đến vài khác biệt đáng lưu ý:
- Không có
arguments
riêng
Arrow function không tạo ra biến arguments
. Nếu cần lấy danh sách tham số, bạn phải dùng cú pháp rest:
1 | const fn = (...args) => { |
Không có
new.target
new.target
là một meta property chỉ xuất hiện khi hàm được gọi bằng từ khóanew
, dùng để kiểm tra xem hàm có đang được khởi tạo như một constructor hay không.1
2
3
4
5
6
7
8
9function A() {
if (!new.target) {
throw new Error('Phải dùng new A() để khởi tạo');
}
console.log('Đang khởi tạo bằng new');
}
new A(); // OK
A(); // Error: Phải dùng new A() để khởi tạoTuy nhiên, arrow function không có
new.target
vì bản thân nó không được thiết kế để làm constructor, và không có ngữ cảnh khởi tạo (construct context) riêng.Nếu bạn cố gọi arrow function với
new
:1
2
3
4
5const Arrow = () => {
console.log(new.target); // SyntaxError: new.target expression is not allowed here
};
new Arrow();Bạn sẽ nhận lỗi ngay:
"new.target expression is not allowed here"
– vì arrow function không cóexecution context
riêng, nên không được phép truy cậpnew.target
.
Không thể làm constructor
Vì arrow function không có
[[Construct]]
và không cóprototype
, nên không thể dùng để tạo object:1
2const F = () => {};
new F(); // TypeError: F is not a constructor
#1.3. Cú pháp ngắn gọn và implicit return
1 | const square = n => n * n; |
Ghi nhớ: Nếu function chỉ có một biểu thức, bạn có thể bỏ {}
và return
. Kết quả của biểu thức sẽ được trả về ngầm định.
#2. Hoisting và Temporal Dead Zone
1 | console.log(fn); // ƒ fn(){} |
Giải thích:
fn
là function declaration → được hoisting đầy đủ cả tên và thân hàm.foo
khai báo bằngvar
→ được hoisting biến, nhưng gán sau nên in raundefined
.bar
dùngconst
→ nằm trong Temporal Dead Zone, truy cập trước khi định nghĩa sẽ lỗi.
#3. this
hoạt động khác nhau như thế nào?
#3.1. Trong global context
1 | function regularFunc() { console.log(this); } |
Tóm lại:
regularFunc()
cóthis
linh hoạt, phụ thuộc cách gọi.arrowFunc()
luôn giữthis
của ngữ cảnh bên ngoài (ở đây là global).
#3.2. Callback & Promise
#3.2.1. Callback với setTimeout
1 | const user = { |
function() {}
cóthis
làwindow
→ inNo name 1
.() => {}
giữthis
làuser
→ inHannah
.
#3.2.2. Callback trong Promise
1 | class API { |
Callback bằng regular function không giữ được this
, nên in ra undefined
.
1 | class APIFixed { |
Dùng arrow function → this
là instance của APIFixed
.
#3.3. Phương thức (Method) của Object
1 | const counter = { |
Lưu ý:
incrementRegular
được gọi quacounter.incrementRegular()
, nênthis
trỏ đếncounter
, cập nhậtvalue
.incrementArrow
cóthis
là outer scope (không phải objectcounter
), nênthis.value
làundefined
,undefined++
sẽ cho kết quảNaN
.
#3.4. Phương thức trong Class
1 | class A { |
Tuy nhiên, sự khác biệt chỉ thể hiện rõ khi bạn truyền các method này làm callback:
1 | setTimeout(a.regular, 100); // undefined (mất `this`) |
Giải thích:
regular()
là method nằm trênA.prototype
. Khi truyền vào callback,this
không còn trỏ tới instance nữa trừ khi dùng.bind(this)
.arrow()
là instance property, được tạo mới mỗi khi khởi tạo object, và giữ nguyênthis
từ nơi định nghĩa (tức là instancea
).
#3.5. DOM Event Handler
1 | button.addEventListener('click', function () { |
- Dùng regular function →
this
là element đang click. - Dùng arrow function →
this
không phải element, thường làwindow
.
#4. Tự luyện và kiểm tra
- Đoán kết quả:
1 | const obj = { |
- Sửa
foo
để in ra10
:
1 | const obj = { |
- Vì sao arrow function không có
arguments
,new.target
và không dùng để làm constructor?
Trả lời: Vì arrow function không tạo ra execution context riêng. Nó dùng lại context của scope chứa nó nên không khởi tạothis
,arguments
, haynew.target
.
#5. Kết luận
Tiêu chí | Regular Function | Arrow Function |
---|---|---|
Có this riêng | ✅ Có, thay đổi theo cách gọi | ❌ Không, kế thừa từ outer scope |
Có arguments | ✅ Có | ❌ Không |
Có super | ✅ Có | ❌ Không hỗ trợ super |
Có prototype (class) | ✅ Có, nằm trên prototype | ❌ Không, là property của instance |
Dùng làm constructor (new ) | ✅ Có thể | ❌ Không |
Dùng làm method | ✅ Chuẩn ES6 | ❌ Không phải method thực sự |
Gọi làm callback (setTimeout, map) | ❌ Dễ mất this nếu không bind | ✅ Tự giữ this |
Hiệu suất (class nhiều instance) | ✅ Tốt (dùng chung prototype) | ❌ Tốn bộ nhớ (tạo mỗi instance) |
#Khi nào nên dùng?
Trường hợp | Nên dùng |
---|---|
Cần super , arguments , hoặc kế thừa logic | ✅ Regular Function |
Dùng làm method thật sự, tối ưu hiệu suất | ✅ Regular Function |
Callback hoặc truyền function ra ngoài class | ✅ Arrow Function |
Tránh .bind(this) thủ công khi làm callback | ✅ Arrow Function |