通過解析過程了解JavaScript

轉載
原文地址:http://www.html5jscss.com/js-data-scope.html

什么是Javascript解析引擎?

Javascript解析引擎(簡稱Javascript引擎),是一個程序,是瀏覽器引擎的一個部分。

每個瀏覽器的Javascript解析引擎都不相同(因為每個瀏覽器編寫Javascript解析引擎的語言(C或者C++)以及解析原理都不相同)。標準的Javascript解析引擎會按照 ECMAScript文檔來實現。雖然每個瀏覽器的Javascript解析引擎不同,但Javascript的語言性質決定了Javascript關鍵的渲染原理仍然是動態執行Javascript字符串。只是詞法分析、語法分析、變量賦值、字符串拼接的實現方式有所不同。

JavaScript解析引擎到底是干什么的?

JavaScript解析引擎就是根據ECMAScript定義的語言標準來動態執行JavaScript字符串。雖然之前說現在很多瀏覽器不全是按照標準來的,解釋機制也不盡相同,但動態解析JS的過程還是分成兩個階段:語法檢查階段和運行階段。

語法檢查包括詞法分析和語法分析,運行階段又包括預解析和運行階段(像V8引擎會將JavaScript字符串編譯成二進制代碼,此過程應該歸到語法檢查過程中)。

JavaScript解析過程

在JavaScript解析過程中,如遇錯誤就直接跳出當前代碼塊,直接執行下一個 script 代碼段。所以在同一個 script 內的代碼段有錯誤的話就不會執行下去,但是不會影響下一個 script 內的代碼段。

第一階段:語法檢查

語法檢查也是JavaScript解析器的工作之一,包括 詞法分析 和 語法分析,過程大致如下:

一:詞法分析

詞法分析:JavaScript解釋器先把JavaScript代碼(字符串)的字符流按照ECMAScript標準轉換為記號流。
例如:把字符流:

a = (b - c);

轉換為記號流:

NAME "a"
EQUALS
OPEN_PARENTHESIS
 NAME "b"
MINUS 
NAME "c"
CLOSE_PARENTHESIS
SEMICOLON
二:語法分析

語法分析:JavaScript語法分析器在經過詞法分析后,將記號流按照ECMAScript標準把詞法分析所產生的記號生成語法樹
通俗地說就是把從程序中收集的信息存儲到數據結構中,每取一個詞法記號,就送入語法分析器進行分析。

語法分析不做的事:去掉注釋,自動生成文檔,提供錯誤位置(可以通過記錄行號來提供)。ECMAScript標準如下:

  • var,if,else,break,continue等是JavaScript的關鍵詞
  • abstract,int,long等是JavaScript保留詞
  • 怎么樣算是數字、怎么樣算是字符串等等
  • 定義了操作符(+,-,=)等操作符
  • 定義了JavaScript的語法
  • 定義了對表達式,語句等標準的處理算法,比如遇到==該如何處理
  • ……

當語法檢查正確無誤之后,就可以進入運行階段了。

第二階段:運行階段

一:預解析

第一步:JavaScript引擎將語法檢查正確后生成的語法樹復制到當前執行上下文中。
第二步:JavaScript引擎會對語法樹當中的變量聲明、函數聲明以及函數的形參進行屬性填充。

“預解析”從語法檢查階段復制過來的信息如下:

  1. 內部變量表varDecls:varDecls保存的用var進行顯式聲明的局部變量。
  2. 內嵌函數表funDecls:在“預解析”階段,發現有函數定義的時候,除了記錄函數的聲明外,還會創建一個原型鏈對象(prototype)。
  3. …其他的信息。

執行上下文(execution context)

(一)預解析階段創建的執行上下文包括:變量對象、作用域鏈、this

  1. 變量對象(Variable Object):由var declaration、function declaration(變量聲明、函數聲明)、arguments(參數)構成。變量對象是以單例形式存在。
  2. 作用域鏈(Scope Chain):variable object + all parent scopes(變量對象以及所有父級作用域)構成。
  3. this值:(thisValue):content object。this值在進入上下文階段就確定了。一旦進入執行代碼階段,this值就不會變了。

(二)“預解析”階段創建執行上下文之后,還會對變量對象/活動對象(VO/AO)的一些屬性填充數值。

  1. 函數的形參:執行上下文的變量對象的一個屬性,其屬性名就是形參的名字,其值就是實參的值;對于沒有傳遞的參數,其值為undefined。
  2. 函數聲明:執行上下文的變量對象的一個屬性,屬性名和值都是函數對象創建出來的;如果變量對象已經包含了相同名字的屬性,則會替換它的值。
  3. 變量聲明:執行上下文的變量對象的一個屬性,其屬性名即為變量名,其值為undefined;如果變量名和已經聲明的函數名或者函數的參數名相同,則不會影響已經存在的函數聲明的屬性,該聲明會被忽略掉,但其包含的賦值操作不會忽略。

變量對象/活動對象(VO/AO)填充的順序也是按照以上順序:函數的形參->函數聲明->變量聲明;在變量對象/活動對象(VO/AO)中權重高低也按照函數的形參->函數聲明->變量聲明順序來。

如下代碼:

    var a=1;
    function b(a) { 
        alert(a);
    }
    var b;
    alert(b); // function b(a) { alert(a); }
    b();  //undefined 

變量對象/活動對象(VO/AO)填充及優先順序

以上代碼在進入執行上下文時,按照函數的形參->函數聲明->變量聲明順序來填充,并且優先權永遠都是函數的形參>函數聲明>變量聲明,所以只要alert(a)中的a是函數中的形參,就永遠不會被函數和變量聲明覆蓋。就算沒有賦值也是默認填充的undefined值。

第二部分:執行代碼

經過“預解析”創建執行上下文之后,就進入執行代碼階段,VO/AO就會重新賦予真實的值,“預解析”階段賦予的undefined值會被覆蓋。

此階段才是程序真正進入執行階段,Javascript引擎會一行一行的讀取并運行代碼。此時那些變量都會重新賦值。

假如變量是定義在函數內的,而函數從頭到尾都沒被激活(調用)的話,則變量值永遠都是undefined值。

進入了執行代碼階段,在“預解析”階段所創建的任何東西可能都會改變,不僅僅是VO/AO,this和作用域鏈也會因為某些語句而改變,后面會講到。

了解完Javascript的解析過程最后我們再來了解下firebug的控制臺對Javascript的報錯提示吧。

其實firebug的控制臺也算是JavaScript的解釋器,而且他們會提示我們哪行出現了錯誤或者錯誤發生在哪個時期,語法檢查階段錯誤,還是運行期錯誤。

如下:

    alert(var);// SyntaxError: syntax error 語法分析階段錯誤 :語法錯誤
    var=1;; // SyntaxError: missing variable name 語法分析階段錯誤 :var是保留字符,導致變量名丟失
    a=b=v // ReferenceError: v is not defined 運行期錯誤: v 是未定義的
    JavaScript錯誤信息)

有如此詳細的錯誤提示,是不是就很快就知道代碼中到底是哪里錯了呢!

接下來我們詳細來介紹執行上下文中的一個重要概念——作用域鏈。

作用域鏈(Scope Chain)

作用域鏈是處理標識符時進行變量查詢的變量對象列表,每個執行上下文都有自己的變量對象:對于全局上下文而言,其變量對象就是全局對象本身;對于函數而言,其變量對象就是活動對象。

作用域鏈以及執行上下文的關系

在Javascript中只有函數能規定作用域,全局執行上下文中的 Scope 是全局上下文中的屬性,也是最外層的作用域鏈。

函數的屬性[[Scope]]是在“預解析”的時候就已經存在的了,它包含了所有上層變量對象,并一直保存在函數中。就算函數永遠都沒被激活(調用),[[Scope]]也都還是存在函數對象上。

創建執行上下文的 Scope 屬性和進入執行上下文的過程如下:

Scope = AO + [[Scope]] //預解析時的 Scope 屬性 
Scope = [AO].concat([[Scope]]); //執行階段,將AO添加到作用域鏈的最前端

執行上下文定義的 Scope 屬性變化過程

執行上下文中的[AO]是函數的活動對象,而[[Scope]]則是該函數屬性作用域。當前函數的AO永遠是在最前面的,保存在堆棧上,而每當函數激活的時候,這些AO都會壓棧到該堆棧上,查詢變量是先從棧頂開始查找,也就是說作用域鏈的棧頂永遠是當前正在執行的代碼所在環境的VO/AO(當函數調用結束后,則會從棧頂移除)。

通俗點講就是:JavaScript解釋器通過作用域鏈將不同執行位置上的變量對象串連成列表,并借助這個列表幫助JavaScript解釋器檢索變量的值。作用域鏈相當于一個索引表,并通過編號來存儲它們的嵌套關系。當JavaScript解釋器檢索變量的值,會按著這個索引編號進行快速查找,直到找到全局對象為止,如果沒有找到值,則傳遞一個特殊的 undefined值。

是不是又想到了一條JavaScript高效準則:為什么說在該函數內定義的變量,能減少函數嵌套能提高JavaScript的效率?因為函數定義的變量,此變量永遠在棧頂,這樣子查詢變量的時間變短了。

作用域的特性

保證查詢有序的訪問所有變量和函數
作用域鏈感覺就是一個VO鏈表,當訪問一個變量時,先在鏈表的第一個VO上查找,如果沒有找到則繼續在第二個VO上查找,直到搜索結束,也就是搜索到全局執行環境的VO中。這也就形成了作用域鏈的概念。

var color="blue";
function changecolor(){ 
    var anothercolor="red"; 
    function swapcolors(){
        var tempcolor=anothercolor; 
        anothercolor=color; 
        color=tempcolor; // Todo something 
    } 
    swapcolors();
}
changecolor();//這里不能訪問tempcolor和anocolor;但是可以訪問color;
alert("Color is now "+color);

作用域鏈保護變量安全

函數的作用域是在函數創建即“預解析”階段就已經就已經定義了,而在代碼執行階段則是將函數的作用域添加到作用域鏈上。

原型鏈查詢

在介紹“預解析”階段時,我們有提到當創建函數時,同時也會創建原型鏈對象(prototype)函數天生的。原型鏈對象在作用域鏈中沒有找到變量對象時,那么就會通過原型鏈來查找。

function Foo() { 
    function bar() { 
        alert(x); 
    } 
    bar();
}
Object.prototype.x = 10;
Foo(); // 10

上例中在作用域鏈中遍歷查詢,到了全局對象了,該對象繼承自Object.prototype,因此,最終變量“x”的值就變成了10。不過,在原型鏈上定義變量對象有些瀏覽器不支持,譬如IE6,而且這樣增加了變量對象的查詢時間。所以變量聲明盡量在調用函數AO里,即在用到該變量的函數內聲明變量對象。

作用域是在“預解析”時就已經決定的,所以作用域被叫做靜態作用域,而在執行階段的則被叫做動態鏈,因為在執行階段會改變作用域鏈中填充的值。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容

  • 目錄 1.靜態作用域與動態作用域 2.變量的作用域 3.JavaScript 中變量的作用域 4.JavaScri...
    一縷殤流化隱半邊冰霜閱讀 7,111評論 37 113
  • 繼承 一、混入式繼承 二、原型繼承 利用原型中的成員可以被和其相關的對象共享這一特性,可以實現繼承,這種實現繼承的...
    magic_pill閱讀 1,068評論 0 3
  • 1,javascript 基礎知識 Array對象 Array對象屬性 Arrray對象方法 Date對象 Dat...
    Yuann閱讀 932評論 0 1
  • Caffe訓練自己的數據集并用Python接口預測 本教程作者是清華大學在讀碩士金天童鞋,在當地較為英俊的男子,大...
    LucasJin閱讀 8,820評論 4 6
  • 七月二十五日 天氣晴 多云 有雨 好雨知時節,當春乃發生。倘若發生在酷熱難耐的奧斯汀的夏季,仍不失為一場好雨。...
    盼之閱讀 580評論 0 0