JS中的作用域(一)-详谈
本篇文章在于詳細解讀JavaScript的作用域,從底層原理來解釋一些常見的問題,例如變量提升、隱式創(chuàng)建變量等問題,在和大家一起交流進步的同時,也算對自己知識掌握的記錄,方便以后復習
首先,直接撿干的來,JS作用域大致分為三部分:詞法作用域、函數作用域/塊作用域、閉包。
在傳統的編譯語言中,程序的源代碼編譯由三個步驟組成:詞法分析、語法分析、代碼生成。而JS屬于動態(tài)語言,它的編譯過程不發(fā)生在構建之前,而是在代碼執(zhí)行前(一般只有幾微妙,甚至更短),簡單說,任何JS代碼執(zhí)行前都要編譯,編譯完通常馬上就要執(zhí)行。
例如: var a = 2; 將其分解為以下步驟:
1.遇到? ?var a? 編譯器會詢問作用域是否已經存在同名變量于同一個作用域的集合中,若存在,則忽略該聲明。若不存在,編譯器在當前作用域聲明一個新變量a。
2.接下來編譯器會為引擎生成運行時的代碼,這些代碼用于處理? a = 2的賦值操作。引擎運行時會詢問作用域,當前作用域是否存在變量a,若存在,引擎直接使用該變量。否則引擎繼續(xù)向上查找,直到頂層全局作用域還未找到,則會拋出ReferenceError,如果找到,就會將2賦值給它。? ?
在上面的例子中,引擎有兩種查詢方式:LHS、RHS。
其中L、R代表“左”、“右”,是相對于賦值操作的左右,當變量出現在賦值操作的左側時進行LHS查詢,出現在右側時進行RHS查詢,也可以這么理解,RHS查詢是找到某個變量的值,而LHS是找到變量的容器本身!!!即作用域中開辟的變量存放空間。舉個例子:如下代碼? ?console.log(a); 引擎對a的查詢就是RHS,這里沒有賦值操作,需要查找a的值,并把它傳給console.log(..);函數。在逐級向上查找中,直到全局也沒找到,則拋出ReferenceError。但LHS若沒找到是不會拋出錯誤的。具體原因繼續(xù)看。
舉個例子:? a= 2; 這里對a的引用則是LHS引用,我們并不關心但前值是多少,只是想要為 =2 這個賦值操作找到合法目標,可能有童鞋疑問,=2不就是賦給a的嘛?對啊,但是a到底存不存在呢?在當前作用域中,我們是不知道是否創(chuàng)建了a的存儲空間的,如果作用域中存在 var a ,那么該a的存儲空間存在,LHS能成功,但是沒有a的存儲空間呢?也就是a并未創(chuàng)建呢?此時,LHS也不會拋出錯誤,而是隱式的在當前作用域(全局作用域、即最高層作用域,一層一層找上去的)為我們創(chuàng)建變量a的存儲空間,然后把 =2 賦值給a。這也就是為啥 var a =2; 創(chuàng)建的是局部變量,而沒有 var 申明的變量是全局變量的原因。
作用域的嵌套
作用域就是一套如何存儲和查找變量的規(guī)則。在嵌套作用域中,如上圖,在foo()中無法找到變量 b,引擎就會在外層嵌套的作用域中繼續(xù)查找,直到找到該變量,或者到達最頂層(全局作用域)。上圖想要執(zhí)行console.log()函數,就要對b進行RHS查找,得到其值。才foo中無法完成b的RHS,但在外層中卻可以完成。即:引擎從當前的執(zhí)行作用域中開始查找變量,找不到,則逐級向上查找,直到最頂層作用域。
區(qū)分LHS和RHS
區(qū)分兩種查詢方式很重要,因為上文簡單提到RHS找不到會拋出ReferenceError,而LHS則不會,它會隱式創(chuàng)建所需的變量。如下? ? ? ? ? ? ?
對b的RHS查找失敗,因為沒有聲明(創(chuàng)建)變量b,未聲明的變量,在任何作用域中都無法找到!那么,在上圖中,只要把 b= 2放在console.log()之前,函數就成功執(zhí)行了,因為第一步執(zhí)行 b= 2;賦值操作進行LHS,找不到,則在全局中隱式創(chuàng)建變量b,此時使用 window.b 是可以得到2的。
詞法作用域
詞法作用域就是定義此法階段的作用域,即你寫代碼時將變量和作用域寫在哪里而決定其作用范圍。作用域在查找到第一個標識符時即停止查找,多層嵌套的作用域中同名的標識符,內部遮蔽外部(遮蔽效應),全局遮蔽可用window.得到其值,而局部遮蔽的則無論如何都無法被訪問到。無論函數在哪里被調用,以何種方式調用,其詞法作用域都只由被聲明時所處的位置決定,即你寫下哪他就在哪發(fā)揮作用。
?
上圖中,全局作用域中只有一個標識符,即foo,函數foo作用域中有三個標識符,即b,bar ,a 。函數bar里面只有一個標識符 c 。其每個標識符處于不同的作用域中,而代碼運行時會以他們不同的位置而訪問權限不同。這些位置在書寫時已經被我們寫死了,他們的作用被我們寫好了,這就是詞法作用域!代碼的位置真的被我們“寫死了嘛”?接著看
詞法欺騙
詞法作用域由寫代碼時聲明的位置決定,也可以由兩種機制來動態(tài)改變詞法作用域。
1.? eval()函數。可接受一個字符串為參數,將其中內容視為好像在書寫時就在這個地方的代碼。可以理解為我在夢中就是高富帥,真實的就連后面的劇情(夢中劇情,哈哈)都是以高富帥為基礎開展的,不知道這個比喻貼切不?即就是eval()可以讓里面的參數代碼段達到書寫時就在這個地方的效果。如下:
輸出結果為a:5,b:8,而不是a= 2,根據詞法作用域中。foo中找不到a,則到上一層作用域中尋找,上一層中找到了 a = 2 ;。可是eval()函數卻欺騙了詞法作用域,直接將a放在了foo內部,而導致引擎不需要到外層作用域去查找,直接使用 a = 5 ,從而達到此法欺騙。
2.? with語句。with 語句通常用作重復引用一個對象的多個屬性的快捷方式。代碼如下:
with語句也可以達到欺騙詞法的作用,但是副作用也很明顯,造成了變量泄露。原因是調用obj2的時候,其沒有變量a,進行LHS查詢,最后隱式創(chuàng)建全局變量屬性a ,導致變量泄露。
以上兩種詞法欺騙方式,第一嚴重影響性能,第二在嚴格模式下有諸多限制,所以不建議使用。函數作用域和閉包近期在整理,過幾天推出
作者:方紅亮
博客:https://www.cnblogs.com/fanghl/p/9369414.html
今天只介紹了作用域和詞法作用域,希望對小伙伴理解有所幫助,分享轉載的朋友請注明出處,碼字不易,謝謝理解!
轉載于:https://www.cnblogs.com/fanghl/p/9369414.html
總結
以上是生活随笔為你收集整理的JS中的作用域(一)-详谈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 下颌角整形费用
- 下一篇: 阿胶糕多少钱一盒啊?