淺談JavaScript 的運行機理

          ——hechangmin@gmail.com  2010.10

          這個話題看似簡單,其實筆者是幾次三番的下筆,又幾次三番的放棄。因為這個內容,對于很多JavaScript的開發人員來講都是一知半解的,當然筆者也在其中,今天之所以出來獻丑了,首先是有了更深的認識,其次微博上有人說獻丑是進步,如果獻丑那必定是有同道之人能指出紕漏,那對于筆者本人來講何嘗不是進步呢?深表贊同!

          今天會以幾個小小的實例來解讀這個課題。希望能與大家共勉。

          首先得先了解JavaScript執行起來的流程,筆者先簡單畫了一個javascript的執行流程圖:



          重點解釋的有三步:詞法分析、預解析、執行。

          script代碼段:用script標簽分隔的js代碼或引入的js文件。

          (1). 預解析

          我們先從幾個常見的javascript 小題目入手,請大家先看看下面的范例輸出什么?

          <script type="text/javascript">

              alert(i); // ?

              var i = 1;

          </script>

          對于javascript的從業者可以試著運行下。看看你的答案和實際輸出一致嗎?別小看這樣兩行腳本,這樣的題目被當作JavaScript的筆試或者面試題目是常有的事情。

          實際輸出結果為:“undefined”,

          這種現象被稱成預解析JavaScript腳本引擎優先解析var變量和function定義。在預解析完成后,才會執行代碼。

          由于變量是被 var聲明的,而被優先解析。所以可以理解為在 alert(i) 執行時候,程序前面已經有 var i;

          所以上面代碼等效解釋為:

          <script type="text/javascript">

              var i;

          alert(i); // 對于被聲明,但未賦值過的i,輸出‘undefined’的結果,是不應該有任何歧義了吧。

              i = 1;

          </script>

          注意:預解析不會報錯,因為他只解析正確的聲明。

          (2). 解釋(主要指詞法分析,生成語法樹的過程)

          請注意,這里‘解釋’的定義是筆者自己方便理解自己定義的,而這個‘解釋’并不在預解析之后。

          我們知道JavaScript是腳本語言,腳本語言是相對于高級編譯型語言而言他是解釋性的。解釋性語言沒有編譯成二進制代碼,但是要進入到運行階段,都應該是會經過詞法分析、語法分析生成語法樹、語義檢查過程,筆者把這個環節叫做解釋,如果讀者有更科學的名字記得告訴我。

          解釋性語言在生成語法樹后,就可以執行了。(這個跟腳本引擎編譯器有關)

          在這個過程中,有語法檢查(比如括號是否匹配),發現無法生成語法樹,則報錯,結束整個代碼塊的解析。

          (3) 執行 與 作用域

          引入我們的第二個示例代碼:

          <script type="text/javascript">

              alert(i); // error: i is not defined.

              i = 1;

          </script>

          聽說JavaScript 變量可以直接用,那為什么還報運行時腳本錯誤?—— i 未定義.

          執行過程,需要理解JavaScript的作用域機制,JavaScript變量的作用域是在定義時決定而不是執行時決定,也就是說詞法作用域取決于源碼,編譯器通過靜態分析就能確定,因此詞法作用域也叫做靜態作用域(static scope)。但需要注意,witheval的語義無法僅通過靜態技術實現,實際上,只能說JS的作用域機制非常接近lexical scope.

          JS引擎在執行每個函數實例時,都會創建一個執行環境(execution context)。execution context中包含一個調用對象(call object調用對象是一個scriptObject結構,用來保存內部變量表varDecls、內嵌函數表funDecls、父級引用列表upvalue等語法分析結構(注意:varDeclsfunDecls等信息是在預解析階段就已經得到,并保存在語法樹中。函數實例執行時,會將這些信息從語法樹復制到scriptObject上)。scriptObject是與函數相關的一套靜態系統,與函數實例的生命周期保持一致。

          lexical scopeJS的作用域機制,還需要理解它的實現方法,這就是作用域鏈(scope chain)。scope chain是一個name lookup機制,首先在當前執行環境的scriptObject中尋找,沒找到,則順著upvalue到父級scriptObject中尋找,一直lookup到全局調用對象(global object)。

          當一個函數實例執行時,會創建或關聯到一個閉包(closure)。 scriptObject用來靜態保存與函數相關的變量表,closure則在執行期動態保存這些變量表及其運行值。closure的生命周期有可能比函數實例長。函數實例在活動引用為空后會自動銷毀,closure則要等要數據引用為空后,由JS引擎回收(有些情況下不會自動回收,就導致了內存泄漏)。

          別被上面的一堆名詞嚇住,一旦理解了執行環境、調用對象、閉包、詞法作用域、作用域鏈這些概念,JS語言的很多現象都能迎刃而解。

          小結

          預解析,其實是在的‘解釋’階段完成,并存儲在語法樹中。當執行到函數實例時,會將varDelcsfuncDecls從語法樹中復制到執行環境的scriptObject上。

          對于示例解析:

          未定義變量意味著在scriptObject的變量表中找不到,JS引擎會沿著scriptObjectupvalue往上尋找,如果都沒找到,對于寫操作i = 1; 最后就會等價為 window.i = 1; window對象新增了一個屬性。對于讀操作,如果一直追溯到全局執行環境的scriptObject上都找不到,就會產生運行期錯誤。

          最后,留個問題給大家:

          <script type="text/javascript">

              var arg = 1;

              function foo(arg) {

                  alert(arg);

                  var arg = 2;

              }

              foo(3);

          </script>

          請問alert的輸出是什么?

          posted on 2010-10-09 01:21 -274°C 閱讀(2281) 評論(0)  編輯  收藏 所屬分類: web前端

          常用鏈接

          留言簿(21)

          隨筆分類(265)

          隨筆檔案(242)

          相冊

          JAVA網站

          關注的Blog

          搜索

          •  

          積分與排名

          • 積分 - 914107
          • 排名 - 40

          最新評論

          主站蜘蛛池模板: 北辰区| 奈曼旗| 靖宇县| 舞钢市| 宁武县| 青铜峡市| 福安市| 德惠市| 会昌县| 和政县| 辉南县| 西丰县| 三明市| 聊城市| 会理县| 浦县| 天全县| 四川省| 永新县| 甘南县| 昆山市| 彩票| 蓬溪县| 芜湖县| 吉木萨尔县| 化德县| 南郑县| 灌云县| 波密县| 东山县| 澜沧| 南通市| 南川市| 托克逊县| 化州市| 大荔县| 育儿| 朝阳区| 孟津县| 博兴县| 松滋市|