Câu 1Nếu khai báo function double(n) { n * 2; } rồi chạy console.log(double(4));, màn hình sẽ hiển thị gì?
Khai báo hàm và biểu thức hàm — tham số và return
Học cách khai báo hàm bằng function, truyền đối số và nhận giá trị qua return, biểu thức hàm, và vì sao quên return thì hàm trả về undefined.
Nếu bạn viết thẳng công thức tính giá đã gồm thuế ở cả 3 nơi — danh sách sản phẩm, giỏ hàng và màn hình xác nhận đơn — thì khi thuế suất thay đổi, bạn phải sửa cả 3 chỗ, và màn hình nào bị sót sẽ vẫn hiện số tiền cũ.
Bài này nói về khai báo function để đặt tên cho một đoạn xử lý rồi gọi nó, biểu thức hàm để gán hàm vào biến như một giá trị, và giá trị mà return trả về.
Đặt tên cho đoạn xử lý để gọi lại — khai báo function và tham số
Cách tính giá đã gồm thuế cho từng sản phẩm lần nào cũng giống nhau, chỉ có đơn giá là khác. Vì vậy, bạn có thể đặt tên cho chính phép tính đó, rồi ở danh sách hay giỏ hàng chỉ việc gọi tên ấy.
Hàm (function — một khối code được đặt tên để bạn gọi lại bao nhiêu lần cũng được) được khai báo theo dạng function tên(tham số) { thân hàm }. Tham số viết trong cặp ngoặc tròn là biến nhận một giá trị ở mỗi lần gọi, còn giá trị bạn viết sau return (giá trị trả về) sẽ được trả về cho bên gọi.
// Đặt tên taxIncluded cho đoạn xử lý tính giá đã gồm thuế
function taxIncluded(price) {
const tax = Math.floor(price * 0.1);
return price + tax; // Trả giá trị vừa tính về cho bên gọi
}
// Gọi cùng một hàm với các đơn giá khác nhau
console.log(taxIncluded(1200)); // 1320
console.log(taxIncluded(980)); // 1078
// Nếu quên truyền giá trị, price sẽ không nhận được gì
console.log(taxIncluded()); // NaN
price thay đổi thì giá trị trả về cũng thay đổi theo. Ở lần gọi không truyền giá trị, price là undefined và kết quả là NaN.price chỉ nhận giá trị, nên dù bạn viết thẳng 1200 hay truyền một biến như itemPrice, hàm vẫn chạy y như nhau.
Nhận nhiều đối số — khớp theo vị trí
Khi tỷ lệ giảm giá thay đổi theo hạng thành viên hay theo đợt khuyến mãi, một hàm chỉ nhận đơn giá là không đủ. Nếu bên gọi truyền được cả tỷ lệ giảm giá, phép tính vẫn chỉ nằm ở một chỗ, và tỷ lệ thì do bên gọi quyết định.
Các tham số được ngăn cách bằng dấu phẩy. Các giá trị trong lời gọi được gán vào tham số theo đúng thứ tự bạn viết: giá trị thứ nhất vào tham số thứ nhất, giá trị thứ hai vào tham số thứ hai. JavaScript không khớp theo tên, nên dù bạn truyền nhầm thứ tự, code vẫn đúng cú pháp và không có lỗi nào được báo.
// Nhận đơn giá và tỷ lệ giảm giá, trả về giá sau khi giảm
function applyDiscount(price, rate) {
return Math.floor(price * (1 - rate));
}
// Giá trị thứ nhất vào price, giá trị thứ hai vào rate
console.log(applyDiscount(2400, 0.2)); // 1920
console.log(applyDiscount(2400, 0.05)); // 2280
// Dù đảo thứ tự, phép tính vẫn cứ chạy
console.log(applyDiscount(0.2, 2400)); // -480
applyDiscount, chỉ đổi thứ tự. Đảo thứ tự không gây lỗi, chỉ có kết quả là thay đổi.Dòng gọi hàm chỉ có các giá trị, nên nhìn applyDiscount(0.2, 2400) bạn sẽ không nhận ra chỗ nhầm. Khi nhận về một giá trị vô lý như -480, hãy đối chiếu thứ tự các giá trị trong lời gọi với định nghĩa của hàm.
Dùng hàm như một giá trị — biểu thức hàm và gán vào biến
Giao hàng thường và giao hàng nhanh tính phí vận chuyển bằng hai công thức hoàn toàn khác nhau. Thay vì ở mỗi chỗ gọi đều phải chỉ rõ dùng công thức nào, bạn có thể gán sẵn hàm cần dùng vào một biến, rồi bên gọi chỉ việc gọi biến đó.
Biểu thức hàm (function expression — cách gán một hàm vào biến như một giá trị) là một lệnh gán, viết theo dạng const tên = function (tham số) { thân hàm };. Khai báo function đã được chuẩn bị sẵn trước khi code bắt đầu chạy từ trên xuống, còn biến của biểu thức hàm chỉ nhận được hàm khi dòng gán được chạy (cơ chế này được giải thích trong bài về scope và hoisting).
// Khai báo function có thể được gọi ngay cả từ những dòng nằm phía trên nó
console.log(expressShipping(1.5)); // 900
function expressShipping(weight) {
return weight <= 2 ? 900 : 1300;
}
// Biểu thức hàm: gán hàm vào biến như một giá trị
const normalShipping = function (weight) {
return weight <= 2 ? 500 : 800;
};
// Viết tên hàm không kèm ngoặc tròn thì chính hàm đó được gán sang biến khác
const feeRule = normalShipping;
console.log(feeRule(3)); // 800
console.log(typeof feeRule); // function
console.log(typeof normalShipping(3)); // number
ReferenceError. Hàm được gán bằng biểu thức hàm chỉ gọi được sau khi dòng gán đã chạy.Thông báo lỗi đầy đủ là ReferenceError: Cannot access 'normalShipping' before initialization. Ngoài ra, hãy để ý dòng feeRule trong đoạn code trên: khi gán tên hàm mà không kèm ngoặc tròn, chính hàm đó được đưa sang một biến khác.
Khai báo trùng tên thì hàm viết sau được dùng
Vì khai báo function được chuẩn bị sẵn trước khi code chạy, nếu bạn khai báo hai hàm cùng tên thì ngay cả lời gọi ở dòng ngay sau hàm thứ nhất cũng chạy thân của hàm viết sau. Chương trình không dừng lại, nên một hàm trùng tên nằm ở chỗ xa trong file rất dễ bị bỏ sót. Với biểu thức hàm dùng const, khai báo lần thứ hai sẽ gây SyntaxError và code không chạy.
Khi không có giá trị trả về — quên viết return
Nếu trong hàm có console.log, khi gọi hàm thì chữ sẽ hiện lên màn hình. Vì có kết quả hiện ra nên trông như hàm đã chạy đúng, nhưng khi lưu giá trị trả về vào một biến để dùng, bạn sẽ thấy trong đó không có chuỗi mình mong đợi.
return vừa trả về một giá trị, vừa kết thúc hàm ngay tại đó. Hàm không có return sẽ trả về undefined — hiển thị ra màn hình và trả về giá trị là hai việc khác nhau. console.log chỉ in ra màn hình, không đưa gì về cho bên gọi.
// Hàm có return: chuỗi được trả về cho bên gọi
function formatLabel(name, stock) {
return `${name} (còn ${stock} cái)`;
console.log("Dòng này không chạy tới"); // Nằm sau return nên không được chạy
}
// Hàm không có return: vẫn in ra màn hình, nhưng giá trị trả về là undefined
function printLabel(name, stock) {
console.log(`${name} (còn ${stock} cái)`);
}
const label = formatLabel("Đèn bàn", 3);
console.log(label); // Đèn bàn (còn 3 cái)
const printed = printLabel("Hub USB", 5); // Hub USB (còn 5 cái)
console.log(printed); // undefined
| Cách viết hàm | Điều xảy ra khi gọi | Bên gọi nhận được |
|---|---|---|
| Trả chuỗi về bằng return | Chuỗi được trả về cho bên gọi | Đèn bàn (còn 3 cái) |
| Chỉ có console.log | In ra nhưng không trả về gì | undefined |
| console.log đặt sau return | Dòng sau return không chạy | Chuỗi vẫn được trả về |
Nếu bên gọi sẽ dùng đến giá trị, hãy trả nó về bằng return thay vì hiển thị ra màn hình.
Xuống dòng ngay sau return thì không có giá trị nào được trả về
Nếu bạn xuống dòng ngay sau return rồi viết giá trị ở dòng tiếp theo, JavaScript coi câu lệnh đã kết thúc ở dòng return, và hàm trả về undefined. Lỗi này dễ gặp khi bạn tách một template literal dài xuống dòng riêng cho dễ đọc. Hãy luôn bắt đầu viết giá trị trên cùng dòng với return.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Khi gọi function fee(price, rate) { return price * rate; } theo dạng fee(0.2, 1000), price sẽ nhận giá trị nào?
Câu 3Điều gì xảy ra nếu bạn gọi calc(1) ở một dòng nằm trước lệnh gán const calc = function (n) { return n + 1; };?