Nếu bạn là người mới học hoặc chưa hiểu cách thread of execution, execution context, và call stack hoạt động, bạn phải đọc bài này để có thể hoàn toàn hiểu khi đọc bài viết dưới đây.
Giới thiệu
Closure là khi một function "nhớ" về lexical scope của nó, ngay cả khi nó được thực thi ở ngoài lexical scope đó. Hãy xem ví dụ sau đây:
Tạm thời cứ hiểu trừu tượng là thế, giờ ta sẽ tìm hiểu cách closure hoạt động.
Closure
Ta sẽ tìm hiểu cách closure hoạt động qua đoạn code dưới đây.
Khi chương trình trên được chạy, JavaScript:
- Khai báo function
outer
ở global memory - Thực thi dòng code 9:
const myFunction = outer()
, khi gọi functionouter
, JavaScript- Đẩy outer() vào call stack
- Tạo một execution context tương ứng
- Khai báo biến
counter
và gán giá trị0
cho nó ở local memory - Khai báo function
incrementCounter
ở local memory - return
incrementCounter
chomyFunction
ở global memory
Sau khi đã thực thi xong, function execution context đó sẽ bị xoá, ở call stack thì outer()
cũng sẽ bị lấy ra, trở lại global().
- Lúc này thread of execution đi đến dòng code 10, và thực thi:
myFunction();
, lúc này JavaScript đẩymyFunction()
vào call stack và tạo một execution context tương ứng cho nó. TrongmyFunction
, ta thực thicounter++
, nhưng tìmcounter
ở đâu?- Đầu tiên là tìm
counter
ở local memory. Không thấy - Chúng ta thực thi
myFunction
ở đâu? Global! Vậy tìmcounter
ở global memory, nhưng cócounter
nào ở global memory đâu?
- Đầu tiên là tìm
Để biết phải tìm counter
ở đâu, ta quay lại dòng code 9
một xíu, thật ra khi dòng code 9 được thực thi, ở bước cuối khi trả về (return) thì JavaScript không chỉ lưu code trong function incrementCounter
vào myFunction
, mà còn mang theo dữ liệu xung quanh (lexical scope) mà nó có tham chiếu tới.
Đây là lý do ở phần giới thiệu mình có nói:
Closure là khi một function "nhớ" về lexical scope của nó, ngay cả khi nó được thực thi ở ngoài lexical scope đó.)
Vậy giờ chỉ cần đến myFunction
tìm counter
và thực thi dòng code là tăng giá trị của counter
từ 0
thành 1
.
Sau khi thực thi xong, function execution context này bị xoá (nếu local memory của nó có dữ liệu thì cũng bị xoá luôn), ở call stack thì myFunction()
cũng sẽ bị lấy ra, trở lại global().
4. Lúc này JavaScript tiếp tục thực thi dòng code 11, tương tự như dòng code 10, sau khi thực thi thì giá trị của counter
này là 2
.
Hãy tưởng tượng chúng ta có một thuộc tính ẩn [[scope]]
- liên kết đến tất cả dữ liệu xung quanh nơi khai báo incrementCounter
. Và nếu những dữ liệu đó được incrementCounter
tham chiếu đến, sẽ đi cùng với myFunction
và chúng ta chỉ có thể truy cập dữ liệu xung quanh này khi thực thi myFunction
.
Chúng ta gọi những dữ liệu xung quanh này là backpack.
- Khi thực thi dòng code 13, tương tự với dòng code 9, JavaScript đẩy
outer()
vào call stack, một execution context mới lại được tạo ra để thực thiouter
. Và vì nó hoàn toàn mới, local memory của nó cũng mới, nên khi JavaScript trả về (return) định nghĩa của functionincrementCounter
chomyNewFunction
thì nó mang theo một backpack hoàn toàn mới,counter
ở backpack này giá trị là0
.
- Tiếp theo JavaScript thực thi dòng code 14, và khi cần tìm
counter
để thực thi, nó sẽ về global code và tìmcounter
tại backpack ởmyNewfunction
và nâng giá trị củacounter
ở đây lên1
.
Kết bài
Như vậy là ta đã tìm hiểu cách closure hoạt động.
Cảm ơn mọi người đã đọc. Em/ mình viết bài này trong lúc học JavaScript nên nếu có gì sai sót hoặc hiểu chưa tới mong mọi người góp ý ở bình luận để em có thể sửa cũng như để lại bài viết cho các bạn mới học khác.