執行環境、作用域鏈
基本概念
-
執行環境定義了變數或函式有權訪問的其他資料,決定了他們各自的行為。每個執行環境都有一個與之關聯的變數物件,環境中定義的所有變數和函式都儲存在這個物件中(開發者無法訪問,解析器在後臺使用)
-
全域性執行環境是最外圍的一個執行環境。web瀏覽器中,全域性執行環境被認為是window物件——因此,所有全域性變數和函式都是作為window物件的屬性和方法建立的。
-
某個執行環境中的所有程式碼執行完畢後,該環境被銷燬,儲存在其中的所有變數和函式定義也隨之銷燬(全域性執行環境直到應用程式退出,如網頁關閉or瀏覽器關閉時才會被銷燬)
-
每個函式都有自己的執行環境。當執行流進入一個函式時,函式的環境就會被推入一個環境棧中。而在函式執行之後,棧將其環境彈出,把控制權返回給之前的執行環境。
-
當程式碼在一個環境中執行時,會建立變數物件的一個作用域鏈,作用域鏈保證對執行環境有權訪問的所有變數和函式的有序訪問。作用域鏈的前端,始終都是當前執行的程式碼所在環境的變數物件。如果這個環境是函式,則將其活動物件作為變數物件。活動物件在最開始時只包含一個變數,即arguments物件(arguments物件在全域性環境中不存在)
-
識別符號解析是沿著作用域鏈一級一級的搜尋識別符號的過程。
延長作用域鏈
- 執行環境的型別總共只有兩種——全域性和區域性(函式)
- 有些語句可以在作用域鏈的前端臨時增加一個變數物件,該變數物件會在程式碼執行後被移除。
a. try-cath語句的catch塊--建立一個新的變數物件,其中包含的是被丟擲的錯誤物件的宣告
b. with語句--會將指定的物件新增到作用域鏈中
with接收了location物件,因此其變數物件中就包含了location物件的所有屬性和方法。因此,其內部的href其實就是location.href。而url成了函式執行環境的一部分,所以可以作為函式的值被返回。(with語句例項) function buildUrl(){ var qs = "?debug=true"; with(location){ var url = href + qs } return url; } 複製程式碼
沒有塊級作用域
-
宣告變數
使用var宣告的變數會自動新增在最近的環境中,在函式內部,最接近的環境就是函式的區域性環境;在with語句中,最接近的環境是函式環境。如果初始化變數時沒有使用var宣告,該變數會自動被新增到全域性環境。
function(){ var a=b=1;//a:function內;b:全域性內 } 複製程式碼
嚴格模式下,初始化未經宣告的變數會導致錯誤
-
查詢識別符號
使用識別符號時,通過搜尋來確定該識別符號實際代表什麼。搜尋過程從作用域鏈的前端開始,向上逐級查詢與給定名字匹配的識別符號。如果在區域性環境中找到,搜尋過程停止,變數就緒。如果在區域性環境中沒找到,則繼續沿作用域鏈向上搜尋,直至全域性環境的變數物件。如果全域性環境中也沒有,則意味著該變數尚未宣告。
let與塊級作用域
-
以上內容都是es5。es6中,引入let,從而產生了塊級作用域的概念
-
var有一種“變數提升”的現象,即變數可以在宣告前使用,值為undefined;但是let定義的變數,如果在宣告前使用,會直接報錯
console.log(foo);//undefined
var foo = 2;
console.log(bar);//報錯
let bar = 2;
複製程式碼
- 暫時性死區:變數在let宣告前使用會直接報錯,這就產生了一個“暫時性死區”:
var tmp = 123;
if(true){
tmp = 'abc';//報錯
let tmp;
}
複製程式碼
- es6明確規定,如果區塊中存在let和const命令,這個區塊對這些命令宣告的變數,從一開始就形成了封閉的作用域。凡是在宣告之前就使用這些變數,就會報錯
暫時性死區意味著typeof不再是一個百分之百安全的操作
typeof x;//報錯
let x;
typeof undeclared_variable//undefined
複製程式碼
塊級作用域與函式宣告
-
es5中函式宣告不能在塊級作用域內定義;但是瀏覽器不會報錯;
-
es6中允許在塊級作用域中宣告函式,函式宣告語句的行為類似於let,在塊級作用域之外不可引用——但是,es6瀏覽器為了相容老程式碼,定義函式宣告類似於var,即會提升到全域性作用域或函式作用域的頭部;同時,函式宣告還會提升到所在塊級作用域的頭部
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重複宣告一次函式f
function f() { console.log('I am inside!'); }
}
f();
}());
es5瀏覽器中執行:I am inside!因為將f提升到了函式頭部
es6瀏覽器:報錯:
相當於
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
複製程式碼
-
所以,不要在塊級作用域內定義函式
-
另外,這種情形注意下:
// 不報錯
'use strict';
if (true) {
function f() {}
}
// 報錯
'use strict';
if (true)
function f() {}
複製程式碼