Trong JavaScript, một hàm không phải là một cấu trúc mà là một loại giá trị đặc biệt.

Cú pháp mà chúng ta đã sử dụng trước đây được gọi là Khai báo hàm :

function sayHi() {
  alert( "Hello" );
}

Có một cú pháp khác để tạo một hàm được gọi là biểu thức hàm .

Nó trông như thế này:

let sayHi = function() {
  alert( "Hello" );
};

Ở đây, hàm được tạo và gán cho một biến nào đó, giống như bất kỳ giá trị nào khác. Bất kể hàm được định nghĩa như thế nào, nó chỉ là một giá trị được lưu trữ trong biến sayHi.

Ý nghĩa của các mẫu code này là như nhau: “tạo một hàm và đưa nó vào biến sayHi“.

Chúng ta thậm chí có thể in ra giá trị đó bằng cách sử dụng alert:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

function sayHi() {
  alert( "Hello" );
}

alert( sayHi ); // shows the function code

Xin lưu ý rằng dòng cuối cùng không chạy hàm, vì không có dấu ngoặc đơn sau sayHi. Có những ngôn ngữ lập trình trong đó chỉ cần đề cập đến tên hàm sẽ gây ra sự thực thi của nó, nhưng JavaScript không giống như vậy.

Trong JavaScript, một hàm là một giá trị, vì vậy chúng ta có thể coi nó là một giá trị. Đoạn code trên cho thấy chuỗi của nó, đó là source code mà thôi.

Chắc chắn, một hàm là một giá trị đặc biệt, theo nghĩa mà chúng ta có thể gọi nó như thế này sayHi().

Nhưng nó vẫn là một giá trị. Vì vậy, chúng ta có thể làm việc với nó như với các loại giá trị khác.

Chúng ta có thể sao chép một hàm sang một biến khác:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

function sayHi() {   // (1) create
  alert( "Hello" );
}

let func = sayHi;    // (2) copy

func(); // Hello     // (3) run the copy (it works)!
sayHi(); // Hello    //     this still works too (why wouldn't it)

Dưới đây là những gì xảy ra ở trên một cách chi tiết:

  1. Khai báo hàm (1)tạo hàm và đặt nó vào biến có tên sayHi.
  2. Dòng (2)sao chép nó vào biến func. Xin lưu ý một lần nữa: không có dấu ngoặc đơn sau sayHi. Nếu có, thì func = sayHi()sẽ ghi kết quả của gọi hàmsayHi() vào func, chứ không phải chính hàm sayHi đó.
  3. Bây giờ hàm có thể được gọi là từ cả hai sayHi()func().

Lưu ý rằng chúng ta cũng có thể đã sử dụng Biểu thức hàm để khai báo sayHi, trong dòng đầu tiên:

let sayHi = function() {
  alert( "Hello" );
};

let func = sayHi;
// ...

Mọi thứ sẽ làm việc như nhau.

Tại sao có dấu chấm phẩy ở cuối?

Bạn có thể tự hỏi, tại sao cuối Biểu thức hàm lại có dấu chấm phẩy ;, nhưng câu lệnh hàm khai báo hàm không:

function sayHi() {
  // ...
}

let sayHi = function() {
  // ...
};

Đáp án đơn giản:

  • Không cần phải cho ;vào cuối khối code và cú pháp cấu trúc sau: if { ... }, for { }, function f { }, vv
  • Biểu thức hàm được sử dụng bên trong một câu lệnh : let sayHi = ...;, nó như một giá trị. Nó không phải là một khối code, mà là một nhiệm vụ. Dấu chấm phẩy ;được khuyến nghị ở cuối các câu lệnh, bất kể giá trị là gì. Vì vậy, dấu chấm phẩy ở đây không liên quan đến chính Biểu thức hàm, nó cho biết là chấm dứt câu lệnh.

1. Hàm Callback

Chúng ta hãy xem xét nhiều ví dụ về việc truyền hàm dưới dạng giá trị và sử dụng biểu thức hàm.

Chúng ta sẽ viết một hàm ask(question, yes, no)với ba tham số:question Nội dung câu hỏi Hàm yes chạy nếu câu trả lời là có, Hàm no chạy nếu câu trả lời là không có

Hàm ask có questionvà, tùy thuộc vào câu trả lời của người dùng, gọi yes()hoặc no():

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( "You agreed." );
}

function showCancel() {
  alert( "You canceled the execution." );
}

// usage: functions showOk, showCancel are passed as arguments to ask
ask("Do you agree?", showOk, showCancel);

Trong thực tế, các hàm như vậy là khá hữu ích. Sự khác biệt chính giữa đời thực askvà ví dụ trên là các hàm trong đời thực sử dụng các cách phức tạp hơn để tương tác với người dùng hơn là confirm đơn giản. Trong trình duyệt, hàm như vậy thường vẽ một cửa sổ câu hỏi trông đẹp mắt. Nhưng nó là một câu chuyện khác.

Các đối số showOkshowCancelcủa askđược gọi là hàm callback hoặc callback .

Ý tưởng là chúng ta sẽ truyền vào một hàm và hy vọng nó sẽ được gọi là trở lại sau này nếu cần thiết. Trong trường hợp của chúng tôi, hãy showOk sẽ được gọi lại cho câu trả lời của Yes, và showCancel sẽ được gọi lại cho câu trả lời của no.

Chúng ta có thể sử dụng Biểu thức hàm để viết cùng một hàm ngắn hơn nhiều:

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

ask(
  "Do you agree?",
  function() { alert("You agreed."); },
  function() { alert("You canceled the execution."); }
);

Ở đây, các hàm được khai báo ngay bên trong khi gọi ask(...). Họ không có tên, và vì vậy được gọi là ẩn danh. Các hàm như vậy không thể truy cập được bên ngoài ask(vì chúng không được gán cho các biến), nhưng đó chỉ là những gì chúng ta muốn ở đây.

Code như vậy xuất hiện trong các tập lệnh của chúng tôi rất nhiều, đó là tinh thần viết gọn nhẹ của JavaScript.

Hàm là một giá trị đại diện cho một hành động

Các giá trị thông thường như chuỗi hoặc số đại diện cho dữ liệu .

Một hàm có thể được coi là một hành động .

Chúng ta có thể chuyển nó giữa các biến và chạy khi chúng ta muốn.

2. So sánh Biểu thức Hàm vs khai báo hàm

Hãy hình thành sự khác biệt chính giữa khai báo hàm và Biểu thức hàm.

Đầu tiên, cú pháp: làm thế nào để phân biệt giữa chúng trong code.

  • Khai báo hàm: một hàm, được khai báo là một câu lệnh riêng, trong luồng code chính. // Function Declaration function sum(a, b) { return a + b; }
  • Hàm biểu thức: một hàm, được tạo bên trong một biểu thức hoặc bên trong một cú pháp cấu trúc khác. Ở đây, hàm được tạo ở phía bên phải của biểu thức gán =:
// Function Expression
let sum = function(a, b) {
  return a + b;
};

Biểu thức hàm được tạo khi thực thi chạy đến nó và chỉ có thể sử dụng được từ thời điểm đó.

Khi luồng thực thi chuyển sang phía bên phải của phép gán let sum = function…– ở đây chúng ta sẽ thực hiện hàm này và có thể được sử dụng (được gán, được gọi, v.v.) từ bây giờ.

Khai báo hàm thì khác nhau.

Một Khai báo hàm có thể được gọi sớm hơn khi nó được định nghĩa.

Ví dụ: Khai báo hàm toàn cục có thể nhìn thấy trong toàn bộ tập lệnh, bất kể nó ở đâu.

Đó là do các thuật toán nội bộ. Khi JavaScript chuẩn bị chạy tập lệnh, trước tiên, nó sẽ tìm các Khai báo hàm toàn cục trong đó và tạo các hàm đó trước. Chúng ta có thể nghĩ về nó như là một giai đoạn khởi tạo thành công.

Và sau khi tất cả các Khai báo hàm được xử lý, code được thực thi. Vì vậy, nó có quyền truy cập vào các hàm này.

Ví dụ, điều này hoạt động như sau:

sayHi("Cafedev.vn"); // Hello, Cafedev.vn

function sayHi(name) {
  alert( `Hello, ${name}` );
}

Câu lệnh hàm sayHiđược tạo khi JavaScript đang chuẩn bị khởi động tập lệnh và hiển thị ở mọi nơi trong nó.

Nếu nó là một biểu thức hàm, thì nó sẽ không hoạt động:

sayHi("Cafedev.vn"); // error!

let sayHi = function(name) {  // (*) no magic any more
  alert( `Hello, ${name}` );
};

Biểu thức hàm được tạo khi thực thi chạy tới chúng. Điều đó sẽ chỉ xảy ra trong dòng (*).

Một tính năng đặc biệt khác của Khai báo hàm là phạm vi khối của chúng.

Trong chế độ nghiêm ngặt(strict), khi khi Khai báo hàm nằm trong khối code, nó sẽ hiển thị ở mọi nơi trong khối đó. Nhưng không phải bên ngoài nó.

Ví dụ, hãy tưởng tượng rằng chúng ta cần khai báo một hàm welcome()tùy thuộc vào agebiến mà chúng ta nhận được trong thời gian chạy. Và sau đó chúng ta dự định sử dụng nó một thời gian sau.

Nếu chúng ta sử dụng Khai báo hàm, nó sẽ không hoạt động như dự định:

let age = prompt("What is your age?", 18);

// conditionally declare a function
if (age < 18) {

  function welcome() {
    alert("Hello!");
  }

} else {

  function welcome() {
    alert("Greetings!");
  }

}

// ...use it later
welcome(); // Error: welcome is not defined

Đó là bởi vì một Khai báo hàm chỉ hiển thị bên trong khối code mà nó nằm trong đó.

Đây là một ví dụ khác:

/*
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/

let age = 16; // take 16 as an example

if (age < 18) {
  welcome();               // \   (runs)
                           //  |
  function welcome() {     //  |
    alert("Hello!");       //  |  Function Declaration is available
  }                        //  |  everywhere in the block where it's declared
                           //  |
  welcome();               // /   (runs)

} else {

  function welcome() {
    alert("Greetings!");
  }
}

// Here we're out of curly braces,
// so we can not see Function Declarations made inside of them.

welcome(); // Error: welcome is not defined

Chúng ta có thể làm gì để welcomenhìn thấy bên ngoài if?

Cách tiếp cận đúng sẽ là sử dụng Biểu thức hàm và gán welcomecho biến được khai báo bên ngoài ifvà có mức độ hiển thị phù hợp.

Code này hoạt động như dự định:

let age = prompt("What is your age?", 18);

let welcome;

if (age < 18) {

  welcome = function() {
    alert("Hello!");
  };

} else {

  welcome = function() {
    alert("Greetings!");
  };

}

welcome(); // ok now

Hoặc chúng ta có thể đơn giản hóa hơn nữa bằng cách sử dụng toán tử dấu hỏi ?:

let age = prompt("What is your age?", 18);

let welcome = (age < 18) ?
  function() { alert("Hello!"); } :
  function() { alert("Greetings!"); };

welcome(); // ok now

Khi nào nên chọn Khai báo hàm so với biểu thức hàm?

Theo nguyên tắc thông thường, khi chúng ta cần khai báo một hàm, việc đầu tiên cần xem xét là cú pháp Khai báo hàm. Nó cho phép tự do hơn trong cách tổ chức code của chúng ta, bởi vì chúng ta có thể gọi các hàm như vậy trước khi chúng được khai báo.

Điều đó cũng tốt hơn cho khả năng đọc code, vì nó dễ tra cứu function f(…) {…}code hơn let f = function(…) {…};. Khai báo hàm sẽ đẹp hơn.

Vì vậy, nếu một số Khai báo hàm không phù hợp với chúng ta vì một số lý do hoặc chúng tôi cần cho một câu lệnh có điều kiện (chúng ta vừa xem một ví dụ), thì nên sử dụng Biểu thức hàm.

3. Tóm lược

  • Hàm là giá trị. Chúng có thể được chỉ định, sao chép hoặc khai báo ở bất kỳ vị trí nào của code.
  • Nếu hàm được khai báo là một câu lệnh riêng biệt trong luồng code chính, thì đó được gọi là Khai báo hàm.
  • Nếu hàm được tạo như là một phần của biểu thức, thì nó được gọi là Biểu thức Hàm.
  • Khai báo hàm được xử lý trước khi khối code được thực thi. Chúng có thể nhìn thấy ở mọi nơi trong khối code của mình.
  • Biểu thức hàm được tạo khi luồng thực thi chạy tới chúng.

Trong hầu hết các trường hợp khi chúng ta cần khai báo một hàm, câu lệnh hàm là thích hợp hơn, bởi vì nó được hiển thị trước bằng khai báo. Điều đó cho chúng ta linh hoạt hơn trong tổ chức code và thường dễ đọc hơn.

Vì vậy, chúng ta chỉ nên sử dụng Biểu thức hàm khi khai báo hàm không phù hợp với nhiệm vụ hiện tại. Chúng ta đã thấy một vài ví dụ về điều đó trong chương này và sẽ thấy nhiều hơn trong tương lai.

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!