在一個前端公眾號,看到這么一個號稱簡單的面試題:
1、以下程序輸出什么?
<script type="text/javascript"> function init(){ for (var i = 0; i < 10; ++i) { setTimeout(function () { console.log(i); }, 0); } } window.onload=init; </script>
2、若需要輸出0123456789,應該怎么修改?
結果,輸出的為10101010101010101010
若要輸出0123456789,則可以將代碼改成
<script type="text/javascript"> function init2(){ for (var i = 0; i < 10; ++i) { setTimeout( (function(i){ return function(){ console.log(i) } })(i), 0); } } window.onload=init2; </script>
解釋:
1、for循環每次注冊一個延遲函數, setTimeout是異步的,傳入事件隊列中,在循環結束后進行處理,當循環結束時,i為10。
setTimeout中的匿名function沒有將 i 作為參數傳入來固定這個變量的值, 讓其保留下來, 而是直接引用了外部作用域中的 i, 因此 i 變化時, 也影響到了匿名function.
2、for循環執行時,給點擊事件綁定的匿名函數傳遞i后,立即執行返回一個內部的匿名函數,因為參數是按值傳遞的,
所以此時形參保存的就是當前i的值,內部的匿名函數一直保存着當前i的值。 返回的匿名函數執行彈出各自保存的 i 的引用的值。
看到解釋到這里,我是一臉懵逼,關鍵在於閉包的參數傳遞,所以我自己對閉包的知識進行了一下下擴展。
0、前言:Js變量的作用域
變量的作用域包括:全局變量、局部變量
Javascript語言的特殊之處,就在於函數內部可以直接讀取全局變量。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
在函數外部無法讀取函數內部的局部變量
function f1(){
var n=999;
}
alert(n); // error
注意:若函數內部聲明變量的時候,一定要用var,否則實際上聲明了全局變量,
會造成其他函數誤用變量、全局對象過於龐大,影響訪問速度等不良影響
function f1(){
n=999;
}
f1();
alert(n); // 999
Javascript語言特有的"鏈式作用域"結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立
1、閉包的解釋
閉包是指有權訪問另一個函數作用域變量的函數,創建閉包的通常方式,是在一個函數內部創建另一個函數。
閉包的本質是一個函數,將函數內部和外部連接起來的橋梁。
2、閉包的寫法
好像,還是,有那么點抽象,那就網上找個例子:
function func1(){
var age = 22;
function func2(){
return age;
}
return func2;
}
var result = func1(); //得到的是函數func2這一整個函數
var _num = result(); //得到的是func2的執行結果也就是age的值
console.log(result); //function func2(){ return age; }
console.log(_num); //22
結果分析:
a)首先要明白一個函數名稱加不加括號的區別,比如說func1和func1(),func1表示的是這個函數本身,func1()表示的是這個名叫func1的函數的執行結果也就是它的返回值。
b) 其次我們來分析閉包的原理,因為func2是函數func1的子函數,所以在func2中可以訪問到變量age,並將這個值作為函數func2的返回值返回,最后將整個func2函數作為func1函數的返回值。
c)最后我們來分析一下result和_num的值,result是函數func1的執行結果,也就是func1的返回值func2函數本身,_num的值為func2的執行結果也就是變量age。
d)至此,我們實際上已經將局部變量age的值獲取到了並存在變量_num中,實現了在函數外部訪問局部變量的值的問題。
3、閉包的用途
a) 匿名自執行函數
創建了一個匿名的函數,並立即執行它,由於外部無法引用它內部的變量,因此在執行完后很快就會被釋放,關鍵是這種機制不會污染全局對象。
① 匿名函數:顧名思義,就是沒有方法名的函數
② 匿名函數調用方式:
使用()將匿名函數括起來,然后后面再加一對小括號(包含參數列表)
(function(a,b) { console.log('匿名函數加圓括號:'+(a+b)); }(1,2));
將匿名函數賦值給一個變量,通過變量引用進行函數調用
var noName= function(a,b){
console.log('匿名函數賦值變量:'+(a+b));
}(1,4);
b) 緩存(未整理驗證)
再來看一個例子,設想我們有一個處理過程很耗時的函數對象,每次調用都會花費很長時間,
那么我們就需要將計算出來的值存儲起來,當調用這個函數的時候,首先在緩存中查找,如果找不到,則進行計算,
然后更新緩存並返回值,如果找到了,直接返回查找到的值即可。閉包正是可以做到這一點,因為它不會釋放外部的引用,
從而函數內部的值可以得以保留。
c)實現封裝
可以先來看一個關於封裝的例子,在person之外的地方無法訪問其內部的變量,而通過提供閉包的形式來訪問
d)面向對象編程
實例獨立訪問成員變量,互不影響
