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ó
argumentsriê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.targetnew.targetlà 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.targetvì 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 contextriê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:
fnlà function declaration → được hoisting đầy đủ cả tên và thân hàm.fookhai báo bằngvar→ được hoisting biến, nhưng gán sau nên in raundefined.bardù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óthislinh hoạt, phụ thuộc cách gọi.arrowFunc()luôn giữthiscủ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óthislàwindow→ inNo name 1.() => {}giữthislà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ênthistrỏ đếncounter, cập nhậtvalue.incrementArrowcóthislà outer scope (không phải objectcounter), nênthis.valuelà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,thiskhô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ênthistừ nơi định nghĩa (tức là instancea).
#3.5. DOM Event Handler
1 | button.addEventListener('click', function () { |
- Dùng regular function →
thislà element đang click. - Dùng arrow function →
thiskhô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.targetvà 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 |