Closures trong Javascript

Closures trong Javascript

Javasript là một ngôn ngữ phổ biến hiện nay. Người biết về javascript thì nhiều nhưng người biết về một số corner của JS thì chắc không nhiều đến thế. Một trong những corner của JS là Closure. JS là một ngôn ngữ khá đặc biệt, đặc biệt ở chỗ JS mang hơi hướng của lập trình hàm (functional programming), khi mà function ở JS cũng là một first-class object, tức là function có thể được tạo mới (contruct new) tại run-time, được lưu dưới dạng một cấu trúc dữ liệu, được truyền qua parammeter, được dùng như một giá trị trả về. Chính vì đặc điểm ấy khiến cho Closure của JS không giống như những ngôn ngữ phổ biến khác.

Closure là gì? Closure là một hàm được viết lồng vào bên trong một hàm khác (hàm cha), có có thể sử dụng biến toàn cục, biến cục bộ của hàm cha và biến cục bộ của chính nó (lexical scoping).

Lexical Scoping

Cùng xem ví dụ sau:

Kết quả

init() một biến cục bộ name và một hàm getName(). Hàm getName() được khai báo bên trong hàm init() và chỉ tồn tại bên trong hàm init(). Hàm displayName() không có biến cục bộ nào của chính nó. Tuy nhiên, hàm getName() truy cập đến biến name vốn được định nghĩa ở hàm cha,  init() . Nếu bên trong hàm  getName() có khai báo biến cục bộ của chính nó, biến đó sẽ được sử dụng. Ta có thể thấy kết quả hiện lên màn hình là “Mozilla”, do đó hàm getName() đã sử dụng biến cục bộ của hàm cha init(). Đây chính là một ví dụ của lexical scoping.

Closure

Một số đặc điểm quan trọng của Closures trong javascript.

Hàm closures có thể truy cập tới biến số của hàm chứa nó, dù cho hàm đó đã return

Tiếp tục xem ví dụ sau:

Kết quả

Hình ảnh này chưa có thuộc tính alt; tên tệp của nó là image-7.png

Kết quả đoạn code trên tương tự như ví dụ về hàm init(), vậy sự khác nhau là gì?
Khi gọi hàm callName() sẽ return về hàm getName() và chưa hề chạy qua đoạn code trong hàm getName().
Trong những ngôn ngữ lập trình khác, biến cục bộ chỉ tồn tại trong quá trình hàm thực thi. Khi hàm chạy callName() chạy xong thì biến name sẽ không được sử dụng nữa. Nhưng ở đây nó vẫn trả về kết quả giống như hàm init() ở trên. Đây chính là một tính chất đặc biệt của javascript.
Trong trường hợp này, myName đang tham chiếu đến một Instance getName() được tạo ra khi chạy callName(). Instance getName() sẽ duy trì ở lexical environment, do đó biến name vẫn tồn tại.

Hàm closures lưu trữ biến số của outer function theo kiểu tham chiếu.

Xét ví dụ dưới đây:

Hàm objId trả về một đối tượng bao gồm 2 hàm closures là getId và setId. Các hàm Closures này sử dụng chung một biến cục bộ là id.
Ban đầu, gọi myObject.getId() thì kết quả trả về là 1 (giá trị của biến cục bộ). Sau đó, gọi myObject.setId(10). Nếu hàm closures chỉ lưu biến cục bộ theo giá trị thì nghĩa là giá trị của biến cục bộ id sẽ không thay đổi. Nhưng khi gọi tiếp myObject.getId() thì giá trị trả về là 10. Chứng tỏ, hàm Closures phải lưu biến cục bộ theo kiểu tham chiếu.

Closure Scope Chain

Mỗi closure chúng ta có 3 scopes:-

  • Scope cục bộ
  • Scope của function chứa closure
  • Scope global

Chúng ta có thể truy cập đến cả 3 scope này trong closure tuy nhiên sẽ ra sau nếu chúng lồng nhiều Closure với nhau. Như ví dụ sau:

Với ví dụ trên, chúng ta có thể nói toàn bộ closure sẽ có cùng scope với function cha.

Kết luận

Như vậy qua bài viết này chúng ta cần nắm được khái niệm về Closure trong javascript, và một số best practices trong việc sử dụng closure. Closure trong javascript hay sử dụng để tạo ra một cái bao mà các thứ trong đấy không được nhìn thấy bởi bên ngoài nhưng vẫn truy cập được từ bên trong, và thường được áp dụng cho một số design pattern trong js (tiêu biểu nhất là module pattern). Nắm được một số điểm quan trọng của Closure trong javascript và hiểu rõ về Closures Scope.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *