雖然之前提過閉包是一個可以把內部函式之外的屬於其父函式的可視變數或物件都包含進來,但實際上在該原則之下有時候要判斷一個閉包所涵蓋的變數範圍究竟精確到哪個程度仍然不足,所以我照書指引寫了一個自己測試內部函式存取閉包變數的程式來檢測一些閉包可視範圍的細節,大致上歸納出了三點。
一個JavaScript內部函式形成的閉包可存取的範圍:
- 外部函式以內所有變數
- 與外部函式同層級且必須定義在內部函式被呼叫之前的所有變數
- 要使用內部函式形成的閉包之前,外部函式必須被呼叫或建立
首先,外部函式以內所有變數 將可被閉包所包覆相當合理,這是第一層建立的最接近閉包一群變數。
接著,舉凡外部函式可以看見的同層級的變數,都會是外部函式可見範圍,而因為內部函式因為是外部函式的一部份,所以在閉包形成後也就包含了外部函式的可見範圍。
最後,閉包形成是在外部函式被真正呼叫執行之後接著呼叫內部函式,只有當閉包形成了,內部函式方能說可見閉包所包覆的範圍。
而即便有變數是在外部函式宣告之後才宣告,只要該變數宣告是發生呼叫內部函式建立閉包之前,即使此變數是宣告在外部函式被執行之後(因為變數無法向前參考,所以外部函式不可見此變數), 此時再引用內部函式亦會把該變數包含進來,但在內部函式被引用之後才宣告的變數,不但外部函式不可見,內部函式同樣也不可見,自然閉包也不會包含進來。
後面我寫了一個簡單測試閉包範圍的 closure_scope_test.html 來實驗了閉包 可視範圍,而下圖是我程式大致的架構。可以看到閉包形成後其包覆的變數和內部函式對變數的可視範圍一致。但此時閉包並無法存取在 later 被呼叫之後(later是innerFunc內部函式的參考)的變declareVarAfterInvokeOuterFunc,不過完整程式在該變數之後又呼叫了一次 later,此時的閉包就會包含該變數了。 而對於外部函式 outerFunc 而言,它就只能看見在它被呼叫執行之前的所有變數,包括在它的宣告之後但被呼叫之前才宣告的變數: declareVarBeforeInvokeOuterFunc ,但在該外部函式 outerFunc 被呼叫之後才宣告的 declareVarAfterInvokeOuterFunc 變數對 outerFunc來說就不可見了。
最後是我的測試程式內容:
GitHub :
https://github.com/jackterrylau/SchoolCode/blob/master/Javascript/closure_scope_test.html
主要目的就是測試在 innerFunc 中能否 Get 到 在程式內出現的變數,先附上一張執行結果,然後再對照程式碼。
GitHub :
https://github.com/jackterrylau/SchoolCode/blob/master/Javascript/closure_scope_test.html
<!DOCTYPE html5>
2. <html>
3. <head>
4. <title>閉包實驗-測試閉包範圍</title>
5. <style>
6. #box {
7. position: absolute;
8. border-color: red;
9. border-width: 5;
10. /*background-color: yellow;*/
11. width: 800px;
12. }
13. </style>
14. </head>
15. <body>
16. <div id="box"></div>
17. <script>
18. let box = document.getElementById("box");
19. let result = "";
20. let outerValue = 'ninja';
21. let later;
22.
23. function outerFunc() {
24. let innerValue = 'Warrior';
25.
26. function innerFunc(parameter) {
27. if (innerValue) result += "<p>Inner Closure contains innerValue: " + innerValue + "</p>";
28. if (outerValue) result += "<p>Inner Closure contains outerValue: " + outerValue + "</p>";
29. if (parameter) result += "<p>Inner Closure contains parameter: <font color='blue'>" + parameter + "</font></p>";
30. if (declareVarBeforeInvokeOuterFunc) {
31. result += "<p>Inner Closure contains declareVarBeforeInvokeOuterFunc: " + declareVarBeforeInvokeOuterFunc + "</p>";
32. }
33. result += "<p>Inner Closure access declareVarAfterInvokeOuterFunc: " +
34. "<font color='red'>" + declareVarAfterInvokeOuterFunc + "</font></p>";
35. result += "----------------------------------------------------------------------------"
36. }
37. result += "<p>outerFunc accesses declareVarBeforeInvokeOuterFunc: " + declareVarBeforeInvokeOuterFunc + "</p>";
38. result += "<p>outerFunc accesses declareVarAfterInvokeOuterFunc: " +
39. "<font color='red'>" + declareVarAfterInvokeOuterFunc + "</font></p>";
40. result += "END OuterFunc ----------------------------------------------------------------------------"
41.
42. later = innerFunc;
43. }
44.
45. /***
46. *
47. * Before invoking outerFunc, The innerFunc is not existed.
48. * Below code will get an error message : innerFunc is not defined.
49. *
50. * **/
51. try {
52. innerFunc("CallInnerBeforeOuter");
53. }
54. catch (e) {
55. result += "<p>Call innerFunc() before invoking outerFunc(): " + e.message + "</p>";
56. }
57.
58. var declareVarBeforeInvokeOuterFunc = "Panda 1";
59. result += "<p>Before invoking outerFunc, later = " + later + "</p>";
60. outerFunc();
61. // if call later here, declareVarAfterInvokeOuterFunc in innerFunc = undefined, 因為call later 前該變數還沒被宣告定義
62. later("Call_Later(innerFunc)_after_invoking_OuterFunc");
63. var declareVarAfterInvokeOuterFunc = "Panda 2";
64. // if call later here, declareVarAfterInvokeOuterFunc in innerFunc = panda 2 , 因為call later 前該變數已宣告定義
65. later("Call_Later(innerFunc)_after_declare_declareVarAfterInvokeOuterFunc");
66.
67. result += "<p>內部函式形成的閉包存取範圍:<ul>"
68. result += "<li><b>外部函式以內所有變數</b></li>"
69. result += "<li><b>與外部函式同層級的所有變數且必須定義在內部函式閉包被呼叫建立之前</b></li>"
70. result += "<li><b>要使用內部函式閉包之前,外部函式必須被呼叫或建立</b></li></ul></p>"
71.
72. box.innerHTML = result;
74. </script>
75. </body>
76. </html>
77.
2024年10月6日星期日
留言列表