經過閱讀《JS高級程序設計》這本書,對js中的做用域和做用域鏈知識有了初步的瞭解和認識,準備成筆記供你們參考,筆記中字數比較多,但我的認爲敘述的挺詳細的,因此但願讀者耐心看。再者,本人瞭解的比較基礎,不足的地方但願你們一塊兒交流,共同窗習。html
1.執行環境(execution context)
執行環境定義了變量和函數有權訪問的其餘數據,決定了他們各自的行爲。每一個執行環境都有與之對應的變量對象(variable object),保存着該環境中定義的全部變量和函數。咱們沒法經過代碼來訪問變量對象,可是解析器在處理數據時會在後臺使用到它。前端
執行環境有全局執行環境(也稱全局環境)和函數執行環境之分。執行環境如其名是在運行和執行代碼的時候才存在的,因此咱們運行瀏覽器的時候會建立全局的執行環境,在調用函數時,會建立函數執行環境。web
1.1 全局執行環境
全局執行環境是最外圍的一個執行環境,在web瀏覽器中,咱們能夠認爲他是window對象,所以全部的全局變量和函數都是做爲window對象的屬性和方法建立的。代碼載入瀏覽器時,全局環境被建立,關閉網頁或者關閉瀏覽時全局環境被銷燬。瀏覽器
1.2 函數執行環境
每一個函數都有本身的執行環境,當執行流進入一個函數時,函數的環境就被推入一個環境棧中,當函數執行完畢後,棧將其環境彈出,把控制權返回給以前的執行環境。函數
2 做用域、做用域鏈
2.1 全局做用域(globe scope)和局部做用域(local scope)
全局做用域能夠在代碼中的任何地方都能被訪問,例如:post
1 var name1="haha"; 2 function changName(){ 3 var name2="xixi"; 4 console.log(name1); // haha 5 console.log(name2);// xixi 6 } 7 changName(); 8 console.log(name1);//haha 9 console.log(name2);//Uncaught ReferenceError: name2 is not defined
其中,name1具備全局做用域,所以在第4行和第8行都會在控制檯上輸出 haha。name2定義在changName()函數內部,具備局部做用域,所以在第9行,解析器找不到變量name2,拋出錯誤。
另外,在函數中聲明變量時,若是省略 var 操做符,那麼聲明的變量就是全局變量,擁有全局做用域,可是不推薦這種作法,由於在局部做用域中很難維護定義的全局變量。學習
再者,window對象的內置屬性都擁有全局做用域。url
局部做用域通常只在固定的代碼片斷內能夠訪問獲得,例如上述代碼中的name2,只有在函數內部能夠訪問獲得。spa
2.2 做用域鏈(scope chain)
全局做用域和局部做用域中變量的訪問權限,實際上是由做用域鏈決定的。設計
每次進入一個新的執行環境,都會建立一個用於搜索變量和函數的做用域鏈。做用域鏈是函數被建立的做用域中對象的集合。做用域鏈能夠保證對執行環境有權訪問的全部變量和函數的有序訪問。
做用域鏈的最前端始終是當前執行的代碼所在環境的變量對象(若是該環境是函數,則將其活動對象做爲變量對象),下一個變量對象來自包含環境(包含當前還行環境的環境),下一個變量對象來自包含環境的包含環境,依次往上,直到全局執行環境的變量對象。全局執行環境的變量對象始終是做用域鏈中的最後一個對象。
標識符解析是沿着做用域一級一級的向上搜索標識符的過程。搜索過程始終是從做用域的前端逐地向後回溯,直到找到標識符(找不到,就會致使錯誤發生)。
例如:
1 var name1 = "haha"; 2 function changeName(){ 3 var name2="xixi"; 4 function swapName(){ 5 console.log(name1);//haha 6 console.log(name2);//xixi 7 var tempName=name2; 8 name2=name1; 9 name1=tempName; 10 console.log(name1);//xixi
11 console.log(name2);//haha 12 console.log(tempName);//xixi
13 } 14 swapName(); 15 console.log(name1);//haha 16 console.log(name2);//xixi 17 //console.log(tempName);拋出錯誤:Uncaught ReferenceError: tempName is not defined 18 } 19 changName(); 20 console.log(name1); 21 //console.log(name2); 拋出錯誤:Uncaught ReferenceError: name2 is not defined 22 //console.log(tempName);拋出錯誤:Uncaught ReferenceError: tempName is not defined
運行結果以下:
上述代碼中,一共有三個執行環境:全局環境、changeName()的局部環境和 swapName() 的局部環境。因此,
1.函數 swapName()的做用域鏈包含三個對象:本身的變量對象----->changeName()局部環境的變量對象 ----->全局環境的變量對象。
2.函數changeName()的做用域包含兩個對象:本身的變量對象----->全局環境的變量對象。
就上述程序中出現的變量和函數來說(不考慮隱形變量):
1.swapName() 局部環境的變量對象中存放變量 tempName;
2.changeName() 局部環境的變量對象中存放變量 name2 和 函數swapName();
3.全局環境的變量對象中存放變量 name1 、函數changeName();
在swapName()的執行環境中,在執行第5句代碼時,解析器沿着函數 swapName()的做用域鏈一級級向後回溯查找變量 name1,直到在全局環境中找到變量 name1.並輸出在控制檯上。一樣,在執行第6句代碼時,解析器沿着函數 swapName()的做用域鏈一級級向後回溯,在函數changeName()的變量對象中發現變量 name2.經過代碼對 name1 和 name2進行交換,並輸出在控制檯上,根據結果咱們發現,這兩個變量的值確實交換了。所以咱們能夠得出結論,函數的局部環境能夠訪問函數做用域中的變量,也能夠訪問和操做父環境(包含環境)乃至全局環境中的變量。
在changeName() 的執行環境中,執行第15行和第16行代碼時,能夠正確地輸出 name1 和 name2 和兩個變量的值(調用了函數swapName(),因此倆變量的值已相互交換),那是由於 name1 在changName()的父環境(全局環境)中, name2 在他本身的局部環境中,即 name1 和 name2 都在其做用域鏈上。但當執行第17行代碼是發生錯誤 tempName is not defined。由於解析器沿着 函數changeName()的做用域鏈一級級的查找 變量 tempName時,並不能找到該變量的存在(變量 tempName不在其做用域鏈上),因此拋出錯誤。所以,咱們能夠得出結論:父環境只能訪問其包含環境和本身環境中的變量和函數,不能訪問其子環境中的變量和函數。
同理,在全局環境中,其變量對象中只存放變量 name1 、函數changeName(); 解析器只能訪問變量 name1 和函數 changeName(), 而不能訪問和操做 函數 changeName() 和函數 swapName() 中定義的變量或者函數。所以,在執行第21行和第22行代碼時拋出變量沒有定義的錯誤。因此說,全局環境只能訪問全局環境中的變量和函數,不能直接訪問局部環境中的任何數據。
其實,咱們能夠把做用域鏈想象成這樣(裏面的能訪問外面的,外面的不能訪問裏面的,圖爲參考):
做用域鏈相關知識的總結:
1.執行環境決定了變量的生命週期,以及哪部分代碼能夠訪問其中變量
2,執行環境有全局執行環境(全局環境)和局部執行環境之分。
3.每次進入一個新的執行環境,都會建立一個用於搜索變量和函數的做用域鏈
4.函數的局部環境能夠訪問函數做用域中的變量和函數,也能夠訪問其父環境,乃至全局環境中的變量和環境。
5.全局環境只能訪問全局環境中定義的變量和函數,不能直接訪問局部環境中的任何數據。
6.變量的執行環境有助於肯定應該合適釋放內存。
3.提高(hoisting)
提高有變量提高和函數提高之分,下面咱們依次介紹他們。
3.1 變量提高(variable hoisting)
請看一下代碼:
1 var name="haha"; 2 function changeName(){ 3 console.log(name); 4 var name="xixi"; 5 } 6 changeName(); 7 console.log(name);
你們認爲第6行和第7行代碼輸出的結果應該是什麼?好了,答案是:輸出結果結果分別是 undefined 和 haha。爲何是undefined? 按照做用域鏈的思路思考,輸出的結果應該是 haha或者xixi啊? (固然你們都知道 xixi是不可能的,由於解析器在解析第3行代碼時,還不知道第4行中的賦值內容)。
那咱們先來分析一下代碼 函數changeName() 的做用域鏈: 本身的變量對象 -----> 全局變量對象。解析器在函數執行環境中發現變量 name,所以不會再向全局環境的變量對象中尋找。可是你們要注意的是,解析器在解析第3句代碼時,還不知道變量name的值,也就是說只知道有變量name,可是不知道它具體的值(由於尚未執行第4句代碼),所以輸出是 undefined,第7行輸出haha你們應該都理解把(做用域問題)。因此上述代碼能夠寫成下面的形式:
1 var name="haha"; 2 function changeName(){ 3 var name; 4 console.log(name); 5 name="xixi"; 6 } 7 changeName(); 8 console.log(name);
這個現象就是變量提高!
變量提高,就是把變量提高到函數的頂部,須要注意的是,變量提高只是提高變量的聲明,不會吧變量的值也提高上來!見上述代碼,最多見的代碼以下,函數example1()和函數example2()是等價的:
1 function example1(){ 2 var a="haha"; 3 var b="xixi"; 4 var c="heihei"; 5 } 6 7 8 function example2(){ 9 var a,b,c; 10 a="haha"; 11 b="xixi"; 12 c="heihei"; 13 }
3.2 函數提高()
函數提高就是把函數提高到前面。
在JavaScript中函數的建立方式有三種:函數聲明(靜態的,像函數example1()的形式)、函數表達式(函數字面量)、函數構造法(動態的,匿名的)。函數表達式的形式以下:
1 var func1 = function(n1,n2){ 2 //function body; 3 };
函數構造法構造函數的形式以下:
var func2 = new Function("para1","para2",...,"function body");
在這裏須要說明的是:只有函數聲明形式才能被提高!例如:
//函數聲明 function myTest1(){ func(); function func(){ console.log("我能夠被提高"); } } myTest1(); //函數表達式 function myTest2(){ func(); var func = function(){ console.log("我不能被提高"); } } myTest2();
控制檯顯示結果以下:
以上就是該文章所講述的東西,歡迎你們批評指正!