Hi 🤓 Cảm ơn bạn đã ghé thăm blog này, nếu những bài viết trên blog giúp ích cho bạn. Bạn có thể giúp blog hiển thị quảng cáo bằng cách tạm ngừng ad blocker 😫 và để giúp blog duy trì hoạt động nếu bạn muốn.
Cảm ơn bạn!

Hi, hôm nay chúng ta sẽ tìm hiểu về khái niệm closure trong JavaScript nhé. Đây là một khái niệm có thể khiến cho một số bạn cảm thấy khó hiểu.

Trước khi chúng ta tìm hiểu về closure trong JavaScript. Mình đã viết một số bài liên quan đến closure đó là scopelexical scope. Các bạn nên đọc bài viết về 2 khái niệm này nếu chưa hiểu chúng là gì 😁.

Bài viết về scopelexical scope:

Sau khi tìm hiểu về 2 khái niệm này thì chúng ta có thể dễ dàng hơn trong việc hiểu closure là gì rồi ^^.

Closure trong JavaScript là gì?

Như chúng ta đã biết, lexical scope cho phép một biến được khai báo bên ngoài một hàm, có thể truy xuất được khi sử dụng biến này bên trong một hàm khác.

Chúng ta cùng xem lại ví dụ trong bài viết trước:

function hamOBenNgoai() {
  // scope
  let text = 'outside';
  function hamOBenTrong() {
    // scope
    console.log(text); // outside
  }
  hamOBenTrong(); // gọi hàm
}
hamOBenNgoai();

Bên trong scope hamOBenTrong(), chúng ta có thể truy xuất biến text được khai báo từ hamOBenNgoai(), vì chúng ta đã truy xuất text trong lexical scope của nó. Và chúng ta cũng thấy hamOBenTrong() được gọi bên trong lexical scope của hàm này.

Bây giờ chúng ta sẽ thay đổi đoạn code trên, gọi hamOBenTrong() bên ngoài lexical scope (scope của hamOBenNgoai()) của nó xem chuyện gì sẽ xảy ra nhé!

Thay vì gọi hàm, mình sẽ return về hamOBenTrong() như sau:

function hamOBenNgoai() {
  // scope
  let text = 'outside';
  function hamOBenTrong() {
    // scope
    console.log(text); // outside
  }
  return hamOBenTrong; // return thay vì gọi hàm
}

function run(){
  const hamBenNgoai = hamOBenNgoai();
  hamBenNgoai();
}

run();

Bạn đoán thử xem, đoạn code trên có chạy thành công không? Liệu hamOBenTrong() có thể truy xuất text? Câu trả lời là .

Trong JavaScript, các local variable tồn tại trong quá trình function thực thi. Nhưng khi function thực thi xong, các biến này sẽ không còn tồn tại và chúng sẽ không thể truy xuất được nữa.

Tuy nhiên, các bạn có thể thấy sau khi hamOBenNgoai() thực thi, inner function (function được return) là hamOBenTrong() vẫn có thể truy xuất biến text của hamOBenNgoai() 😮, tại sao lại như thế? Cái mà bạn đang thắc mắc chính là một đặc điểm của closure.

Khi hamOBenNgoai() thực thi, hamOBenTrong sẽ ghi nhớ các biến từ bên ngoài(Biến không được khởi tạo bên trong hàm) mà nó sử dụng.

Vì lý do đó, bất kể bạn gọi hamOBenTrong ở đâu thì nó có thể truy xuất đến các biến này. Nói cách khác hamOBenTrong sẽ ghi nhớ các biến được sử dụng trong lexical scope của nó.

Ở ví dụ trên, ta có thể gọi hamOBenTrong() là một closure.

Vậy function là một closure khi function này có thể sử dụng các biến từ lexical scope của nó, ngay cả khi function này thực thi bên ngoài lexical scope của nó. Hay các inner function có thể sử dụng các biến từ parent scope của nó, ngay cả khi parent function đã thực thi xong thì function đó cũng là một closure.

Ví dụ về closure

Ta cùng xem một vài ví dụ về closure trong Javascript nhé!

Cùng xem một ví dụ về setTimeout():

const text = 'homiedev.com';
setTimeout(function showText() {
  console.log(text); // homiedev.com
}, 1000);

Ở ví dụ trên showText() là một closure vì trong function có sử dụng biến text từ lexical scope của nó.

function increment() {
  let sum = 0;
  return function plusByOne() {
    return sum++;
  };
}

const showValue = increment();
showValue(); // 0
showValue(); // 1
showValue(); // 2

Trong ví dụ này plusByOne() là một closure vì nó sử dụng biến bên ngoài là sum.

function plusByOne() được gán cho biến showValue khi thực thi xong increment(). Câu hỏi là tại sao khi thực thi showValue(), ta nhận được giá trị khác nhau mà không phải là giá trị 0.

Câu trả lời là closure sẽ lưu trữ biến sum vào bộ nhớ theo kiểu reference. Tức là khi bạn thay đổi giá trị sum thì nó sẽ update lại giá trị sum.

Lần tiếp theo gọi showValue() nó sẽ lấy sum xem giá trị bao nhiêu và tiếp tục thực hiện sum++. Đây là lí do tại sao ta nhận được kết quả như trên.

Bạn có thể dùng dir để hiểu kết quả trên hơn.

console.dir(showValue);

Ta sẽ có các properties của showValue như hình dưới đây:

example_closure

Ở hình trên ta thấy closure đã ghi nhớ biến được sử dụng là sum với giá trị khởi tạo là 0 trong một object. Khá hay đúng không 😁. Hy vọng bài viết này sẽ giúp bạn hiểu closure trong JavaScript hơn ^^.

Kết luận

Như vậy là chúng ta đã tìm hiểu về closure trong JavaScript. Đây là một khái niệm phải nói là khá khó cho các bạn nào mới tiếp cận JavaScript.

Mình hy vọng bài viết sẽ giúp ích cho các bạn. Hẹn gặp các bạn trong các bài viết tiếp theo ^^.

Có thể bạn thích ⚡
homiedev
About Me

Hi, I'm @devnav. Một người thích chia sẻ kiến thức, đặc biệt là về Frontend 🚀. Trang web này được tạo ra nhằm giúp các bạn học Frontend hiệu quả hơn 🎉😄.

Chúc các bạn tìm được kiến thức hữu ích trong blog này 😁😁.