Closures trong JavaScript

Closure là gì ?

Closure là một khía cạnh quan trọng của Javascript mà các lập trình viên nên biết, bài viết hôm nay chúng ta cùng tìm hiểu những điều cơ bản về Closure, cách thức hoạt động của Closure trong Javascript. Chúng ta sẽ bắt đầu bằng cách xem xét hai định nghĩa về Closure:

Định nghĩa #1

Closure là một chức năng (function) có quyền truy cập vào phạm vi phụ huynh (parent scope) ngay cả khi phạm vi đã đóng.

Định nghĩa #2

Closure là sự kết hợp của một chức năng và môi trường mà chức năng đó được khai báo

Đầu tiên chúng ta cần tìm hiểu phạm vi (scope) trong Javascript. Phạm vi (scope) về cơ bản là tuổi thọ của một biến trong Javascript, nơi mà một biến được định nghĩa đóng một vai trò trong bao lâu và những chức năng (functions) trong chương trình có quyền truy cập nó.

Ví dụ:

Khi tạo một hàm trong Javascript, nó có quyền truy cập các biến được tạo bên trong và bên ngoài hàm. Các biến được tạo bên trong một hàm là các biến được định nghĩa cục bộ. Một biến địa phương (local variable) chỉ có thể được truy cập trong phạm vi chức năng mà nó được định nghĩa. Trong ví dụ dưới đây, bạn sẽ thấy rằng nếu chúng ta cố gắng lấy giá trị của words ngoài chức năng (function) chúng ta nhận được một lỗi tham chiếu. Đó là bởi vì các từ là một biến địa phương:

// Example of accessing variables INSIDE the function
// words is a LOCAL variable
function speak(){
  var words = 'hi'; 
  console.log(words);
}
speak(); // 'hi'
console.log(words); // Uncaught ReferenceError: words is not defined

Tương phản với ví dụ này nơi chúng ta định nghĩa words trong phạm vi toàn cầu (global scope). Điều này có nghĩa là nó có thể truy cập tới cả trong và ngoài hàm (function):

// Example of accessing variables OUTSIDE the function
// words is a GLOBAL variable
var words = 'hi';
function speak(){ 
  console.log(words);
}
speak(); // 'hi' 
console.log(words); // 'hi'

Nested Functions

Điều gì xảy ra khi chúng ta viết một hàm bên trong một hàm khác? Chúng ta cùng xem ví dụ sau để biết nhé 👌 : Đầu tiên tạo một hàm có tên speak. 'speak sẽ trả về một hàm tên logIt. Và cuối cùng hàm logIt hiển thị giá trị biến words ra màn hình console, trong trường hợp này có nghĩa là nó ghi 'hi' vào giao diện điều khiển.

 function speak() {
      return function logIt() {
          var words = 'hi';
          console.log(words);
      }
 }

Chúng ta tạo ra một biên và gán nó cho hàm speak:

var sayHello = speak();

Bây giờ chúng ta có thể thấy giá trị của sayHello bằng cách gọi biến nhưng không gọi hàm bên trong:

sayHello;
//  function logIt() {
//    var words = 'hi';
//    console.log(words);
//  }

Như mong đợi, sayHello đang tham chiếu chức năng bên trong (inner function) của hàm speak. Điều này có nghĩa là nếu chúng ta chạy sayHello () trong giao diện điều khiển (console), nó sẽ gọi và chạy hàm logIt ():

sayHello();
// 'hi'

Nó hoạt động! Nhưng đây không phải là điều đặc biệt. Hãy di chuyển một dòng mã và xem những gì thay đổi. Hãy xem ví dụ dưới đây. Tôi đã di chuyển khai báo của chúng ta chuyển biến words ra bên ngoài hàm logI và bên trong hàm speak ():

function speak() {
  var words = 'hi';
  return function logIt() {
    console.log(words);
  }
}

Giống như trước, khai báo một biến và gán nó cho chức năng speak của chúng ta:

var sayHello = speak();

Bây giờ chúng ta sẽ xem xét biến sayHello của chúng ta đang tham chiếu:

sayHello
//  function logIt() {
//    console.log(words);
//  }

Không có định nghĩa biến words. Vậy điều gì sẽ xảy ra khi chúng ta gọi hàm?

sayHello();
// 'hi'

Nó vẫn đúng. Và đó là bởi vì bạn vừa trải qua những ảnh hưởng của một Closure. Trong trường hợp này, phạm vi chức năng củaspeak () của chúng ta đã đóng. Điều này có nghĩa là các biến var words = 'hi' cũng nên kết thúc. Tuy nhiên, trong JavaScript chúng ta có khái niệm nhỏ được gọi là Closure: chức năng bên trong của chúng ta duy trì một tham chiếu đến phạm vi mà nó đã được tạo ra. Điều này cho phép hàm logit () vẫn truy cập vào các biến từ - ngay cả sau khi speak () đã đóng.

function speak() {
  var words = 'hi';
  return function logIt() {
    console.log(words);
  }
}

Trên đây là phần giới thiệu cơ bản về Closure và cách thức hoạt động của một Closure trong Javascript.