Hàm là một khối các bước có tên
Mỗi khi muốn đặt tên cho một đoạn logic để dùng lại, bạn viết một hàm (function). Cách lâu đời và đơn giản nhất trong JavaScript là khai báo hàm (function declaration): từ khóa function theo sau là một cái tên.
Đọc từ trái sang phải:
functionlà từ khóa bắt đầu một khai báo.greetlà tên hàm.(name)là danh sách tham số, tức các đầu vào mà hàm nhận.- Khối nằm trong cặp ngoặc nhọn là thân hàm.
greet("Ada")là một lời gọi hàm. JavaScript chạy thân hàm vớinamemang giá trị"Ada".
Định nghĩa hàm không làm hàm chạy. Gọi hàm mới làm nó chạy.
Tham số và đối số
Tham số (parameter) là cái tên nằm trong định nghĩa hàm. Đối số (argument) là giá trị được truyền vào khi bạn gọi hàm. Khi nói chuyện thường ngày, người ta hay dùng lẫn hai từ này; sự khác biệt chỉ quan trọng khi bạn đọc thông báo lỗi.
Ở đây base và exponent là tham số. 2 và 10 là đối số. JavaScript gán chúng theo thứ tự: đối số đầu tiên cho tham số đầu tiên, và cứ thế tiếp tục.
Khác với một số ngôn ngữ, JavaScript không báo lỗi nếu bạn truyền sai số lượng đối số. Đối số bị thiếu sẽ thành undefined; đối số thừa bị bỏ qua mà không báo gì. Điều này linh hoạt nhưng đôi khi là cái bẫy: chúng ta sẽ gặp giá trị mặc định và rest parameter ở các trang tài liệu sau.
return gửi một giá trị trở lại
console.log ghi ra đầu ra. return trả một giá trị về cho nơi gọi hàm để nơi đó dùng tiếp:
Nếu không có return, hàm sẽ trả về undefined:
return cũng thoát khỏi hàm ngay lập tức. Return sớm là cách chuẩn để xử lý các trường hợp biên trước khi vào logic chính:
Return sớm giúp thân hàm chính không biến thành một kim tự tháp if/else.
Hoisting: gọi hàm trước khi định nghĩa
Đây là điều thật sự khiến những người đến từ ngôn ngữ khác bất ngờ. Khai báo hàm được hoisting: JavaScript đưa chúng lên đầu phạm vi trước khi chạy bất kỳ dòng code nào. Nghĩa là bạn có thể gọi một hàm ở dòng nằm phía trên chỗ nó được khai báo:
Đoạn code này chạy bình thường. Trước khi thực thi, engine quét toàn bộ phạm vi, ghi nhận square là một hàm, rồi mới bắt đầu chạy các câu lệnh từ trên xuống dưới.
Đây là khác biệt thật sự về hành vi giữa khai báo hàm và các cách tạo hàm khác (biểu thức hàm, arrow function). Những cách kia được hoisting giống biến: tên đã được biết, nhưng giá trị chưa được gán cho đến khi dòng đó chạy. Phần tiếp theo sẽ nói rõ hơn về sự khác biệt này.
Dù vậy, hầu hết các style guide đều khuyên bạn định nghĩa hàm trước khi gọi. Hoisting là tấm lưới an toàn, không phải phong cách để dựa vào.
Khai báo hàm và biểu thức hàm
Một khai báo hàm đứng riêng như một câu lệnh. Một biểu thức hàm (function expression) xuất hiện ở chỗ cần một giá trị, thường gặp nhất là ở vế phải của phép gán:
Cả hai đều tạo ra hàm có thể gọi được. Những điểm khác nhau:
- Khai báo hàm được hoisting toàn bộ. Biểu thức hàm tuân theo quy tắc hoisting của biến mà nó được gán vào (
constvàletnằm trong temporal dead zone cho đến khi dòng đó chạy). - Khai báo hàm bắt buộc phải có tên. Biểu thức hàm có thể ẩn danh, dù đặt tên sẽ giúp stack trace dễ đọc hơn.
- Khai báo hàm không thể xuất hiện ở bất cứ đâu. Trong strict mode, một
function foo() {}bên trong khốiifhoạt động không nhất quán giữa các engine, nên ở đó hãy dùng biểu thức hàm.
Với phần lớn các hàm hỗ trợ ở cấp cao nhất của một tệp, khai báo hàm đọc rất rõ ràng. Với hàm được truyền làm đối số hoặc gán cho thuộc tính, biểu thức hàm (hoặc arrow function) là lựa chọn thường thấy.
Đặt tên và phong cách
Tên hàm là một lời hứa về việc hàm đó làm gì. Bỏ thêm vài ký tự để chọn một cái tên mô tả rõ ràng sẽ được đền đáp ngay lần đầu có người đọc code.
Một vài quy ước nên áp dụng:
- Tên hàm nên là động từ:
fetchProfile,computeTotal,sendEmail. - Dùng
camelCase, đó là chuẩn của JavaScript. - Hàm trả về boolean thường bắt đầu bằng
is,hashoặccan:isValid,hasAccess,canEdit.
Mục tiêu là để chỗ gọi hàm đọc lên như một câu văn.
Một ví dụ nhỏ hoàn chỉnh
Ghép các phần lại với nhau: tham số, return sớm, một cái tên mô tả rõ ràng:
Một khai báo hàm, một câu lệnh bảo vệ, một giá trị trả về rõ ràng. Cấu trúc đó đủ cho phần lớn các hàm bạn sẽ viết.
Tiếp theo: arrow function
Khai báo hàm là công cụ chủ lực, nhưng JavaScript hiện đại dùng rất nhiều một cú pháp ngắn gọn hơn là arrow function, đặc biệt cho callback và các hàm một dòng. Chúng trông khác, hoạt động khác khi đụng đến this, và đó là chủ đề của trang tiếp theo.
Câu hỏi thường gặp
Khai báo hàm trong JavaScript như thế nào?
Dùng từ khóa function, theo sau là tên hàm, cặp ngoặc tròn chứa tham số và một khối lệnh. Ví dụ: function greet(name) { return 'Hi, ' + name; }. Bạn gọi hàm bằng cách viết tên hàm kèm cặp ngoặc tròn: greet('Ada').
Hoisting hàm trong JavaScript là gì?
Khai báo hàm được đưa lên đầu phạm vi chứa nó trước khi code chạy, nên bạn có thể gọi hàm ở dòng nằm phía trên chỗ nó được viết. Điều này chỉ áp dụng cho khai báo function: biểu thức hàm và arrow function gán cho let hoặc const không được hoisting theo cùng cách.
Khai báo hàm và biểu thức hàm khác nhau thế nào?
Khai báo hàm đứng riêng như một câu lệnh (function greet() {}) và được hoisting. Biểu thức hàm gán một hàm cho một biến (const greet = function() {}) và tuân theo quy tắc hoisting thông thường của biến. Khai báo hàm luôn có tên; biểu thức hàm thường là hàm ẩn danh.