Câu 1Nếu khai báo const twice = n => n * 2; rồi hiển thị twice(4), màn hình sẽ hiện gì?
Arrow function — cách viết rút gọn và trả về object
Học cách viết lại hàm bằng =>, khi nào bỏ được ngoặc tròn và ngoặc nhọn, cách trả về object, và thân hàm dạng khối.
Ngay cả với một xử lý ngắn như định dạng từng phần tử của mảng, nếu viết bằng biểu thức hàm thì lần nào bạn cũng phải viết function, cặp ngoặc nhọn và return, khiến phần xử lý thật sự bị chìm giữa đống ký hiệu.
Bài này nói về arrow function tạo hàm bằng =>, và các cách viết rút gọn cho phép bỏ ngoặc tròn và ngoặc nhọn.
Viết cùng một xử lý ngắn hơn — dạng cơ bản của arrow function
Hàm chỉ nhận một giá trị, thực hiện một phép tính rồi trả kết quả về là loại hàm rất hay gặp — ví dụ tính điểm thưởng từ số tiền mua hàng. Vậy mà hàm nào cũng cần từ khóa function, cặp ngoặc nhọn và return, nên khi có nhiều biểu thức hàm nằm cạnh nhau, phần cú pháp lại nổi bật hơn cả phép tính.
Arrow function (cách tạo hàm bằng =>) được viết theo dạng const tên = (tham số) => { thân hàm };. Bạn bỏ từ khóa function và đặt => giữa danh sách tham số và thân hàm, rồi gán vào biến và gọi y như biểu thức hàm.
// Cách viết đã dùng tới giờ: biểu thức hàm, gán một hàm vào biến như một giá trị
const rewardPoints = function (price) {
return price * 0.02;
};
// Arrow function
const rewardPointsArrow = (price) => {
return price * 0.02;
};
console.log(rewardPoints(3200)); // 64
console.log(rewardPointsArrow(3200)); // 64
console.log(typeof rewardPointsArrow); // function
Hai hàm được đặt tên khác nhau chỉ để bạn so sánh cạnh nhau. Cả hai đều có typeof là function, và nhận tham số cũng như dùng return theo cùng một cách. Khi thỏa một số điều kiện, bạn còn có thể rút gọn dạng cơ bản này hơn nữa bằng cách bỏ ngoặc hoặc bỏ return.
Bỏ ngoặc và return — một tham số và return ngầm định
Có những xử lý chỉ cần một biểu thức, như cộng phí vận chuyển vào số tiền hay kiểm tra tổng tiền có từ 5000 yên trở lên không. Ở dạng cơ bản, ngay cả phép tính một dòng cũng chiếm 3 dòng vì phải thêm ngoặc nhọn và return, và khi có nhiều hàm như vậy, ngoặc với return chiếm gần hết số dòng.
Nếu có đúng một tham số, bạn có thể bỏ cặp ngoặc tròn bao quanh nó. Hơn nữa, nếu thân hàm chỉ là một biểu thức, bạn còn bỏ được cả ngoặc nhọn và return. Điều này có được là nhờ return ngầm định (implicit return — quy tắc lấy luôn giá trị của biểu thức trong thân hàm làm giá trị trả về).
// Dạng cơ bản: viết đủ ngoặc tròn quanh tham số, ngoặc nhọn quanh thân hàm và return
const withFee = (price) => {
return price + 500;
};
// Dạng rút gọn
const withFeeShort = price => price + 500;
const isFreeShipping = total => total >= 5000;
console.log(withFee(1200)); // 1700
console.log(withFeeShort(1200)); // 1700
console.log(isFreeShipping(4800)); // false
Thân của isFreeShipping chỉ là một phép so sánh, nên khi truyền 4800 thì hàm trả về false, là kết quả của 4800 >= 5000. Return ngầm định trả về giá trị của biểu thức, dù đó là kết quả gì, nên phép tính hay phép so sánh đều được viết theo cùng một kiểu.
Không bỏ được ngoặc tròn khi có 0 hoặc 2 tham số
Khi không có tham số nào (() => biểu thức) hoặc có từ hai tham số trở lên ((price, rate) => biểu thức), nếu bỏ ngoặc tròn bạn sẽ gặp SyntaxError và code không chạy được. Nếu sau này thêm tham số, nhớ đặt lại ngoặc tròn. Khi chỉ có một tham số, bạn vẫn có thể giữ ngoặc như (price) => biểu thức.
Trả về object — bọc trong ngoặc tròn
Khi định dạng một bản ghi để hiển thị, thứ bạn muốn trả về thường không phải một con số mà là một object có nhiều key. Nhưng nếu dùng dạng rút gọn và viết cặp ngoặc nhọn của object ngay sau =>, bạn sẽ nhận về undefined chứ không phải object.
Cặp ngoặc nhọn viết sau => được đọc là thân dạng khối (block body — vùng có thể viết bao nhiêu câu lệnh cũng được), chứ không được đọc là object. Để trả về object, hãy bọc nó trong ngoặc tròn như (name) => ({ name: name }), để cặp ngoặc nhọn được đọc là một biểu thức.
// Viết ngoặc nhọn trực tiếp thì chúng được đọc là thân dạng khối
const toBadge = (name) => { label: name };
console.log(toBadge("Chuột không dây")); // undefined
// Bọc trong ngoặc tròn thì ngoặc nhọn được đọc là object
const toCard = (name, stock) => ({ name: name, inStock: stock > 0 });
const card = toCard("Chuột không dây", 4);
console.log(JSON.stringify(card)); // {"name":"Chuột không dây","inStock":true}
console.log(card.inStock); // true
Khi có từ hai key trở lên, như { name: name, inStock: true }, phần bên trong không thể đọc thành câu lệnh, nên code báo SyntaxError và không chạy được. Khi chỉ có một key, label: name lại tình cờ là một câu lệnh hợp lệ (một label theo sau là một biểu thức), nên không có lỗi nào, hàm cứ lặng lẽ trả về undefined — rất khó nhận ra cho tới khi bạn hiển thị kết quả.
Khi xử lý cần nhiều dòng — thân dạng khối và return
Có những xử lý phải tính một giá trị trung gian hoặc rẽ nhánh theo điều kiện trước khi quyết định trả về gì, như tính xem khách hàng còn bao nhiêu ngày để đổi trả sản phẩm. Dạng rút gọn chỉ gồm một biểu thức không có chỗ để giữ giá trị trung gian, cũng không có cách nào trả về giá trị khác nhau cho từng điều kiện.
Thân dạng khối có thể chứa các câu lệnh như const hay if, nhưng return ngầm định không áp dụng ở đây, nên bạn phải viết return để trả về giá trị. Một hàm có thể có nhiều câu lệnh return, và hàm kết thúc ở câu nào được chạy trước.
// Thân chỉ gồm một biểu thức: giá trị của biểu thức được trả về luôn
const shortMessage = (days) => `${days} ngày kể từ khi mua`;
console.log(shortMessage(12)); // 12 ngày kể từ khi mua
// Thân dạng khối: tạo giá trị trung gian rồi return theo từng điều kiện
const returnMessage = (days, isOpened) => {
const limit = isOpened ? 7 : 14;
if (days > limit) {
return "Đã hết hạn đổi trả"; // Hàm kết thúc tại đây
}
return `Còn ${limit - days} ngày`;
};
console.log(returnMessage(12, false)); // Còn 2 ngày
console.log(returnMessage(12, true)); // Đã hết hạn đổi trả
// Không có return thì nhận về undefined
const brokenMessage = (days) => { `Còn ${days} ngày`; };
console.log(brokenMessage(12)); // undefined
Nếu bạn return sớm cho những trường hợp không thỏa điều kiện, các dòng phía sau chỉ cần xử lý trường hợp bình thường. Bảng dưới đây cho thấy bên gọi nhận về gì với từng cách viết thân hàm.
| Thân hàm | Trong thân hàm | Bên gọi nhận |
|---|---|---|
| days => biểu thức | Trả về giá trị biểu thức | 12 ngày kể từ khi mua |
| (days, isOpened) => { ...; return } | Trả về giá trị return đã chạy | Còn 2 ngày / Đã hết hạn đổi trả |
| days => { chỉ có biểu thức } | Tính nhưng không trả về | undefined |
this hoạt động khác với hàm thông thường
Bên trong method của một object, bạn có thể dùng this (từ khóa trỏ tới chính object đã gọi method đó). Arrow function không có this của riêng nó mà dùng luôn this của đoạn code bao bên ngoài. Bài về class trong chương lập trình hướng đối tượng sẽ quay lại điểm khác biệt này.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Với const toItem = name => { label: name };, gọi toItem sẽ trả về gì?
Câu 3Với một arrow function có hai tham số, điều gì xảy ra nếu bạn bỏ cặp ngoặc tròn bao quanh chúng?