閉包在紅寶書中的解釋就是:有權訪問另一個函數作用域中的變數的函數。 1.變數作用域 全局變數:所有的函數外部定義的變數,它的作用域是整個script。 局部變數:定義在函數體內部的變數,作用域僅限於函數體內部。離開函數體就會無效。再調用就是出錯。 舉例如下-局部變數: a變數定義在fun函數內,是局 ...
閉包在紅寶書中的解釋就是:有權訪問另一個函數作用域中的變數的函數。
1.變數作用域
全局變數:所有的函數外部定義的變數,它的作用域是整個script。
局部變數:定義在函數體內部的變數,作用域僅限於函數體內部。離開函數體就會無效。再調用就是出錯。
舉例如下-局部變數:
<script type="text/javascript"> function fun(){ var a = 100; } console.log(a); </script>
a變數定義在fun函數內,是局部變數,所以它不能在外部被訪問。
舉例如下-全局變數:
<script type="text/javascript"> var c = 100; function fun(){ var a = 100; console.log(c) } fun(); console.log(c); </script>
在全局定義一個全局變數c,不僅能在fun函數內部被訪問,在函數外依舊能被訪問。
2.間接訪問局部變數
<script type="text/javascript"> function fun(){ var a = 100; function fun1(){ console.log(a); } fun1(); } fun(); </script>
通過調用fun1把a列印出來,fun1是可以訪問fun的所有變數
3.作用域鏈
可以參考我的這篇文章JS之預編譯和執行順序(全局和函數)可以更好的理解預編譯的原理,為作用域鏈做準備。
舉例:
<script type="text/javascript"> var a = 100; function fun(){ var b = 200 function fun2(){ var c = 300 } function fun3(){ var d = 400 } fun2() fun3() } fun() </script>
首先預編譯,一開始生成一個GO{
a:underfined
fun:function fun(){//fun的函數體
var b = 200
function fun2(){
var c = 300
}
function fun3(){
var d = 400
}
fun2()
fun3()
}
}
逐行執行代碼,GO{
a:100
fun:function fun(){//fun的函數體
var b = 200
function fun2(){
var c = 300
}
function fun3(){
var d = 400
}
fun2()
fun3()
}
}
當fun函數執行時,首先預編譯會產生一個AO{
b:underfined
fun2:function fun2(){
var c = 300
}
fun3:function fun3(){
var d = 400
}
}
這裡註意的是fun函數是在全局的環境下產生的,所以自己身上掛載這一個GO,由於作用域鏈是棧式結構,先產生的先進去,最後出來,
在這個例子的情況下,AO是後於GO產生的,所以對於fun函數本身來說,執行代碼的時候,會先去自己本身的AO里找找看,如果沒有找到要用的東西,就去父級查找,此題的父級是GO
此刻fun的作用域鏈是 第0位 fun的AO{}
第1位 GO{}
fun函數開始逐行執行AO{
b:200
fun2:function fun2(){
var c = 300
}
fun3:function fun3(){
var d = 400
}
}
註意:函數每次調用才會產生AO,每次產生的AO還都是不一樣的
然後遇到fun2函數的執行,預編譯產生自己的AO{
c:underfined
}
此刻fun2的作用域鏈是第0位 fun2的AO{}
第1位 fun的AO{}
第2位 GO{}
然後遇到fun3函數的執行,預編譯產生自己的AO{
d:underfined
}
此刻fun3的作用域鏈是第0位 fun3的AO{}
第1位 fun的AO{}
第2位 GO{}
fun2和fun3的作用域鏈沒有什麼聯繫
當函數fun2和fun3執行完畢,自己將砍掉自己和自己的AO的聯繫,
最後就是fun函數執行完畢,它也是砍掉自己和自己AO的聯繫。
這就是一個我們平時看到不是閉包的函數。
4.閉包
1.閉包在紅寶書中的解釋就是:有權訪問另一個函數作用域中的變數的函數。
2.寫法:
1 <script type="text/javascript"> 2 function fun1(){ 3 var a = 100; 4 function fun2(){ 5 a++; 6 console.log(a); 7 } 8 return fun2; 9 } 10 11 var fun = fun1(); 12 fun() 13 fun() 14 </script>
3.效果如下:
4.分析:
執行代碼
GO{
fun:underfined
fun1:function fun1()
{
var a = 100;
function fun2()
{
a++;
console.log(a);
}
return fun2;
}
}
然後第十一行開始這裡,就是fun1函數執行,然後把fun1的return賦值給fun,這裡比較複雜,我們分開來看,
這裡fun1函數執行,產生AO{
a:100
fun2:function fun2(){
a++;
console.log(a);
}
}
此刻fun1的作用域鏈為 第0位 AO
第1位 GO
此刻fun2的作用域鏈為 第0位 fun1的AO
第1位 GO
解釋一下,fun2只是聲明瞭,並沒有產生調用,所以沒有產生自己的AO,
正常的,我們到第7行代碼我們就結束了,但是這個時候來了一個return fun2,把fun2這個函數體拋給了全局變數fun,好了,fun1函數執行完畢,消除自己的AO,
此刻fun2的作用域鏈為 第0位 fun1的AO
第1位 GO
第十二行就是fun執行,然後,它本身是沒有a的,但是它可以用fun1的AO,然後加,然後列印,
因為fun中的fun1的AO本來是應該在fun1銷毀時,去掉,但是被拋給fun,所以現在fun1的AO沒辦法銷毀,所以現在a變數相當於一個只能被fun訪問的全局變數。
所以第十三行再調用一次fun函數,a被列印的值為102.
5.閉包之深入理解
舉例1:
1 <script type="text/javascript"> 2 function fun1(){ 3 var a = 100; 4 function fun2(){ 5 a ++; 6 console.log(a); 7 } 8 9 return fun2; 10 } 11 var fn1 = fun1(); //生成自己的AO,上面有a 12 var fn2 = fun1(); 13 fn1()//101 14 fn1()//102 15 fn2()//101 16 </script>
fn1和fn2互不幹涉,因為fun1函數調用了兩次,所以兩次的AO是不一樣的。
舉例2:
1 <script type="text/javascript"> 2 function fun(){ 3 var num = 0; 4 function jia(){ 5 num++; 6 console.log(num); 7 } 8 function jian(){ 9 num--; 10 console.log(num) 11 } 12 return [jia,jian]; 13 } 14 var fn = fun(); 15 var jia = fn[0]; 16 var jian = fn[1]; 17 jia()//1 18 jian()//0 19 </script>
jia和jian共用一個fun的AO,一動全都動,十二行返回了一個數組,
舉例3:
1 <script type="text/javascript"> 2 function fun(){ 3 var num = 0; 4 function jia(){ 5 num++; 6 console.log(num); 7 } 8 function jian(){ 9 num--; 10 console.log(num) 11 } 12 return [jia,jian]; 13 } 14 var jia = fun()[0]; 15 var jian = fun()[1]; 16 jia()//1 17 jian()//-1 18 </script>
這裡有一個坑,jia = fun()[0]; jian = fun()[1];fun函數執行了兩遍,所以兩次的AO不一樣,所以jia和jian操作的對象不一樣。
6.閉包好處與壞處
好處:
①保護函數內的變數安全 ,實現封裝,防止變數流入其他環境發生命名衝突
②在記憶體中維持一個變數,可以做緩存(但使用多了同時也是一項缺點,消耗記憶體)
③匿名自執行函數可以減少記憶體消耗
壞處:
①其中一點上面已經有體現了,就是被引用的私有變數不能被銷毀,增大了記憶體消耗,造成記憶體泄漏,解決方法是可以在使用完變數後手動為它賦值為null;
②其次由於閉包涉及跨域訪問,所以會導致性能損失,我們可以通過把跨作用域變數存儲在局部變數中,然後直接訪問局部變數,來減輕對執行速度的影響
7.閉包解決的問題
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> <script type="text/javascript"> var lis = document.getElementsByTagName("li"); for(var i = 0;i < lis.length;i++){ lis[i].onclick = function(){ console.log(i) } } </script> </body> </html>
不管點擊哪個都是10,那是因為點擊事件是我們點擊才觸發的函數,等到觸發的時候,i早就變成10跳出迴圈了,,這個時候我們就需要立即執行函數,創造了十個不同的作用域
解決方案:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> <script type="text/javascript"> var lis = document.getElementsByTagName("li"); for(var i = 0;i < lis.length;i++){ // lis[i].onclick = function(){ // console.log(i) // } (function(i){ lis[i].onclick = function(){ console.log(i) } })(i) } </script> </body> </html>