【JS深淵】系列是我用來對前端原生javascript語言相關技術進行的深度探究或者筆記記錄,我我的是一個很愛去挖層的這麼一我的,但不幸的是覺悟的太晚,但我不會放棄。決定開始寫博客目的有兩個,一個是有儀式性的去持續提升本身的技術能力,另外一個就是但願向上再向上。不過本身能力也是有限的,若是寫的很差或者出現什麼紕漏又或者錯誤的地方,脫褲式歡迎你們的建議、指正。沒錯看着我,對,盯着你的屏幕,別眨眼,請記住個人這句話:
您的反饋是就是我持續進步的動力。
好了,收!biu~javascript
javascrip是一個弱類型解釋性語言,依賴於瀏覽器JS引擎進行解析執行。裏面的解析過程也十分複雜。可是稍微瞭解一下,對javascript語言的掌握有着很大的幫助,可以使咱們在平常的開發過程當中更加靈活運用js語言自己的特性。廢話很少說了,開始:🤣🤣html
lotoze我先來段風騷的代碼前端
console.log("123")
console.log("456");
複製代碼
某某俠士:「喂,糟老頭!你在逗我玩?「,哪裏風騷了,不就是兩句打印嗎? java
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<script type="text/javascript">
//js代碼塊...
</script>
</body>
</html>
複製代碼
這一過程,專業一點叫作語法分析,每一個計算機語言都會有這一步,並且計算機語言的區分的奧妙就在語法分析、詞法分析、語義分析的差別。不過這裏只說語法分析,能夠理解爲對加載好的代碼的一次的初次檢驗。
首先,系統,不,確切的說是js引擎線程並不會當即執行文件中的js代碼,而是會先通讀一遍js代碼,看看是否有低級的語法錯誤,若是有低級語法錯誤,那麼就當即中止並拋出錯誤到控制檯;若是沒有低級的語法錯誤,就會爲接下來js代碼的真正執行作一些準備工做。可是這樣說真的對嗎?必定要注意理解! 咱們來看一下代碼:數組
console.log("123");
console.log"456");
console.log("789");
複製代碼
咱們先來假設一下:按照剛纔咱們所說的js代碼並不會當即執行,而是會先進行通讀一遍代碼,若是有低級錯誤就拋出,並當即中止後續的代碼。那麼按照這種理解,最後的結果就是:直接拋出低級錯誤,"123"並不會輸出。可是你會驚奇的發現😱😱,"123"竟然正常輸出了,第二行報錯,後邊的不執行。最終結果爲:瀏覽器
console.log("123"); //正常打印"123"
console.log"456"); //拋出低級錯誤,當即中止解析執行
console.log("789");
複製代碼
這說明什麼?bash
說明: js引擎在通讀代碼作分析時,並非讀所有的js代碼,而是一行一行的去讀,若是這一行代碼沒有低級語法錯誤,那麼是這一行代碼進入js執行的下一準備階段,若是這一行代碼有低級語法錯誤則會在這一行拋出錯誤並當即中止。注意是 這一行。數據結構
這又是爲何?爲何要一行一行的讀?閉包
由於javascript語言是解釋性的語言,是解釋一行執行一行。那麼這裏請跟lotoze去發散思考一下,解釋性語言是否是都是解釋一行執行一行呢?答案是確定的。若是是先等到所有的代碼解釋完成再同一執行,這性能得有多慢啊,黃花菜都涼了。值得一提的是,一有錯誤就會當即中止,這是單線程計算機語言的特性。異步
預編譯是js代碼執行前一刻的準備過程。爲js的執行提供一些必要所需,在加載好的js代碼經歷過js引擎的語法分析檢驗以後會當即進入此階段。
首先,想要了解這一階段咱們須要先明確幾個重要的概念。
顧名思義,隱式屬性即爲系統內部調用,不能被咱們看到或使用的屬性。
做用域分爲全局做用域和函數做用域,能夠說這兩類出現緣由是由於函數的出現,函數有着本身專屬的做用域。
做用域有個比較專業一點的名字叫作執行期上下文。其實看着高大上的名字,本質就是一個對象,裏面存儲着js代碼執行時所須要的必要「糧食」。
//全局做用域
...
function test() {
//局部/函數做用域
...
}
複製代碼
做用域鏈專業一點的說法叫作,執行期上下文的集合按照必定的規律造成的鏈式結構叫作做用域鏈。 通俗一點講,執行期上下文穿串了,而後串太長了,成了個鏈子la😂😂
咱們說,在js中的數據結構受Java的影響很大,在js中也是一切皆對象,函數也能夠看作一個對象,對象能夠擁有屬性和方法,屬性分爲顯式屬性和隱式屬性。在函數對象上都有着一個叫作[[scopes]]的隱式屬性。這個屬性的做用就是存儲着執行期上下文的集合。換句話說,[[scopes]]就是做用域鏈。它是一個棧結構,能夠理解爲是一個數組,具備棧結構First in, last out的特性。
var b = 20;
function test(argument) {
var a = 10;
}
複製代碼
下面是在控制檯打印出來的test函數體,能夠看到[[scopes]]:
基本的概念已經瞭解了,可是咱們進一步思考一下,咱們說預編譯是爲js代碼真正執行階段準備過程,那麼預編譯到底準備了什麼?準備的過程是怎樣的?
*下面我來總結一下預編譯的過程,預編譯的執行過程分爲兩步:
預編譯是變量聲明提高和函數聲明提高的根本緣由。通過預編譯過程以後,代碼將當即進入真正的執行階段。
這裏咱們來一個的栗子,來進行理解一下預編譯執行過程。
console.log(a);
console.log(b);
var a = 10;
var b = a;
function a(a) {
var a = "aaa";
console.log(a);
}
function foo(b) {
var a = 100;
var b = a;
console.log(a);
}
foo();
a("lalala");
//請問上述代碼打印什麼?
複製代碼
這個栗子若是你只知道變量聲明提高和函數聲明提高,想要得出正確結果然的很麻煩。可是這個栗子實質上就是js代碼的預編譯過程。 咱們來分析一下:
當js代碼加載以後,通過語法分析後,進入程序運行的前一刻的預編譯階段。這個階段分爲全局預編譯階段和函數預編譯階段。 那麼咱們依據這個栗子去具體分析一下預編譯過程:
//Go
{}
複製代碼
//GO
{
a: undefined,
b: undefined
}
複製代碼
//GO
{
a: a(){}, //賦值爲函數體
b: undefined
}
複製代碼
// aAO --> 表示a函數的AO對象
{}
複製代碼
//aAO --> 表示a函數的AO對象
{
a: undefined
}
複製代碼
//aAO --> 表示a函數的AO對象
{
a: "lalala"
}
複製代碼
//aAO --> 表示a函數的AO對象
{
a: a(){}
}
複製代碼
//aAO --> 表示foo函數的AO對象
{
a: undefined,
b: undefined
}
複製代碼
這時預編譯過程就已經完成了,生成了這個一個GO和兩個AO對象,每一個預編譯對象(虛構的,爲了理解,包括GO和AO)生成完畢的同時都會當即壓入[[scopes]]棧中,一般GO先入棧,而後是當前的AO對象入棧。下一步就是真正的執行了,調用棧則會按照先進後出的方式出棧執行。 最後的結果爲:
console.log(a); //a(){}
console.log(b); //undefined
var a = 10;
var b = a;
function a(a) {
var a = "aaa";
console.log(a);
}
function foo(b) {
var a = 100;
var b = a;
console.log(a);
}
foo(); //100
a("lalala"); //報錯a is not a function
複製代碼
js的內部執行階段,是一個很是龐雜的過程。老頭我會去使用專門的篇幅來寫。這裏只作一個簡單的介紹。
當加載的js代碼通過語法分析以及預編譯後,會進入js引擎線程會把js代碼劃分爲的宏任務(包括同步任務、異步任務)、微任務。而後按照宏任務(同步任務)-->微任務-->宏任務(異步任務)的輪詢中。
lotoze | 【原創】
着重說明:裏面一些表情圖片並不是原創,只是爲了讀者讀起來不是那麼枯燥乏味。但若是原做者以爲有侵犯版權的意思,請使用下方聯繫方式與我聯繫,爲了尊重原創做者的辛苦創做,我將及時處理!
固然,沒事也能夠聯繫啦😘😘歡迎交流!
寫做不易,
若是您還以爲湊合,就給個贊!
若是以爲確實以爲: 「老傢伙,有你的啊!」就加個關注!
若是文章有任何的錯誤,脫褲式歡迎你們來進行批評指正!
每個鼓勵都是lotoze我持續拋頭顱,撒雞血的創做動力!
每個批評反饋也都是lotoze我持續成長的臺階!