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ể gây mất binding this
, dẫn đến lỗi khó debug và ảnh hưởng tính năng. Hiểu rõ điểm khác biệt giúp bạn viết code sạch hơn, dễ bảo trì và tránh các “cạm bẫy” trong ứng dụng.
#1. Khái niệm cơ bản
#1.1. Regular Function
1 | greet('Paolo'); // Hello, Paolo |
Regular function cho phép bạn sử dụng để tạo constructor
, sử dụng arguments
để truy cập tất cả tham số, có thể thực thi trước khi định nghĩa nhờ hoisting
.
#1.2. Arrow Function
1 | const greet = (name) => console.log(`Hello, ${name}`); |
Arrow function được thiết kế chủ yếu để làm function “nhỏ gọn” và giữ nguyên ngữ cảnh (lexical) của this
. Chính cách thiết kế này dẫn đến việc:
Không có
arguments
- Arrow function không khởi tạo
arguments
. Thay vào đó, nếu cần tham số động, bạn phải dùngrest parameter
:1
2
3const fn = (...args) => {
console.log(args);
};
- Arrow function không khởi tạo
Không có
super
- Trong class, khi gọi method thông thường, bạn có thể dùng
super
để gọi phương thức cha. Ví dụ:1
2
3
4
5
6
7
8class A { method() { console.log('A'); } }
class B extends A {
method() {
super.method(); // gọi A.method()
}
}
new B().method() - Arrow function không có bản đồ prototype và không khởi tạo
this
haynew.target
, nên cũng không có binding chosuper
.
- Trong class, khi gọi method thông thường, bạn có thể dùng
Không có
new.target
new.target
chỉ tồn tại trong một hàm được gọi bằngnew
, để biết hàm đó có bị khởi tạo làm constructor hay không.- Arrow function không thể dùng với
new
, nên không tạo bindingnew.target
.
Không thể làm constructor
- Một hàm muốn được gọi bằng
new
thì phải có nội bộ method[[Construct]]
và tạo ra đối tượng mới với prototype tương ứng. - Arrow function không có nội bộ
[[Construct]]
, không cóprototype
property, nên khi bạn cố gắngnew (() => {})
sẽ bị lỗi:1
2const F = () => {};
new F(); // TypeError: F is not a constructor
- Một hàm muốn được gọi bằng
#1.3. Cú pháp ngắn gọn & implicit return
1 | const square = n => n * n; // implicit return |
Lưu ý: Khi arrow function chỉ chứa một biểu thức, bạn có thể bỏ dấu ngoặc nhọn và return
; giá trị của biểu thức chính là giá trị trả về.
#2. Hoisting và Temporal Dead Zone
1 | console.log(fn); // ƒ fn(){} |
Lưu ý:
var foo
được hoisted (đưa lên đầu scope) nhưng chỉ khởi tạo giá trịundefined
trước khi gán hàm, nênconsole.log(foo)
in raundefined
.const bar
nằm trong Temporal Dead Zone (TDZ) từ khi vào scope đến khi khởi tạo, nên truy cậpbar
trước khi định nghĩa gâyReferenceError
.- Nhờ đó, bạn hiểu rằng regular function declaration được hoisted hoàn toàn, nhưng function expression hoặc arrow function với
let/const
thì không.
#3. Cách hoạt động của this
#3.1. Global Context
1 | function regularFunc() { console.log(this); } |
Lưu ý:
- regularFunc():
this
bên trong regular function ở non-strict mode làwindow
, ở strict mode làundefined
. - arrowFunc():
this
bên trong arrow function luôn là this của outer scope, tức làwindow
.
#3.2. Callback & Promise
#3.2.1. Callback
1 | const user = { |
Lưu ý:
- Trong
setTimeout(function(){...})
, this lúc này làwindow
do ở non-strict mode, do đóthis.name
không phảiuser.name
. - Trong
setTimeout(()=>{...})
, this lúc này làuser
, do đóthis.name
tương ứng vớiuser.name
.
#3.2.1. Promise
Callback của .then(function(){...})
có this
riêng, không phải instance API
.
1 | class API { |
Callback bằng arrow function sẽ giữ this
là instance APIFixed
vì this
bên trong arrow function luôn là this của outer scope, tức là APIFixed
.
1 | class 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, nênthis
trỏ đếnwindow
, do đóthis.value
làundefined
,undefined++
choNaN
.
#3.4. DOM Event Handler
1 | button.addEventListener('click', function() { |
Lưu ý:
- Regular function trong sự kiện
click
cóthis
là element gắn sự kiện. - Arrow function có
this
là outer scope, nênthis
trỏ đếnwindow
, không dùng được để thao tác trực tiếp với element.
#4. Bài tập tự kiểm tra
- Dự đoán kết quả:
1
2
3
4
5
6
7const obj = {
x: 10,
foo: () => console.log(this.x),
bar() { console.log(this.x); }
};
obj.foo();
obj.bar(); - Viết lại
foo
để in ra10
:1
2
3
4
5const obj = {
x: 10,
foo: () => console.log(this.x)// undefined
};
obj.foo(); - Giải thích vì sao arrow function không có
arguments
,super
,new.target
và không thể làm constructor.
#5. Kết luận
Regular Function
- Khi cần
this
linh hoạt, làm method trong object/class. - Khi cần sử dụng constructor, hoisting,
arguments
.
- Khi cần
Arrow Function
Khi cần callback ngắn gọn, functional programming (map/filter/reduce).
Khi cần giữ nguyên
this
của outer scope, tránh phải dùng.bind(this)
:- Khi bạn truyền một method dùng
this
làm callback, ví dụ chosetTimeout
hoặc event handler, regular function sẽ mất bindingthis
, buộc bạn phải:Hoặc lưu biến trung gian:1
setTimeout(this.method.bind(this), 1000);
Dùng arrow function thì không cần thêm1
2const self = this;
setTimeout(function() { self.method(); }, 1000);.bind(this)
vì arrow tự động mượnthis
từ nơi nó được định nghĩa:1
setTimeout(() => this.method(), 1000);
- Khi bạn truyền một method dùng