1、先理解一下作用域
如果我們初始化一個變量,比如:var a = 1;參與這段代碼執行的幾個角色包括:
引擎:從頭到尾負責整個JavaScript程序的編譯和執行
編譯器:負責詞法分析、語法分析及代碼生成等任務
作用域:負責收集并維護由所有聲明的標識符(變量)組成的一系列查詢,并實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限
對于var a = 1;這段程序,引擎認為這里有兩個完全不同的聲明,一個在編譯器編譯時處理,另一個在引擎運行時處理。
首先編譯器會將這段程序分解為詞法單元,然后將詞法單元解析成一個樹結構,在代碼生成階段進行如下處理:
1.遇到var a,編譯器會先詢問作用域中是否已經存在該名稱的變量,如果是,會忽略該聲明繼續編譯;如果否,會要求作用域在當前作用域集合中聲明一個名為a的變量。
2.之后編譯器會為引擎生成在運行時需要的代碼,這些代碼用來處理a = 2這個賦值操作。引擎運行時先問作用域是否有改變量,如果有則使用,如果沒有,則向上一級作用域中查找。
如果引擎最終找到了a,就把1賦值給它,如果沒有,就會拋出異常。
總結:變量的賦值操作會執行兩個動作,首先編譯器會在當前作用域中聲明一個變量,然后在運行時引擎會查找該變量,如果有則對它賦值。
作用域是根據名稱查找變量的一套規則,而作用域鏈是這套規則的具體實現。
2、作用域鏈
作用域鏈在執行上下文的創建階段生成,是由當前環境以及上層環境的一系列變量對象組成。它的作用是保證對執行環境有權訪問的所有變量和函數的有序訪問。
標識符的解析是沿著作用域鏈一級一級向上查找作用域的過程,查找始終從作用域開始,找到則停止,否則一直向上查找,知道全局作用域,即作用域鏈的末尾。
通過一個例子理解一下:
var color = "blur";
function changeColor() {
? ? var anotherColor = "red";
? ? function swapColor() { ??
? ? ? ? var tempColor = anotherColor;
? ? ? ? anotherColor = color;
? ? ? ? color = tempColor;
? ? }
}
以上代碼共涉及三個執行環境:全局環境、changeColor的局部環境和swapColor的局部環境。通過圖來展示作用域鏈:
內部環境可以通過作用域鏈訪問所有外部環境中的變量和函數,但是外部環境不能訪問內部環境。
閉包跟作用域鏈息息相關,下面就來介紹一下閉包。
3、閉包
閉包的概念:當函數可以記住并訪問所在的作用域(全局作用域除外)時,就產生了閉包,即使函數是在當前作用域之外執行的。簡單來說,就是一個函數中又聲明了一個函數,就產生了閉包。
function changeColor() {
? ? var anotherColor = "red";
? ? function swapColor() {
? ? ? ? console.log(anotherColor);
? ? }
? ? return swapColor;
}
var fn = changeColor();
這樣代碼執行時,就把swapColor的引用復制給了全局變量fn,而函數的執行上下文,在執行完畢生命周期結束之后,執行上下文就會失去引用,進而其占用的內存空間被垃圾回收器釋放。但是閉包的存在,打破了這種現象,因為swapColor的引用并沒有被釋放。所以閉包很容易造成內存泄漏的問題。
如何讓下面的代碼輸出1,2,3,4,5
for(vari=1;i<=5;i++){
setTimeout(functiontimer(){
console.log(i);
},0);
}
1. 使用中間變量承接一下
function fn(i) {
console.log(i);
}
for (var i=1; i<=5; i++) {
setTimeout( fn(i), 0 );
}
通過傳入實參緩存循環的數據,并且setTimeout的第一個參數是立即執行的函數,不執行不可以。
2、使用立即執行函數
for (var i=1; i<=5; i++) {
setTimeout( (function timer() {
console.log(i);
})(), 0 );
}
3、用let或const聲明
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log(i);
}, 0 );
}
這個問題的主要原因是因為執行到setTimeOut時函數沒有執行,而是把它放到了任務隊列中,等到for循環結束后再執行。所以i最后都變成了5。
循環中的事件也會有這個問題,因為事件需要觸發,大多數時候事件觸發的時候循環已經執行完了,所以循環相關的變量就變成了最后一次的值。