Hàm trong JavaScript: function declaration, expression và arrow function
JavaScript có 3 cách viết hàm. Không phải để chọn cái nào "đẹp hơn" — mỗi cách có đặc điểm riêng, ảnh hưởng đến cách hoạt động của this và hoisting. Hiểu sự khác nhau sẽ giúp bạn tránh nhiều bug phổ biến.
Hàm trong JavaScript là gì?
Hàm là khối code có thể tái sử dụng. Điểm đặc biệt của JavaScript: hàm là first-class citizen — có thể gán vào biến, truyền như đối số, và trả về từ hàm khác. Đây là nền tảng của lập trình hàm (functional programming) trong JS.
Function Declaration
function tinh_tong(a, b) {
return a + b;
}
console.log(tinh_tong(3, 4)); // 7
Hoisting — Đặc điểm quan trọng
// Gọi được TRƯỚC khi khai báo!
console.log(xin_chao("Minh")); // "Xin chào, Minh!" ✓
function xin_chao(ten) {
return `Xin chào, ${ten}!`;
}
Function declaration được JavaScript engine "kéo lên đầu" file, nên gọi được ở bất kỳ vị trí nào.
Function Expression
// Gán hàm vào biến
const tinh_tong = function(a, b) {
return a + b;
};
console.log(tinh_tong(3, 4)); // 7
// Không có hoisting
console.log(tinh_tich(3, 4)); // TypeError! Gọi trước khi khai báo
const tinh_tich = function(a, b) { return a * b; };
Arrow Function (ES6) — Cú pháp gọn nhất
// Đầy đủ
const tinh_tong = (a, b) => {
return a + b;
};
// Rút gọn — chỉ có 1 biểu thức return
const tinh_tong_ngan = (a, b) => a + b;
// 1 tham số — không cần ngoặc
const nhan_doi = x => x * 2;
// Không tham số — cần ngoặc rỗng
const xin_chao = () => "Xin chào!";
// Dùng với map/filter — rất phổ biến
const diem = [7.5, 8.0, 9.2, 5.5];
const diem_gioi = diem.filter(d => d >= 8.0); // [8.0, 9.2]
Sự khác biệt quan trọng của this
// Regular function: this phụ thuộc vào cách gọi hàm
const button = {
label: "Nhấn vào đây",
click: function() {
console.log(this.label); // "Nhấn vào đây" ✓
}
};
// Arrow function: this kế thừa từ scope bên ngoài
const button2 = {
label: "Nhấn vào đây",
click: () => {
console.log(this.label); // undefined ✗
}
};
button.click(); // "Nhấn vào đây"
button2.click(); // undefined
Bảng so sánh 3 loại hàm
| Declaration | Expression | Arrow | |
|---|---|---|---|
| Cú pháp | function f(){} | const f = function(){} | const f = () => {} |
| Hoisting | Có ✓ | Không ✗ | Không ✗ |
this | Dynamic | Dynamic | Lexical (kế thừa) |
| Dùng làm method | Tốt ✓ | Tốt ✓ | Không nên ✗ |
| Callback/map/filter | OK | OK | Tốt nhất ✓ |
Tham số nâng cao
Default Parameters
function chao(ten, ngon_ngu = "vi") {
if (ngon_ngu === "vi") return `Xin chào, ${ten}!`;
return `Hello, ${ten}!`;
}
console.log(chao("Minh")); // Xin chào, Minh!
console.log(chao("John", "en")); // Hello, John!
Rest Parameters
function tong_tat_ca(...so) {
return so.reduce((tong, n) => tong + n, 0);
}
console.log(tong_tat_ca(1, 2, 3)); // 6
console.log(tong_tat_ca(1, 2, 3, 4, 5)); // 15
Higher-Order Functions — map, filter, reduce
Đây là 3 hàm quan trọng nhất trong JavaScript hiện đại:
const diem = [8.5, 6.0, 9.2, 7.8, 5.5, 8.0];
// map — Biến đổi mỗi phần tử, trả về mảng mới
const diem_lam_tron = diem.map(d => Math.round(d));
// [9, 6, 9, 8, 6, 8]
// filter — Lọc phần tử thỏa điều kiện
const diem_gioi = diem.filter(d => d >= 8.0);
// [8.5, 9.2, 8.0]
// reduce — Tích lũy thành một giá trị
const trung_binh = diem.reduce((acc, d) => acc + d, 0) / diem.length;
// 7.5
Closures
function tao_bo_dem(bat_dau = 0) {
let dem = bat_dau; // Biến "private"
return {
tang: () => ++dem,
giam: () => --dem,
xem: () => dem
};
}
const bo_dem = tao_bo_dem(10);
console.log(bo_dem.tang()); // 11
console.log(bo_dem.tang()); // 12
console.log(bo_dem.giam()); // 11
// Biến dem không thể truy cập từ bên ngoài!
Kết luận
3 kiểu hàm trong JavaScript đều có vai trò riêng: Declaration cho hàm độc lập có tên, Expression cho hàm gán vào biến, Arrow cho callback và hàm ngắn. Quy tắc chọn hàm: dùng arrow function cho callback và map/filter/reduce, dùng regular function khi cần this trỏ vào object.