標籤:pre keyword 方便 文法分析 detail mod 維護 使用 編譯器
JS的容錯率很高,一些其他語言常見的小錯誤JS都能大度得包容,比如給一個方法傳入超出預計的參數、在聲明變數之前使用該變數(變數的聲明提升解決了這個問題)等等,這裡我們就要解剖一下JS變數重複聲明以及當我們忽略var使用 a=2來聲明變數時a為全域變數的問題:
[javascript] view plain copy
- //第一段代碼
- var a = 2;
- var a = 3;
- alert(a);//3
- //第二段代碼
- <span style="font-size:18px;"></span><pre name="code" class="javascript">a = 2;
- alert(a);//2
這兩段代碼在JS的眼中是完全可行的,JS會默默忽略掉第二個var聲明來將程式繼續執行下去,而且後面聲明的值會覆蓋掉前面聲明的值,而第二段代碼JS會將忽略var的聲明預設聲明為全域變數。這些大家都應該很清楚,但是JS遇到重複聲明時背後到底是怎樣啟動並執行呢?那就關係到了JS的幕後黑手:引擎以及他的左膀右臂:編譯器以及範圍。
在JS代碼運行過程中:
引擎負責整個代碼的編譯以及運行,編譯器則負責詞法分析、文法分析、代碼產生等工作而範圍則如我們熟知的一樣,負責維護所有的標識符(變數)。
當我們執行上面的代碼時,我們可以簡單的理解為新變數分配一塊兒記憶體,命名為a,並賦值為2,但在啟動並執行時候編譯器與引擎還會進行兩項額外的操作:判斷變數是否已經聲明:
1.首先編譯器對代碼進行分析拆解,從左至右遇見var a,則編譯器會詢問範圍是否已經存在叫a的變數了,如果不存在,則招呼範圍聲明一個新的變數a,若已經存在,則忽略var 繼續向下編譯,這時a = 2被編譯成可執行檔代碼供引擎使用。
2.引擎遇見a=2時同樣會詢問在當前的範圍下是否有變數a,若存在,則將a賦值為2(由於第一步編譯器忽略了重複聲明的var,且範圍中已經有a,所以重複聲明會發生值得覆蓋而並不會報錯)。若不存在,則順著範圍鏈向上尋找,若最終找到了變數a則將其賦值2,若沒有找到,則招呼範圍聲明一個變數a並賦值為2(這就是為什麼第二段代碼可以正確執行且a變數為全域變數的原因,當然,在strict 模式下JS會直接拋出異常:a is not defined)。
雖然JS很勤勞,可以幫我們解決一些小問題,但是作為程式員的我們最好按照代碼規範來進行書寫,於人於己都大有裨益,何樂而不為呢。
註:關於a = 2 a會被聲明為全域變數其中涉及到LHS查詢方式,如需瞭解請移步至本人另一篇文章:JS引擎之LHS RHS
在書寫代碼的時候我們無時無刻不在與範圍較勁,而引擎是如何在沿著範圍鏈把我們想要的東西尋找出來的呢?這裡就涉及到了L與R的區別。
通過字面意思就很容易理解L代表left R代表right,而LHS與RHS查詢我們可以先簡單的區分為:查詢在 = 號左邊的變數時,引擎使用LHS,查詢在 = 右邊的變數時,引擎使用RHS。LHS查詢出來的是變數的地址,方便進行形如a = 2的賦值操作,因為引擎根本不需要關心a裡面存的是什麼鬼,按照程式猿的要求把2塞給a就可以了,而RHS查詢出來的是變數儲存的值,以便形如 a = b的賦值操作,引擎同樣不需要關心 b 放在記憶體的哪個“格子”,只需要知道格子裡面放的什麼就可以了。
當然, 根據 = 左右來區分LHS RHS是不全面的,因為我們很容易漏掉一些隱藏的LHS與RHS:
[javascript] view plain copy
- var c =3;
- function a(b){
- console.log(b+c);
- }
- a(2);
在上面一段代碼中,我們可以很明顯的得出 c ...使用了LHS,console.log()中的b、c使用了RHS,但是在調用函數a、console.log的時候同樣使用了RHS,參數b的賦值也同樣使用了LHS,所以我們最好通過取值、取地址這兩個行為來判斷引擎使用的查詢方式。
對於上面的代碼,引擎與範圍是這樣交流的:
引擎:全域範圍,我想找一下c,你見過他嗎?
全域範圍:嗨,別提了,編譯器那小子剛剛聲明了它,拿去吧!
引擎:太棒了!我現在要把3丟給他
引擎:等一下,還有一個事情想麻煩你一下,我想對a函數進行引用,你知道她在哪裡嗎?
全域範圍:就是那個跟c一起被丟進來的傢伙把?喏,在這裡呢,給你。
引擎:哈哈,太感謝了,這樣我只需要把2.....。
a函數範圍:薩瓦迪卡,引擎,今天天氣不錯啊,一起出去玩吧!
引擎:別提了,我手頭上忙的要死,對了,你碰到過一個叫b的嗎?
a函數範圍:哦,他是a函數的一個形參,我這剛好有,拿去用
引擎:夠哥們,這樣我只需要把2放到b裡面,之後.....哎,小a,console你有麼
a函數範圍:有有有,這是個內建對象,給你
引擎:哈哈,你一直這麼靠譜,我找找,哎呦,真有log這個函數,我得趕緊引用他
引擎:你看我這腦子,你能在幫我找一下b麼,我得確認一下b的內容
a函數範圍:放心,看!b沒有變過,放心
引擎:那最好了,就差最後一步了,做完喝酒去,你那裡有沒有c,交出來我請你一包辣條!
a函數範圍:真的麼!我找找,嗯.....不行,我這裡沒有,你得去問問我大哥 全域範圍
引擎:全域範圍,不好意思,又來找你了,不知道你有沒有c,我拿辣條跟你換
全域範圍:看你累的滿頭大汗的,辣條你自己留著吧,c給你,做完快歇歇吧
引擎:麼麼噠,你最好了,晚上我請你吃飯!
看完上面的對話,不知道你對LHS RHS是否有了足夠的瞭解,還有一點需要注意的就是,當尋找到全域範圍時,若還沒有尋找到要找的變數資訊,若為LHS查詢,會預設聲明一個與請求的變數同名的全域變數,而RHS則會拋出錯誤,當然,在strict 模式下,LHS也同樣會報錯,這是需要注意的地方。
【repost】 JS變數重複聲明以及忽略var 聲明的問題及其背後的原理