Javascript中作用域的詳細(xì)介紹
1、編譯原理
在傳統(tǒng)編譯語言的流程中,程序中的一段代碼執(zhí)行前會(huì)經(jīng)歷三個(gè)步驟。統(tǒng)稱為“編譯”。
詞法分析
將代碼字符串分解成有意義的代碼塊,這些代碼塊稱為詞法單元。例如:在js中,var a = 2;。這段程序通常被拆分為以下詞法單元。var、a、2、;。至于空格是否會(huì)被當(dāng)成詞法單元,取決于空格在這門語言中是否有意思。
語法分析
將詞法單元流(數(shù)組)轉(zhuǎn)換為“抽象語法樹”(AST,Abstract Syntax Tree。編譯原理課程中提到過)。
代碼生成
將AST轉(zhuǎn)換為可執(zhí)行代碼。與語言,平臺(tái)有關(guān)(java跨平臺(tái))。簡單來說:var a = 2;的AST被轉(zhuǎn)換成一組機(jī)器指令,用來創(chuàng)建一個(gè)a的變量(分配內(nèi)存等),并將2存儲(chǔ)在a中。
而對(duì)于Javascript而言,盡管通常它被歸類為“動(dòng)態(tài)”或“解釋執(zhí)行”語言,但實(shí)際上它是一門編譯語言。所不同的是,在它編譯時(shí)引擎要執(zhí)行更復(fù)雜的操作過程。
首先,Javascript引擎不會(huì)有大量的(向其他編譯器那么多的)時(shí)間來進(jìn)行優(yōu)化,因?yàn)榕c其他語言不同,它的編譯過程不是在構(gòu)建之前的。
對(duì)于Javascript而言,大部分編譯發(fā)生在代碼執(zhí)行前的幾微秒(甚至更短)。所以引擎會(huì)用盡各種方法(比如JIT)來保證性能最佳。
簡單的說,任何Js代碼在執(zhí)行前都要編譯(幾微秒前)。因此,在執(zhí)行var a = 2;這段代碼前,引擎會(huì)先編譯,然后做好執(zhí)行它的準(zhǔn)備(加入到代碼隊(duì)列)。通常是馬上執(zhí)行。
2、理解作用域
引擎
負(fù)責(zé)整個(gè)編譯以及執(zhí)行過程。
編譯器
引擎的好朋友之一,負(fù)責(zé)語法分析和代碼生成等臟活累活。
作用域
引擎的另一個(gè)好朋友,負(fù)責(zé)收集和維護(hù)所有變量,并實(shí)施一套非常嚴(yán)格的規(guī)則,以保證當(dāng)前代碼(作用域)對(duì)變量的訪問權(quán)限。
對(duì)于var a = 2;,它不僅僅是一句簡單的聲明。聲明它有兩個(gè)過程。編譯時(shí):編譯器進(jìn)行相關(guān)操作。執(zhí)行時(shí),Js引擎進(jìn)行相關(guān)操作。
var a,編譯器會(huì)在當(dāng)前作用域查找是否有a這個(gè)變量。如果有,則編譯器忽略此聲明。否則,在當(dāng)前作用域創(chuàng)建一個(gè)a變量(分配內(nèi)存)。
a = 2,接下來編譯器(語法分析,代碼生成…)生成運(yùn)行時(shí)所需的代碼用來處理這個(gè)賦值操作。具體的賦值操作由Js引擎負(fù)責(zé)。Js引擎會(huì)在當(dāng)前作用域查找a這個(gè)變量,如果找到,就進(jìn)行賦值操作。否則,在父級(jí)作用域查找(作用域嵌套),直至全局作用域。如果找到,進(jìn)行賦值操作。找不到拋出異常。
在查找作用域的過程中,會(huì)涉及到LHS查詢和RHS查詢。它們分別代表賦值操作的目標(biāo)和賦值操作的源頭。不僅僅是賦值操作,更有函數(shù)賦值操作等等。比如:
function foo(a){
console.log(a);
}
foo(2);
最后一行foo()函數(shù)的調(diào)用需要對(duì)foo()本身進(jìn)行RHS查詢。在全局作用域中找到了foo的聲明。并且()意味著要把foo當(dāng)做一個(gè)函數(shù)執(zhí)行,所以foo最好是一個(gè)函數(shù),否則會(huì)報(bào)錯(cuò)。
還有一個(gè)容易忽視的細(xì)節(jié)。在把2作為實(shí)參傳入到foo的形參時(shí),會(huì)有一個(gè)隱式的a=2操作。a是賦值操作的源頭,2是賦值操作的目標(biāo)。所以這里對(duì)a進(jìn)行了一次LHS查詢。由于在編譯過程中在當(dāng)前作用域(函數(shù)作用域)將a聲明為foo的一個(gè)形參了,所以可以找到。
然后就是console.log(a);,console本身也需要一個(gè)LHS查詢,它是在window下面的內(nèi)置對(duì)象,所以可以找到。然后對(duì)a進(jìn)行RHS查詢。幸運(yùn)的是,在將2賦值給函數(shù)形參a的時(shí)候,a已經(jīng)聲明并賦值了。所以這個(gè)RHS是可以進(jìn)行的。
3、作用域嵌套
在之前我們說過,作用域負(fù)責(zé)收集和維護(hù)所有變量,并實(shí)施一套非常嚴(yán)格的規(guī)則,以保證當(dāng)前代碼(作用域)對(duì)變量的訪問權(quán)限??紤]以下代碼:
function foo(a){
console.log(a+b);
}
var b = 2;
foo(2);
我們只考慮這里對(duì)b的RHS引用。Js引擎開始試圖在foo函數(shù)作用域查找b變量,但是并沒有找到。于是,Js引擎就會(huì)突破當(dāng)前限制,去外層作用域查找。哎呀,找到了!于是就對(duì)b進(jìn)行RHS引用成功了。當(dāng)然呢,要是沒找到的話,Js引擎也不會(huì)放棄,會(huì)繼續(xù)往外層作用域查找,直到找到全局作用域。然后遵循的規(guī)則參照a=2賦值那塊。
4、異常
在一個(gè)變量還沒有聲明(任何作用域都無法查到)的情況下,LHS和RHS查詢失敗后的操作是不一樣的。可以預(yù)料,RHS查詢失敗會(huì)拋出一個(gè)異常,那么LHS查詢失敗呢?
function foo(a){
console.log(a+b);
b = a;
}
foo(2);
第一次對(duì)b進(jìn)行RHS查詢時(shí),在任何作用域無法找到該變量的聲明。那么有小伙伴就疑惑了,b=a呢?不是對(duì)b的聲明嗎?答案是:是。這里確實(shí)是對(duì)b的聲明。
但在對(duì)作用域查找的過程中,只會(huì)向上查找聲明(涉及到聲明提升)。由于這里b是在console.log()后面定義的。所以是失敗的,拋出ReferenceError異常。值得注意的是,ReferenceError是非常重要的異常類型。再考慮下述代碼:
function foo(a){
b = a;
console.log(a+b);
}
foo(2);
這里呢,第一次對(duì)b進(jìn)行的是LHS查詢。如果在頂層(全局)作用域也無法查到foo的話,那么Js引擎就會(huì)很熱心的幫你在全局作用域創(chuàng)建一個(gè)b變量,前提是在非“嚴(yán)格模式”下,在一個(gè)作用域內(nèi)加上代碼“use strict”,表明使用嚴(yán)格模式。在嚴(yán)格模式下,LHS查詢失敗時(shí),并不會(huì)創(chuàng)建一個(gè)全局變量,而是拋出同RHS查詢失敗時(shí)類似的ReferenceError異常。
接下來,加入你找到了這個(gè)變量,但是你試圖對(duì)這個(gè)變量進(jìn)行不合理的操作。如:對(duì)一個(gè)非函數(shù)類型的變量進(jìn)行()函數(shù)調(diào)用、對(duì)null或undefined類型的值進(jìn)行訪問,那么引擎會(huì)拋出另一種類型的異常,叫做TypeError。
總之,RefercenError同作用域判別失敗相關(guān),而TypeError表示作用域判別成功,但是對(duì)結(jié)果的操作是不合法的。
5、小結(jié)
作用域是一套規(guī)則,規(guī)定在何處以及如何查找變量(加上之前說的,重要的事情說三遍)。如果查找的目的是賦值,就是進(jìn)行LHS查詢。如果目的是獲取變量的值,就會(huì)進(jìn)行RHS查詢。
Js引擎會(huì)在代碼執(zhí)行前對(duì)其進(jìn)行編譯。var a = 2;,這樣的操作會(huì)被分成兩個(gè)步驟。
1.編譯時(shí), 編譯器聲明a變量,即var a。
2. 運(yùn)行時(shí),對(duì)a變量進(jìn)行賦值。a=2。
LHS查詢和RHS查詢失敗會(huì)進(jìn)行不同的操作。RHS查詢失敗會(huì)拋出ReferenceError異常。LHS查詢失敗會(huì)在全局作用域創(chuàng)建變量(非嚴(yán)格模式),在嚴(yán)格模式下拋出ReferenceError異常。
以上就是本文的全部內(nèi)容,希望對(duì)大家有所幫助,希望大家繼續(xù)關(guān)注腳本之家的最新內(nèi)容。
相關(guān)文章
教你用wxml2canvas將微信小程序頁面轉(zhuǎn)為圖片
如果需要實(shí)現(xiàn)將小程序的頁面轉(zhuǎn)為圖片,第一步是要先把頁面轉(zhuǎn)為canvas,再將canvas轉(zhuǎn)為圖片,下面這篇文章主要給大家介紹了關(guān)于用wxml2canvas將微信小程序頁面轉(zhuǎn)為圖片的相關(guān)資料,需要的朋友可以參考下2022-11-11
使用JavaScript進(jìn)行進(jìn)制轉(zhuǎn)換將字符串轉(zhuǎn)換為十進(jìn)制
JS 是一個(gè)很神奇的語言,可以將任意進(jìn)制字符串轉(zhuǎn)換為十進(jìn)制,如二進(jìn)制,八進(jìn)制,十六進(jìn)制, 第二數(shù)數(shù)不寫即為最常用的轉(zhuǎn)換為整型十進(jìn)制2014-09-09
詳解微信小程序「渲染層網(wǎng)絡(luò)層錯(cuò)誤」的解決方法
這篇文章主要介紹了詳解微信小程序「渲染層網(wǎng)絡(luò)層錯(cuò)誤」的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
深入了解Javascript的事件循環(huán)機(jī)制
單線程的同步等待極大影響效率,任務(wù)不得不一個(gè)一個(gè)等待執(zhí)行,對(duì)于網(wǎng)頁應(yīng)用是無法接受的。所以Javascript使用事件循環(huán)機(jī)制來解決異步任務(wù)的問題。本文就來講講Javascript的事件循環(huán)機(jī)制,希望對(duì)你有所幫助2022-09-09
小程序跳轉(zhuǎn)到的H5頁面再跳轉(zhuǎn)回跳小程序的方法
這篇文章主要介紹了小程序跳轉(zhuǎn)到的H5頁面再跳轉(zhuǎn)回跳小程序的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
JavaScript字符串的json的自定義加密解密函數(shù)示例
JavaScript自定義函數(shù)中使用String.fromCharCode函數(shù)將輸入字符串中每個(gè)字符的Unicode編碼加1,然后將加密后的字符拼接成一個(gè)新字符串返回,調(diào)用JSON.stringify函數(shù)轉(zhuǎn)換json成一個(gè)普通字符串2023-12-12

