在弄明白什麼是詞法環境(Lexical Environments)、什麼是運行上下文(Execution Context)、函數是被建立的過程之後,咱們要來理解javascript的做用域就很是的容易。javascript
咱們知道在JavaScript語言中,有兩種類型的做用域:java
那這兩種做用域是如何鏈接起來的呢?bash
其實咱們前面已經提到不少次,複習一下咱們的運行環境模型:函數
Runtime = {
executionContextStack: [];
};
//獲取當前的運行上下文,也就是運行棧,棧頂的運行上下文
Runtime.getRunningExecutionContext = function() {
return this.executionContextStack[this.executionContextStack.length - 1];
}
//把運行棧站定的運行上下文彈出
Runtime.pop = function() {
this.executionContextStack.pop();
}
//把一個運行上下文放到運行棧的棧頂
Runtime.push = function(newContext) {
this.executionContextStack.push(newContext);
}
//在當前運行上下文登記一個變量信息
Runtime.register = function(name) {
var env = this.getRunningExecutionContext().VariableEnvironment;
env.EnvironmentRecord.register(name);
}
//在當前運行上下文初始化一個變量信息
Runtime.initialize = function(name,value) {
var env = this.getRunningExecutionContext().VariableEnvironment;
env.EnvironmentRecord.initialize(name,value);
}
//在當前運行上下文上,解析一個標識符
Runtime.getIdentifierVaule = function (name) {
var env = this.getRunningExecutionContext().LexicalEnvironment;
while(env){
var envRec = env.EnvironmentRecord;
var exists = envRec.isExist(name);
if(exists) return envRec.getValue(name);
env = env.outer;
}
}
複製代碼
咱們解析一個變量,就是在當前運行運行上下文(Execution Context)上查找是否存在這個變量。那運行上下文由什麼構成?ui
由詞法環境(Lexical Environments)環境構成:this
function ExecutionContext() {
this.LexicalEnvironment = undefined;
this.VariableEnvironment = undefined;
this.ThisBinding = undefined;
}
function LexicalEnvironment() {
this.EnvironmentRecord = undefined;
this.outer = undefined; //outer Environment Reference
}
複製代碼
詞法環境(Lexical Environments)又是負責登記本環境(function內的)變量的。函數內的變量登記在當前詞法環境(Lexical Environments)的EnvironmentRecord裏面,函數如何訪問到全局的變量的,這些變量並沒登記在函數詞法環境上?spa
經過LexicalEnvironment的outer。code
函數運行時的過LexicalEnvironment的指向的是函數對象的[[scope]],而函數對象的[[scope]]保存着函數建立時的詞法環境。cdn
所以函數中,可訪問詞法環境範圍就是:函數本身的詞法環境+函數建立時的詞法環境。若是函數建立時的詞法環境也是一個函數的運行環境(內嵌函數),函數的可訪問的詞法環境就變成:對象
函數本身的詞法環境+函數建立時的詞法環境 = 函數本身的詞法環境+外面函數的詞法環境+外面函數建立時的詞法環境。。。
複製代碼
就這樣一個個的函數詞法環境連接在一塊兒,就是就是函數的做用域鏈。
咱們分析一段代碼更清晰:
var bestAvenger = "global";
function a() {
var bestActor = "in a";
console.log(bestAvenger); // "global";
function c() {
var bestC = "in c";
console.log(bestActor); // "in a";
b();
}
c();
}
function b() {
console.log(bestC); //**not defined error**
}
a();
複製代碼
全局運行上下文初始化:
//建立全局運行上下文
var globalExecutionContext = new ExecutionContext();
globalExecutionContext.LexicalEnvironment = creatGlobalEnvironment(globalobject);
globalExecutionContext.VariableEnvironment = creatGlobalEnvironment(globalobject);
globalExecutionContext.ThisBinding = globalobject;
//入棧
Runtime.push(globalExecutionContext);
//這時的Runtime = {
// executionContextStack: [globalExecutionContext]
//}
複製代碼
看起來是這樣的:
開始登記各個聲明
執行代碼
模型僞代碼以下:
//建立新的運行上下文
var barExecutionContext = new ExecutionContext();
//建立一個新的詞法環境(Lexical Enviroment)
var localEnviroment = new LexicalEnvironment();
//建立新的EnvironmentRecord
var barEnvironmentRecord = new EnvironmentRecord();
localEnviroment.EnvironmentRecord = barEnvironmentRecord
localEnviroment.outer = [[scope]] of bar function object
barExecutionContext.LexicalEnvironment = localEnviroment;
barExecutionContext.VariableEnvironment = localEnviroment;
barExecutionContext.ThisBinding = globalobject;//此例子中thisArg是undefined,且不是strict,因此設置爲 globalobject
//把函數的運行上下文入棧:
Runtime.push(barExecutionContext);
複製代碼
這時候運行棧:
開始在函數a的運行上下文上登記a函數內的聲明:
開始執行a函數裏面的語句:
你們會發如今當前運行上下文上,找不到變量bestC。
因此,做用域並非按照運行上下文,上一個連接下一個,這樣連接起來的。而是按照代碼的詞法結構,文本結構,連接起來的。
在上面的例子中:a,c是在全局運行山下文上建立的,因此a、c的做用域鏈是,自己函數做用域連接全局做用域。
因此儘管在函數c裏運行b函數,b函數做用域鏈上並未不能找到在c中定義的變量。
看一段代碼片斷:
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0](); //3
data[1](); //3
data[2](); //3
複製代碼
你能用咱們提到過的運行模型分析這段代碼,爲何三個函數調用都是打印3嗎?