1. 完全搞懂javascript-詞法環境(Lexical Environments)

開始以前先來看兩段代碼: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();
複製代碼
  1. 你是否會疑惑在function做用域內不是先查找本做用域的變量,找不到才沿做用域鏈往上找嗎?爲何 函數內有var a = 3;foo()仍是引用全局的a=2呢?java

  2. 爲啥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(詞法環境),之因此叫詞法環境,是由於它是和源程序的結構對應,就是和你所寫的那些源碼的文字的結構對應,你寫代碼的時候這個環境就定了。Lexical Environments(詞法環境)和四個類型的代碼結構相對應:spa

  • Global code:通俗點講就是源文件代碼,就是一個詞法環境
  • 函數代碼 :一個函數塊內本身是一個新的詞法環境
  • eval:進入eval調用的代碼有時會建立一個新的詞法環境
  • with結構:一個with結構塊內也是本身一個詞法環境
  • catch結構:一個catch結構快內也是本身一個詞環境

爲何?不知道,ES5就是這麼規定設計的。。。prototype

讀到這裏有些小夥伴急了,「不對,不對,我記得只有在全局代碼、函數代碼、和eval代碼三種狀況,纔會建立運行上下文,你專門有5種」。

對,你說的沒錯,只有在全局代碼、函數代碼、和eval代碼三種狀況,纔會建立運行上下文,但我這裏說的是詞法環境,Lexical Environments。不是運行上下文。

看起有點像是這樣的:

圖1

這個有點像一個學校,每一個班級由班主任負責登記班級學生,年段段長負責登記本年段老師,校長負責登記學校其餘行政人員。

可是這其中有些微妙的差別,with結構,catch結構 的詞法環境只用來檢索,不用來登記,就是說with結構,catch結構的var聲明和函數聲明也是到其餘(到哪?後續說明)詞法環境上登記綁定的,它其實就是一個戶口檢索代理機構(代理嘛,有可能沒有戶口登記的給你返回有此人。。),這些細節後續會深刻解釋。

觀察上面第一張圖,有幾點須要注意:

  1. 圖中畫了和4種結構對應的詞法環境是爲了展現,並非說一會兒就創建4個詞法環境,記住,詞法環境是在進入上述幾種代碼結構以前以前建立的,就像學校開課以前這些學生登記工做就要作了。
  2. 從圖中,能夠發現,詞法環境是嵌套的,那怎麼實現嵌套呢?這就須要瞭解詞法環境的結構了。

老師把學生信息登記到登記簿, Lexical Environments(詞法環境)要把變量信息登記在哪呢?哈哈, Lexical Environments(詞法環境)也有本身的「登記簿」-Environment Records。先看下Lexical Environments(詞法環境)結構是什麼樣:

詞法環境由兩部分組成:

  • Environment Records(環境記錄):這個就是變量登記的地方了
  • outer:outer 是個指向,包含(包圍)本詞法環境的外部詞法環境

用僞代碼表示就是相似這樣的:

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 主要用於函數 、catch詞法環境
  • object environment records. 主要用於with 和global的詞法環境

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];
}
複製代碼

全局環境(Global Environment)

全面提過,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

複製代碼
相關文章
相關標籤/搜索