作用域鏈
作用域就是變量和函數的可訪問範圍,他包含全局變量和局部變量~我們知道的是JavaScript在執行語句的時候是有預解析的。
var a=3; //全局變量
function fn(b){ //局部變量
c=2; //全局變量
var d=5; //局部變量
function subFn(){
var e=d; //父函數的局部變量對子函數可見
for(var i=0;i<3;i++){
console.write(i);
}
alert(i);//3, 在for循環內聲明,循環外function內仍然可見,沒有塊作用域
}
}
alert(c); //在function內聲明但不帶var修飾,仍然是全局變量
javascript 是沒有塊級作用域的,但是他有預解析
console.log(a); //undefined
var a = 3;
console.log(a); //3
console.log(b); //Uncaught ReferenceError: b is not defined
引言——執行環境
執行環境(execution context)定義了變量或函數有權訪問的其它數據,決定了它們的各自行爲。每個執行環境都有一個與之關聯的變量對象(variable object, VO)
執行環境中定義的所有變量和函數都會保存在這個對象中,解析器在處理數據的時候就會訪問這個內部對象。
全局執行環境是最外層的一個執行環境,在web瀏覽器中全局執行環境是window對象,因此所有全局變量和函數都是作爲window對象的屬性和放大創建的。
每個函數都有自己的執行環境,當執行流進入一個函數的時候,函數的環境會被推入一個函數棧中,而在函數執行完畢後執行環境出棧並被銷燬,保存在其中的所有變量和函數定義隨之銷燬,控制權返回到之前的執行環境中,全局的執行環境在應用程序退出(瀏覽器關閉)纔會被銷燬。
例如上面的圖片~當我執行一次Fn的時候,就會產生一個需要執行Fn一次的任務,這個任務產生一個Fn的執行環境,裏面有變量b = 2,c = 3; 當這個任務被執行完,這些變量就會一起被銷燬
現在來說說作用域鏈
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈(scope chain,不簡稱sc)來保證對執行環境有權訪問的變量和函數的有序訪問。作用域第一個對象始終是當前執行代碼所在環境的變量對象(VO)
function a(x,y){
var b = x + y;
return b;
}
// 在這裏函數 a 的執行環境就是一個包含在 window 作用域下的一個分支
再來看看這段代碼:
function a(x,y){
var b = x + y;
function c() {
var d = 4;
}
return b;
}
var total = a(5,10);
再來看看閉包
只要存在調用內部函數的可能,JavaScript就需要保留被引用的函數。而且JavaScript運行時需要跟蹤引用這個內部函數的所有變量,直到最後一個變量廢棄,JavaScript的垃圾收集器才能釋放相應的內存空間。
for(var i = 0;i < data.length; i++){
data[i].onclick = function (){
alert(i);
}
}
每次循環產生一個 onclick 的函數,這些函數隨時都有可能被執行,並且這些函數裏面需要被打印的變量 i 是父級作用域的值
所以最後打印出來的 i 都是 3,就是因爲子函數在執行的時候應用了父執行環境的變量~所以導致父級函數在執行完之後沒有被銷燬,而是繼續保留。
總結
例如在javascript中,只有函數內部的子函數才能讀取局部變量,所以閉包可以理解成“定義在一個函數內部的函數“。在本質上,閉包是將函數內部和函數外部連接起來的橋樑。
作用域 是針對變量的,比如我們創建了一個函數,函數裏面又包含了一個函數,那麼現在就有三個作用域
- 全局作用域==>函數1作用域==>函數2作用域
作用域的特點就是,先在自己的變量範圍中查找,如果找不到,就會沿着作用域往上找。
原型鏈 是針對構造函數的,比如我先創建了一個函數,然後通過一個變量new了這個函數,那麼這個被new出來的函數就會繼承創建出來的那個函數的屬性,然後如果我訪問new出來的這個函數的某個屬性,但是我並沒有在這個new出來的函數中定義這個變量,那麼它就會往上(向創建出它的函數中)查找,這個查找的過程就叫做原型鏈。