Học bằng cách đọc theo thứ tự

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
Viết lại biểu thức hàm thành arrow function
Biểu thức hàmconst tên =function (price){ returnprice * 0.02 }Truyền 3200trả về 64Arrow functionconst tên =(price) =>{ returnprice * 0.02 }Truyền 3200trả về 64Chỉ khác ở đây
Phần gán và thân hàm giữ nguyên, chỉ có phần ở giữa thay đổi. Từ khóa function biến mất, và => được đặt sau tham số.

Hai hàm được đặt tên khác nhau chỉ để bạn so sánh cạnh nhau. Cả hai đều có typeoffunction, 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.

Viết lại yearlyFee — biểu thức hàm tính giá gói thuê bao theo năm — thành arrow function. standardMonthly và premiumMonthly cũng đã được khai báo sẵn.

① Hãy chuyển yearlyFee thành arrow function mà không thay đổi cách nó hoạt động.

② Hãy hiển thị giá theo năm của gói Standard và Premium theo dạng "Standard: ◯ yên" và "Premium: ◯ yên".

③ Hãy hiển thị chênh lệch giữa hai giá ở ② theo dạng "Chênh lệch: ◯ yên".

(Nếu chạy đúng, phần giải thích sẽ xuất hiện.)

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

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
Rút gọn cùng một hàm qua từng bước
(price) => {return price + 500; }Không bỏ gìprice => {return price + 500; }Một tham số:bỏ ( )price =>price + 500Một biểu thức:bỏ { } và return
Ba bước chỉ khác nhau ở cách viết, còn phép tính thì như nhau. Truyền 1200 vào bước nào cũng nhận về 1700.

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.

Tạo các hàm dùng để ước tính phí vận chuyển, mỗi hàm có thân chỉ gồm một biểu thức. shippingFee và boxCount đã được khai báo sẵn.

① Hãy viết discountFee trên một dòng: nhận phí vận chuyển và trả về số tiền sau khi giảm 10% (làm tròn xuống).

② Hãy viết totalFee trên một dòng: nhận phí vận chuyển và số thùng, rồi trả về tổng phí vận chuyển.

③ Hãy gọi ① và hiển thị "Sau giảm giá: ◯ yên".

④ Hãy truyền kết quả của ① vào ② và hiển thị "Tổng sau giảm giá: ◯ yên".

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

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
Thân dạng khối hay object?
(name) =>{ label: name }{ } được đọc làthân dạng khốiKhông córeturn nàoTrả về undefined(name) =>({ label: name })Bên trong ( ) đượcđọc là biểu thức{ } trở thànhobjectTrả vềobject
Cùng một cặp ngoặc nhọn nhưng được đọc khác nhau tùy vào việc có bọc trong ngoặc tròn hay không. Ngoặc nhọn không được bọc sẽ được xem là thân dạng khối không có return.

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ả.

Tạo bảng báo giá phí vận chuyển. orderTotals và freeLine đã được khai báo sẵn; đơn dưới freeLine thì phí vận chuyển là 500 yên, còn lại được miễn phí.

① Hãy viết toQuote trên một dòng: nhận một số tiền và trả về object có các key total và shippingFee, theo đúng thứ tự đó.

② Hãy hiển thị báo giá thứ nhất bằng JSON.stringify.

③ Hãy hiển thị số tiền phải trả của báo giá thứ hai (số tiền + phí vận chuyển) theo dạng "Số tiền phải trả: ◯ yên".

④ Hãy hiển thị tổng phí vận chuyển của cả hai báo giá theo dạng "Tổng phí vận chuyển: ◯ yên".

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

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
Sản phẩm đã mở hộp hay chưa quyết định return nào được chạy
returnMessage(12, false)limit là 1412 > 14là falseĐi tiếp tớireturn cuối cùngreturnMessage(12, true)limit là 712 > 7là trueKết thúc ở returntrong if
Chỉ lời gọi cho sản phẩm đã mở hộp mới chạy tới return bên trong if. Hàm kết thúc tại đó và không bao giờ đi tới return phía sau.

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àmTrong thân hàmBên gọi nhận
days => biểu thứcTrả về giá trị biểu thức12 ngày kể từ khi mua
(days, isOpened) => { ...; return }Trả về giá trị return đã chạyCò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.

Tính số lượng hàng cần nhập thêm, làm tròn theo đơn vị lô. targetStock, currentStock và lotSize đã được khai báo sẵn.

① Hãy viết planRestock là một arrow function có thân dạng khối, nhận tồn kho mục tiêu, tồn kho hiện tại và số cái mỗi lô.

② Trong thân hàm, hãy tính số lượng còn thiếu và trả về số đó sau khi làm tròn lên theo đơn vị lô (trả về 0 nếu không thiếu).

③ Hãy gọi hàm với tồn kho hiện tại và hiển thị "Cần nhập thêm: ◯ cái".

④ Hãy hiển thị theo cùng dạng đó cho trường hợp tồn kho hiện tại bằng tồn kho mục tiêu.

JavaScript / TypeScript Editor

Chạy code để xem đầu ra
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

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ì?

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?