開始以前先來看兩段代碼:javascript
function foo() {
console.log( a );
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
複製代碼
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
console.log(foo);
}
bar();
複製代碼
你是否會疑惑在function做用域內不是先查找本做用域的變量,找不到才沿做用域鏈往上找嗎?爲何 函數內有var a = 3;foo()仍是引用全局的a=2呢?java
爲啥foo會是10,難道var foo = 10 被執行了?瀏覽器
針對這種代碼中的現象,以前筆者也是查找各類帖子文章,在查看這些文章的時候,scope、詞法環境、靜態做用域、執行上下文等名詞不斷出現。從中大概知道,哦,js中變量是有提高的現象,哦,還有函數的做用域是在它建立的時候的詞法環境決定的,哦,還有this是在運行是決定的不是在建立時決定的(what?TMD究竟是怎麼決定的)。。。等等,再加上原型鏈、做用域鏈、閉包、匿名函數、當即執行函數。。。什麼鬼!相信小夥伴們都有相似的經歷,對這些概念聽過,知道,可是模糊。bash
爲了完全瞭解本身寫的代碼到底再幹些啥,G哥決定一探究竟,花了一些時間去探索,現現在我以爲已經真相大白了,能夠把我所瞭解的分享給你們。不是爲了搞些奇技淫巧,而是爲了更加透徹了此言語特性。閉包
注意:須要說明的是本系列文章是居於ECMAScript 5.1(ES5) 爲參考的,因此如何有小夥伴會疑惑,「爲啥你說的運行環境和我看大不大同樣,我看到的有變量對象(ES3),有realm(ES6)」。ES6的我會另出出一個系列來專門聊聊。本系列大部分只涉及ES5。函數
在開始以前,咱們先來想一個問題?JS引擎在執行代碼時,是從哪找到要引用的變量的值、函數調用時是如何找到要執行的函數的呢?答案其實簡單,簡單得有點意外---就是先登記,後使用。就像小夥伴們小時候上學,報道第一天先註冊,把名字、性別啥先登記了。登記完了,上課時,老師喊「小明」,小明才能迴應「嗯,哥在這」,是否是。好比說沒有登記小紅「這個」,登記簿上根本沒小紅,老師要怎麼叫,因此無法叫(ReferenceError 「not defined」)。可是若是「小李」開學有登記,表上也有它得名字「小李」,可是這天小李打電動曠課了,老師喊「小李」,沒人應(默認值undefined,這裏小夥伴要搞清楚了,undefined不是說沒定義,而是說這個變量定義了,它的值爲undefined,undefined是js語言中的一個值,記得否?)。ui
開學時,學生找老師來註冊登記入學,那咱們JS裏的變量名、函數名找誰註冊登記呢?找Lexical Environments(詞法環境)登記!this
Lexical Environments(詞法環境),之因此叫詞法環境,是由於它是和源程序的結構對應,就是和你所寫的那些源碼的文字的結構對應,你寫代碼的時候這個環境就定了。Lexical Environments(詞法環境)和四個類型的代碼結構相對應:spa
爲何?不知道,ES5就是這麼規定設計的。。。prototype
讀到這裏有些小夥伴急了,「不對,不對,我記得只有在全局代碼、函數代碼、和eval代碼三種狀況,纔會建立運行上下文,你專門有5種」。
對,你說的沒錯,只有在全局代碼、函數代碼、和eval代碼三種狀況,纔會建立運行上下文,但我這裏說的是詞法環境,Lexical Environments。不是運行上下文。
看起有點像是這樣的:
這個有點像一個學校,每一個班級由班主任負責登記班級學生,年段段長負責登記本年段老師,校長負責登記學校其餘行政人員。
可是這其中有些微妙的差別,with結構,catch結構 的詞法環境只用來檢索,不用來登記,就是說with結構,catch結構的var聲明和函數聲明也是到其餘(到哪?後續說明)詞法環境上登記綁定的,它其實就是一個戶口檢索代理機構(代理嘛,有可能沒有戶口登記的給你返回有此人。。),這些細節後續會深刻解釋。
觀察上面第一張圖,有幾點須要注意:
老師把學生信息登記到登記簿, Lexical Environments(詞法環境)要把變量信息登記在哪呢?哈哈, Lexical Environments(詞法環境)也有本身的「登記簿」-Environment Records。先看下Lexical Environments(詞法環境)結構是什麼樣:
詞法環境由兩部分組成:
用僞代碼表示就是相似這樣的:
function LexicalEnvironment() {
this.EnvironmentRecord = undefined;
this.outer = undefined; //outer Environment Reference
}
複製代碼
這個outer很重要,它是做用域鏈可以鏈起來的關鍵。那outer是什麼呢,我又要比喻了,比如是班主任手裏有一個登記簿,還有一個本年段段長的電話號碼?這個outer就是段長的電話號碼。什麼做用呢?想一下,班主任要找一個叫「9527」的人,發現本身班級裏沒有,這時候,就能夠打outer這個電話問問段長:「我找一個9527的人,我這名單上找不到,可能不是學生,是老師,你能在你登記簿上找一下嗎?」。段長就開始在本身的登記簿上找,若是找不到,段長也有一個電話號碼-校長的電話號碼,這時段長就給校長打電話:「校長,我在找一個9527的人,我登記簿上找不到,會不會是學校的行政人員,您能在您的登記簿上找一下這我的嗎」。就這樣一級一級網上連接,到了校長,他的電話號碼就是空了(null),他那找不到,就找不到了。
扯這麼多,一句話,outer 就是指向外部Lexical Environments(詞法環境)的引用。
在咱們JS中,global Lexical Environments(全局詞法環境)就是「校長」,它還有一個專門的名稱叫GlobalEnvironment 由於GlobalEnvironment 是最外層的詞法環境,因此GlobalEnvironment的outer = null。而在與GlobalEnvironment關聯的代碼中定義的函數的Lexical Environments(詞法環境)法環境的outer就是GlobalEnvironment,with和catch同理,它們如何關聯起來咱們後續還要細講。
如今來看看「登記簿」---EnvironmentRecord。EnvironmentRecord就是真是登記變量信息的地方了。ES5中EnvironmentRecord分爲兩類,就像有的老師把信息登記在本子上,有的把信息登記在電腦裏:
declarative environment records能夠簡單理解爲字典類型的結構,key-value形式結論變量等對應的名字和值。
而object environment records會關聯一個對象,用這個對象的屬性-值來登記變量等對應的名字和值。
用僞代碼表示(這些代碼只是用來講明相關結構用,不保證嚴謹性和可行性,僞代碼!):
function EnvironmentRecord(obj) {
if(isObject(obj)) {
this.bindings = object;
this.type = 'Object';
}
this.bindings = new Map();
this.type = 'Declarative';
}
EnvironmentRecord.prototype.register = function(name) {
if (this.type === 'Declarative')
this.bindings.set(name,undefined)
this.bindings[name] = undefined;
}
EnvironmentRecord.prototype.initialize = function(name,value) {
if (this.type === 'Declarative')
this.bindings.set(name,value);
this.bindings[name] = value;
}
EnvironmentRecord.prototype.getValue = function(name) {
if (this.type === 'Declarative')
return this.bindings.get(name);
return this.bindings[name];
}
複製代碼
全面提過,GlobalEnvironment,其就是一個特殊的Lexical Environments(詞法環境),它的outer爲null,它的EnvironmentRecord 是一個object environment records,且與全局對象global object(瀏覽器:window對象)關聯。看起來像這樣。
var GlobalEnvironment = new LexicalEnvironment();
GlobalEnvironment.outer = null;
GlobalEnvironment.EnvironmentRecord = new EnvironmentRecord(globalobject); ;//globalobject能夠看做是瀏覽器環境下的window
複製代碼
上面提到詞法環境(Lexical Environments),是用來登記變量和相關函數名字的,也知道這個名字是登記在 詞法環境的 EnvironmentRecord上的。
那問題又來了,何時登記,怎麼登記?是直接找老師(Lexical Environments)登記,仍是設置一個辦公廳,辦公廳設置登記窗口提供登記服務?
咱們下一篇來細細說明!
//Lexical Environment
function LexicalEnvironment() {
this.EnvironmentRecord = undefined;
this.outer = undefined; //outer Environment Reference
}
//EnvironmentRecord
function EnvironmentRecord(obj) {
if(isObject(obj)) {
this.bindings = object;
this.type = 'Object';
}
this.bindings = new Map();
this.type = 'Declarative';
}
EnvironmentRecord.prototype.register = function(name) {
if (this.type === 'Declarative')
this.bindings.set(name,undefined);
this.bindings[name] = undefined;
}
EnvironmentRecord.prototype.initialize = function(name,value) {
if (this.type === 'Declarative')
this.bindings.set(name,value);
this.bindings[name] = value;
}
EnvironmentRecord.prototype.getValue = function(name) {
if (this.type === 'Declarative')
return this.bindings.get(name);
return this.bindings[name];
}
//全局環境GlobalEnvironment
function creatGlobalEnvironment(globalobject) {
var globalEnvironment = new LexicalEnvironment();
globalEnvironment.outer = null;
globalEnvironment.EnvironmentRecord = new EnvironmentRecord(globalobject);
return globalEnvironment;
}
GlobalEnvironment = creatGlobalEnvironment(globalobject)//能夠看做是瀏覽器環境下的window
複製代碼