淺談JavaScript 的運(yùn)行機(jī)理

          ——hechangmin@gmail.com  2010.10

          這個(gè)話題看似簡(jiǎn)單,其實(shí)筆者是幾次三番的下筆,又幾次三番的放棄。因?yàn)檫@個(gè)內(nèi)容,對(duì)于很多JavaScript的開發(fā)人員來講都是一知半解的,當(dāng)然筆者也在其中,今天之所以出來獻(xiàn)丑了,首先是有了更深的認(rèn)識(shí),其次微博上有人說獻(xiàn)丑是進(jìn)步,如果獻(xiàn)丑那必定是有同道之人能指出紕漏,那對(duì)于筆者本人來講何嘗不是進(jìn)步呢?深表贊同!

          今天會(huì)以幾個(gè)小小的實(shí)例來解讀這個(gè)課題。希望能與大家共勉。

          首先得先了解JavaScript執(zhí)行起來的流程,筆者先簡(jiǎn)單畫了一個(gè)javascript的執(zhí)行流程圖:



          重點(diǎn)解釋的有三步:詞法分析、預(yù)解析、執(zhí)行。

          script代碼段:用script標(biāo)簽分隔的js代碼或引入的js文件。

          (1). 預(yù)解析

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

          <script type="text/javascript">

              alert(i); // ?

              var i = 1;

          </script>

          對(duì)于javascript的從業(yè)者可以試著運(yùn)行下。看看你的答案和實(shí)際輸出一致嗎?別小看這樣兩行腳本,這樣的題目被當(dāng)作JavaScript的筆試或者面試題目是常有的事情。

          實(shí)際輸出結(jié)果為:“undefined”,

          這種現(xiàn)象被稱成預(yù)解析JavaScript腳本引擎優(yōu)先解析var變量和function定義。在預(yù)解析完成后,才會(huì)執(zhí)行代碼。

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

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

          <script type="text/javascript">

              var i;

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

              i = 1;

          </script>

          注意:預(yù)解析不會(huì)報(bào)錯(cuò),因?yàn)樗唤馕稣_的聲明。

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

          請(qǐng)注意,這里‘解釋’的定義是筆者自己方便理解自己定義的,而這個(gè)‘解釋’并不在預(yù)解析之后。

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

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

          在這個(gè)過程中,有語法檢查(比如括號(hào)是否匹配),發(fā)現(xiàn)無法生成語法樹,則報(bào)錯(cuò),結(jié)束整個(gè)代碼塊的解析。

          (3) 執(zhí)行 與 作用域

          引入我們的第二個(gè)示例代碼:

          <script type="text/javascript">

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

              i = 1;

          </script>

          聽說JavaScript 變量可以直接用,那為什么還報(bào)運(yùn)行時(shí)腳本錯(cuò)誤?—— i 未定義.

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

          JS引擎在執(zhí)行每個(gè)函數(shù)實(shí)例時(shí),都會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境(execution context)。execution context中包含一個(gè)調(diào)用對(duì)象(call object調(diào)用對(duì)象是一個(gè)scriptObject結(jié)構(gòu),用來保存內(nèi)部變量表varDecls、內(nèi)嵌函數(shù)表funDecls、父級(jí)引用列表upvalue等語法分析結(jié)構(gòu)(注意:varDeclsfunDecls等信息是在預(yù)解析階段就已經(jīng)得到,并保存在語法樹中。函數(shù)實(shí)例執(zhí)行時(shí),會(huì)將這些信息從語法樹復(fù)制到scriptObject上)。scriptObject是與函數(shù)相關(guān)的一套靜態(tài)系統(tǒng),與函數(shù)實(shí)例的生命周期保持一致。

          lexical scopeJS的作用域機(jī)制,還需要理解它的實(shí)現(xiàn)方法,這就是作用域鏈(scope chain)。scope chain是一個(gè)name lookup機(jī)制,首先在當(dāng)前執(zhí)行環(huán)境的scriptObject中尋找,沒找到,則順著upvalue到父級(jí)scriptObject中尋找,一直lookup到全局調(diào)用對(duì)象(global object)。

          當(dāng)一個(gè)函數(shù)實(shí)例執(zhí)行時(shí),會(huì)創(chuàng)建或關(guān)聯(lián)到一個(gè)閉包(closure)。 scriptObject用來靜態(tài)保存與函數(shù)相關(guān)的變量表,closure則在執(zhí)行期動(dòng)態(tài)保存這些變量表及其運(yùn)行值。closure的生命周期有可能比函數(shù)實(shí)例長(zhǎng)。函數(shù)實(shí)例在活動(dòng)引用為空后會(huì)自動(dòng)銷毀,closure則要等要數(shù)據(jù)引用為空后,由JS引擎回收(有些情況下不會(huì)自動(dòng)回收,就導(dǎo)致了內(nèi)存泄漏)。

          別被上面的一堆名詞嚇住,一旦理解了執(zhí)行環(huán)境、調(diào)用對(duì)象、閉包、詞法作用域、作用域鏈這些概念,JS語言的很多現(xiàn)象都能迎刃而解。

          小結(jié)

          預(yù)解析,其實(shí)是在的‘解釋’階段完成,并存儲(chǔ)在語法樹中。當(dāng)執(zhí)行到函數(shù)實(shí)例時(shí),會(huì)將varDelcsfuncDecls從語法樹中復(fù)制到執(zhí)行環(huán)境的scriptObject上。

          對(duì)于示例解析:

          未定義變量意味著在scriptObject的變量表中找不到,JS引擎會(huì)沿著scriptObjectupvalue往上尋找,如果都沒找到,對(duì)于寫操作i = 1; 最后就會(huì)等價(jià)為 window.i = 1; window對(duì)象新增了一個(gè)屬性。對(duì)于讀操作,如果一直追溯到全局執(zhí)行環(huán)境的scriptObject上都找不到,就會(huì)產(chǎn)生運(yùn)行期錯(cuò)誤。

          最后,留個(gè)問題給大家:

          <script type="text/javascript">

              var arg = 1;

              function foo(arg) {

                  alert(arg);

                  var arg = 2;

              }

              foo(3);

          </script>

          請(qǐng)問alert的輸出是什么?

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

          常用鏈接

          留言簿(21)

          隨筆分類(265)

          隨筆檔案(242)

          相冊(cè)

          JAVA網(wǎng)站

          關(guān)注的Blog

          搜索

          •  

          積分與排名

          • 積分 - 916110
          • 排名 - 40

          最新評(píng)論

          主站蜘蛛池模板: 来安县| 胶南市| 渝中区| 西林县| 云梦县| 南丰县| 孝感市| 辉南县| 桂林市| 昌宁县| 呼图壁县| 正定县| 贡山| 黔西| 吉木萨尔县| 临湘市| 伊春市| 张家港市| 剑川县| 武穴市| 杨浦区| 大埔区| 广汉市| 利辛县| 青海省| 桂阳县| 太仆寺旗| 崇信县| 孟津县| 陇南市| 郎溪县| 邻水| 潞城市| 济源市| 潢川县| 中卫市| 南投市| 扎赉特旗| 辽阳市| 濉溪县| 肥西县|