Understand JavaScript - Closures (P2)
Trong bài trước mình đã giới thiệu sơ qua về Closure , một tính chất rất quan trọng của JavaScript
Để giải thích rõ hơn về Closure mình sẽ giải thích một ví dụ :

Dùng kiến thức về Closure đã học ở bài trước để giải thích hiện tượng này.
- Khi chạy chương trình
- EC Global được tạo ra , JSE chạy code bên trong EC Global qua 2 giai đoạn Create Phase và Execution Phase..ở giai đoạn CreatePhase hàm buildFunction hàm buildFunction và biến fs được hosting.
- Trong giai đoạn Execution Phase chạy đến dòng 13 nó khi hàm buildFunction được gọi nó sẽ tạo ra một EC mới.
Trong EC hàm build chúng có 2 biến i và mảng arr , nhưng điều chúng ta cần xác định là giá trị của 2 biến đó là gì khi chúng ta return arrr ?
Soi xét từng dòng chúng ta thấy , khi vòng lặp chạy , lần đầu tiên nó đẩy một hàm vào mảng nhưng bạn chú ý rằng hàm được đẩy vào nhưng nó KHÔNG chạy đây là kiến thức về function expression mình đã đề cập ở bài trước , mình tạo ra một hàm đơn giản là mình đang tạo ra một object đặc biết có thêm một property code , hàm không chạy nó đơn giản là chỉ được tạo ra.
Tiếp tục như vậy vòng lặp chạy và thêm một hàm khác vào mảng .
=> Bạn nên hiểu rằng trong mỗi lần lặp mình chỉ đơn giản tạo ra một Object (vì hàm thực ra là một object) và mình đẩy khối code trong hàm vào thuộc tính code của Object đó , mình chỉ đơn giản đẩy console.log(i) vào property code mà thôi , còn giá trị của i chưa được xác định.
=> Giá trị của i sẽ được xác định khi chúng ta invoked các hàm lúc đó JSE sẽ tìm nó trong thân hàm , Scope Chain hoặc Closure ^^.
=> Cuối cùng vòng lặp kết thúc trong mảng có 3 hàm và biến i có giá trị = 3.
Khi mình trả về mảng arr những gì trong vùng nhớ của EC build có
- biến i =3;
- mảng arr chứa 3 hàm ẩn danh (chưa được invoked)
=> Cuối cùng hàm build bị bật ra khỏi ECS nhưng .....những gì nằm trong vùng nhớ của EC Build vẫn nằm đó , nó không biến mất trong bài trước mình đã trình bày về phần này.
- JSE thực thi code theo từng ký tự ,từng dòng từ trái sang phải khi nó chạy đến fs[0]()
điều quen thuộc lại xảy ra , một EC mới được tạo ra.
- Chúng ta thấy rằng hàm này nó yêu cầu biến i nhưng biến i không có trong Variable Environment của EC này nên JSE đi lên ScopeChain để tìm biến i , nó đi tìm trong những tham chiếu bên ngoài của EC , nó sẽ bắt đầu tìm trong vùng nhớ của EC Build vì Build chính là hàm nơi nó được định nghĩa khởi tạo (như mình đã nói bên trên vùng nhớ này không bị mất đi như EC Build)
=> Khi đã tìm thấy biến i nó log => Hàm kết thúc.
Tiếp tục chúng ta chạy đến với hàm tiếp theo , một EC mới được tạo ra , nhưng nó có mọi thành phần đều giống với EC bên trên , nó tham chiếu đến cùng một outer environment vì bọn nó đều được ạo ra trong cùng một hàm vì vậy cách thức nó tìm biến i theo scope chain cũng giống y hệt nhau.=> nó lại tìm được biến i = 3.
Như vậy cả 3 hàm được chạy đều có cùng cha mẹ để JSE nó tìm kiếm biến , giống như bạn đang nói chuyện với 3 đứa trẻ và bạn hỏi bọn chúng cha mỗi đứa bao nhiêu tuổi vậy.
=> Bạn nhớ rằng khi mình gọi một hàm , nó sẽ chỉ cho bạn biết những giá trị trong vùng nhớ của hàm cha của chúng là gì ? nhưng bạn chú ý rằng là tại thời điểm gọi hàm chứ không phải ở thời điểm khởi tạo hàm đây là một điểm rất quan trọng dẫn đến giá trị của i được lấy ra không phải tại thời điểm vòng lặp chạy , giá trị của i được lấy ra ở thời điểm hàm được gọi.
- Vậy làm thế nào để chúng ta có thể bảo tồn giá trị của biến i trong mỗi lần vòng lặp chạy ?? Bạn muốn rằng mỗi lần vòng lặp chạy EC của hàm sẽ ngay lập tức lấy giá trị biến i tại thời điểm đó như vậy bạn muốn hàm được khởi tạo ra chạy luôn thì mới tạo ra EC từ đó tạo vùng nhớ để lưu trữ biến đc, áp dụng kiến thức của bài trước bạn chỉ cần sử dụng IIFE gọi thực thi hàm ngay sau khi khởi tạo hàm,
Bạn thấy rằng trong đoạn code trên mình đã áp dụng kiến thức của IIFE khi vòng lặp chạyhàm được đẩy vào mảng nhưng
- Ngay khi hàm khởi tạo thì nó được thực thi luôn => Tạo ra EC mới và lưu biến i vào trong Variable Environment của nó khi hàm này trả về một hàm khác mình sử dụng biến j gán j = i => lưu trữ được giá trị biến i qua mỗi lần lặp.
Khi bạn tạo ra một Closure bạn đang JS rằng hãy bảo tồn trạng thái của những thứ bên trong hàm và tất nhiên chỉ các biến được mới được Closure.
- Lưu ý thêm rằng nếu bạn sử dụng let để khai báo biến thì sẽ khắc phục được tình trạng này vì khi bạn sử dụng let trong vòng lặp , phạm vi của biến i chỉ ở trong vòng lặp mà thôi khác với biến i được khai báo theo kiểu var nó có phạm vi cả ở trong vòng lặp lẫn hàm chứa vòng lặp.
Mỗi lần vòng lặp chạy nó sẽ tạo ra một EC mới hoàn toàn như vậy EC do vòng lặp tạo ra sẽ bao bọc lấy EC của hàm , EC do vòng lặp tạo ra lưu trữ giá trị của biến i trong Variable Environmet nên EC của hàm sẽ lấy biến i trong đó luôn.
Để giải thích rõ hơn về Closure mình sẽ giải thích một ví dụ :
Bạn đoán xem kết quả của chương trình bên trên là gì ??
Dùng kiến thức về Closure đã học ở bài trước để giải thích hiện tượng này.
- Khi chạy chương trình
- EC Global được tạo ra , JSE chạy code bên trong EC Global qua 2 giai đoạn Create Phase và Execution Phase..ở giai đoạn CreatePhase hàm buildFunction hàm buildFunction và biến fs được hosting.
- Trong giai đoạn Execution Phase chạy đến dòng 13 nó khi hàm buildFunction được gọi nó sẽ tạo ra một EC mới.
Trong EC hàm build chúng có 2 biến i và mảng arr , nhưng điều chúng ta cần xác định là giá trị của 2 biến đó là gì khi chúng ta return arrr ?
Soi xét từng dòng chúng ta thấy , khi vòng lặp chạy , lần đầu tiên nó đẩy một hàm vào mảng nhưng bạn chú ý rằng hàm được đẩy vào nhưng nó KHÔNG chạy đây là kiến thức về function expression mình đã đề cập ở bài trước , mình tạo ra một hàm đơn giản là mình đang tạo ra một object đặc biết có thêm một property code , hàm không chạy nó đơn giản là chỉ được tạo ra.
Tiếp tục như vậy vòng lặp chạy và thêm một hàm khác vào mảng .
=> Bạn nên hiểu rằng trong mỗi lần lặp mình chỉ đơn giản tạo ra một Object (vì hàm thực ra là một object) và mình đẩy khối code trong hàm vào thuộc tính code của Object đó , mình chỉ đơn giản đẩy console.log(i) vào property code mà thôi , còn giá trị của i chưa được xác định.
=> Giá trị của i sẽ được xác định khi chúng ta invoked các hàm lúc đó JSE sẽ tìm nó trong thân hàm , Scope Chain hoặc Closure ^^.
=> Cuối cùng vòng lặp kết thúc trong mảng có 3 hàm và biến i có giá trị = 3.
Khi mình trả về mảng arr những gì trong vùng nhớ của EC build có
- biến i =3;
- mảng arr chứa 3 hàm ẩn danh (chưa được invoked)
=> Cuối cùng hàm build bị bật ra khỏi ECS nhưng .....những gì nằm trong vùng nhớ của EC Build vẫn nằm đó , nó không biến mất trong bài trước mình đã trình bày về phần này.
- JSE thực thi code theo từng ký tự ,từng dòng từ trái sang phải khi nó chạy đến fs[0]()
điều quen thuộc lại xảy ra , một EC mới được tạo ra.
- Chúng ta thấy rằng hàm này nó yêu cầu biến i nhưng biến i không có trong Variable Environment của EC này nên JSE đi lên ScopeChain để tìm biến i , nó đi tìm trong những tham chiếu bên ngoài của EC , nó sẽ bắt đầu tìm trong vùng nhớ của EC Build vì Build chính là hàm nơi nó được định nghĩa khởi tạo (như mình đã nói bên trên vùng nhớ này không bị mất đi như EC Build)
=> Khi đã tìm thấy biến i nó log => Hàm kết thúc.
Tiếp tục chúng ta chạy đến với hàm tiếp theo , một EC mới được tạo ra , nhưng nó có mọi thành phần đều giống với EC bên trên , nó tham chiếu đến cùng một outer environment vì bọn nó đều được ạo ra trong cùng một hàm vì vậy cách thức nó tìm biến i theo scope chain cũng giống y hệt nhau.=> nó lại tìm được biến i = 3.
Như vậy cả 3 hàm được chạy đều có cùng cha mẹ để JSE nó tìm kiếm biến , giống như bạn đang nói chuyện với 3 đứa trẻ và bạn hỏi bọn chúng cha mỗi đứa bao nhiêu tuổi vậy.
=> Bạn nhớ rằng khi mình gọi một hàm , nó sẽ chỉ cho bạn biết những giá trị trong vùng nhớ của hàm cha của chúng là gì ? nhưng bạn chú ý rằng là tại thời điểm gọi hàm chứ không phải ở thời điểm khởi tạo hàm đây là một điểm rất quan trọng dẫn đến giá trị của i được lấy ra không phải tại thời điểm vòng lặp chạy , giá trị của i được lấy ra ở thời điểm hàm được gọi.
- Vậy làm thế nào để chúng ta có thể bảo tồn giá trị của biến i trong mỗi lần vòng lặp chạy ?? Bạn muốn rằng mỗi lần vòng lặp chạy EC của hàm sẽ ngay lập tức lấy giá trị biến i tại thời điểm đó như vậy bạn muốn hàm được khởi tạo ra chạy luôn thì mới tạo ra EC từ đó tạo vùng nhớ để lưu trữ biến đc, áp dụng kiến thức của bài trước bạn chỉ cần sử dụng IIFE gọi thực thi hàm ngay sau khi khởi tạo hàm,
Bạn thấy rằng trong đoạn code trên mình đã áp dụng kiến thức của IIFE khi vòng lặp chạyhàm được đẩy vào mảng nhưng
- Ngay khi hàm khởi tạo thì nó được thực thi luôn => Tạo ra EC mới và lưu biến i vào trong Variable Environment của nó khi hàm này trả về một hàm khác mình sử dụng biến j gán j = i => lưu trữ được giá trị biến i qua mỗi lần lặp.
Khi bạn tạo ra một Closure bạn đang JS rằng hãy bảo tồn trạng thái của những thứ bên trong hàm và tất nhiên chỉ các biến được mới được Closure.
- Lưu ý thêm rằng nếu bạn sử dụng let để khai báo biến thì sẽ khắc phục được tình trạng này vì khi bạn sử dụng let trong vòng lặp , phạm vi của biến i chỉ ở trong vòng lặp mà thôi khác với biến i được khai báo theo kiểu var nó có phạm vi cả ở trong vòng lặp lẫn hàm chứa vòng lặp.
Mỗi lần vòng lặp chạy nó sẽ tạo ra một EC mới hoàn toàn như vậy EC do vòng lặp tạo ra sẽ bao bọc lấy EC của hàm , EC do vòng lặp tạo ra lưu trữ giá trị của biến i trong Variable Environmet nên EC của hàm sẽ lấy biến i trong đó luôn.
Nhận xét
Đăng nhận xét