摘要:所以覺得把這個執行的詳細過程整理一下,幫助更好的理解。類似的語法報錯的如下圖所示三預編譯階段代碼塊通過語法分析階段之后,語法都正確的下回進入預編譯階段。另開出新文章詳細分析,主要介紹執行階段中的同步任務執行和異步任務執行機制事件循環。
一、概述
js是一種非常靈活的語言,理解js引擎的執行過程對于我們學習js是非常有必要的。看了很多這方便文章,大多數是講的是事件循環(event loop)或者變量提升的等,并沒有全面分析其中的過程。所以覺得把這個js執行的詳細過程整理一下,幫助更好的理解js。
1.1基礎概念js是單線程語言。
在瀏覽器中一個頁面永遠只有一個線程在執行js腳本代碼
js是單線程怨言,但是代碼解析是非常迅速的,不會發生解析阻塞。
js是異步執行的,通過實踐循環(event loop)方式實現的
暫時我們不考慮事件循環(event loop),我們先來看這樣一段代碼,來確定我們是否理解js引擎的執行過程
console.log(person) console.log(personFun) var person = "saucxs"; console.log(person) function personFun() { console.log(person) var person = "songEagle"; console.log(person) } personFun() console.log(person)
可以自己直接使用瀏覽器看出輸出結果
首先我們來分析一下上面的代碼,雖然很多開發人員基本上都能答出來,但是還是要啰嗦一下。
全面分析js引擎的執行過程,分為三個階段
1、語法分析
2、預編譯階段
3、執行階段
說明:瀏覽器先按照js的順序加載
三、預編譯階段js代碼塊通過語法分析階段之后,語法都正確的下回進入預編譯階段。
在分析預編譯階段之前,我們先來了解一下js的運行環境,運行環境主要由三種:
1、全局環境(js代碼加載完畢后,進入到預編譯也就是進入到全局環境)
2、函數環境(函數調用的時候,進入到該函數環境,不同的函數,函數環境不同)
3、eval環境(不建議使用,存在安全、性能問題)
每進入到一個不同的運行環境都會創建 一個相應的執行上下文(execution context),那么在一段js程序中一般都會創建多個執行上下文,js引擎會以棧的數據結構對這些執行進行處理,形成函數調用棧(call stack),棧底永遠是全局執行上下文(global execution context),棧頂則永遠時當前的執行上下文。
3.1函數調用棧什么是函數調用棧?
函數調用棧就是使用棧存取的方式進行管理運行環境,特點是先進后出,后進后出
我們來分析一下簡答的js代碼來理解函數調用棧:
function bar() { var B_context = "bar saucxs"; function foo() { var f_context = "foo saucxs"; } foo() } bar()
上面代碼塊通過語法分析后,進入預編譯階段,如圖所示
1、首先進入到全局環境,創建全局執行上下文(global Execution Context ),推入到stack中;
2、調用bar函數,進入bar函數運行環境,創建bar函數執行上下文(bar Execution Context),推入stack棧中;
3、在bar函數內部調用foo函數,則再進入到foo函數運行環境中,創建foo函數執行上下文(foo Execution Context),如上圖,由于foo函數內部沒有再調用其他函數,那么則開始出棧;
5、foo函數執行完畢之后,棧頂foo函數執行上下文(foo Execution Context)首先出棧;
6、bar函數執行完畢,bar函數執行上下文(bar Execution Context)出棧;
7、全局上下文(global Execution Cntext)在瀏覽器或者該標簽關閉的時候出棧。
說明:不同的運行環境執行都會進入到代碼預編譯和執行兩個階段,語法分析則在代碼塊加載完畢時統一檢查語法。
3.2創建執行上下文執行上下文可以理解成當前的執行環境,與該運行環境相對應。創建執行上下文的過程中,主要是做了下面三件事,如圖所示:
1、創建變量對象(variable object)
2、創建作用域鏈(scope chain)
3、確定this的指向
3.2.1創建變量對象創建變量對象主要是經過以下過程,如圖所示:
1、創建arguments對象,檢查當前上下文的參數,建立該對象的屬性與屬性值,僅在函數環境(非箭頭函數)中進行的,全局環境沒有此過程。
2、檢查當前上下文的函數聲明,按照代碼順序查找,將找到的函數提前聲明,如果當前上下文的變量對象沒有該函數名屬性,則在該變量對象以函數名建立一個屬性,屬性值則指向該函數所在堆內存地址引用,如果存在,則會被新的引用覆蓋掉。
3、檢查當前上下文的變量聲明,愛去哪找代碼順序查找,將找到的變量提前聲明,如果當前上下文的變量對象沒有變量名屬性,則在該變量對象以變量名建立一個屬性,屬性值為undefined;如果存在,則忽略該變量聲明。
說明:在全局環境中,window對象就是全局執行上下文的變量對象,所有的變量和函數都是window對象的屬性方法。
所以函數聲明提前和變量聲明提升是在創建變量對象中進行的,且函數聲明優先級高于變量聲明。
下面我們再來分析這個簡單代碼
function fun(m,n){ var saucxs = 1; function execution(){ console.log(saucxs) } } fun(2,3)
這里我們在全局環境中調用fun函數,創建fun的執行上下文,這里暫時不說作用域鏈以及this指向的問題。
funEC = { //變量對象 VO: { //arguments對象 arguments: { m: undefined, n: undefined, length: 2 }, //execution函數 execution:, //num變量 saucxs: undefined }, //作用域鏈 scopeChain:[], //this指向 this: window }
1、funEC表示fun函數的執行上下文(fun Execution Context 簡寫為funEC);
2、funEC的變量對象中arguments屬性,上面這樣寫只是為了理解,在瀏覽器中展示以類數組的方式展示的
3、
說明:創建變量對象發生在預編譯階段,還沒有進入到執行階段,該變量對象都不能訪問的,因為此時的變量對象中的變量屬性尚未賦值,值仍為undefined,只有在進行執行階段,變量中的變量屬性才進行賦值后,變量對象(Variable Object)轉為活動對象(Active Object)后,才能進行訪問,這個過程就是VO->AO過程。
3.2.2創建作用域鏈作用域鏈由當前執行環境的變量對象(未進入到執行階段前)與上層環境的一系列活動對象組成,保證了當前執行還款對符合訪問權限的變量和函數有序訪問。
理解清楚作用域鏈可以幫助我們理解js很多問題包括閉包問題等,下面我們結合一個例子來理解一下作用域鏈。
var num = 30; function test() { var a = 10; function innerTest() { var b = 20; return a + b } innerTest() } test()
在上面例子中,當執行到調用innerTest函數,進入到innerTest函數環境。全局執行上下文和test函數執行上下文已進入到執行階段,innerTest函數執行上下文在預編譯階段創建變量對象,所以他們的活動對象和變量對象分別是AO(global),AO(test)和VO(innerTest),而innerTest的作用域鏈由當前執行環境的變量對象(未進入到執行階段前)與上層環境的一系列活動對象組成,如下:
innerTestEC = { //變量對象 VO: {b: undefined}, //作用域鏈 scopeChain: [VO(innerTest), AO(test), AO(global)], //this指向 this: window }
我們這里可以直接使用數組表示作用域鏈,作用域鏈的活動對象或者變量對象可以直接理解成作用域。
1、作用域鏈的第一項永遠是當前作用域(當前上下文的變量對象或者活動對象);
2、最后一項永遠是全局作用域(全局上下文的活動對象);
3、作用域鏈保證了變量和函數的有序訪問,查找方式是沿著作用域鏈從左至右查找變量或者函數,找到則會停止找,找不到則一直查找全局作用域,再找不到就會排除錯誤。
3.2.3閉包什么是閉包?思考一下
看一下簡單的例子
function foo() { var num = 20; function bar() { var result = num + 20; return result } bar() } foo()
因為對于閉包的有很多的不同理解,包括我看一些書籍(js高級程序設計),我這直接以瀏覽器解析,以瀏覽器的閉包為準來分析閉包,如圖
如圖所示,谷歌瀏覽器理解的閉包是foo,那么按照瀏覽器的標準是如何定義的閉包,自己總結為三點:
1、在函數內部定義新函數
2、新函數訪問外層函數的局部變量,即訪問外層函數環境的活動對象屬性
3、新函數執行,創建新函數的執行上下文,外層函數即為閉包
3.2.4確定this指向1、在全局環境下,全局執行的上下文中變量對象的this屬性指向為window;
2、在函數環境下的this指向比較靈活,需要根據執行環境和執行方法確定,列舉典型例子來分析
四、總結由于涉及到的內容過多,下一次將第三階段(執行階段)多帶帶分離出來。另開出新文章詳細分析,主要介紹js執行階段中的同步任務執行和異步任務執行機制(事件循環(Event Loop))。
五、參考書籍你不知道的javascript(上卷)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109701.html
摘要:所以覺得把這個執行的詳細過程整理一下,幫助更好的理解。類似的語法報錯的如下圖所示三預編譯階段代碼塊通過語法分析階段之后,語法都正確的下回進入預編譯階段。另開出新文章詳細分析,主要介紹執行階段中的同步任務執行和異步任務執行機制事件循環。 一、概述 js是一種非常靈活的語言,理解js引擎的執行過程對于我們學習js是非常有必要的。看了很多這方便文章,大多數是講的是事件循環(event loo...
摘要:如果對語法分析和預編譯,還有疑問引擎執行的過程的理解語法分析和預編譯階段。參與執行過程的線程分別是引擎線程也稱為內核,負責解析執行腳本程序的主線程例如引擎。以上便是引擎執行宏任務的整個過程。 一、概述 js引擎執行過程主要分為三個階段,分別是語法分析,預編譯和執行階段,上篇文章我們介紹了語法分析和預編譯階段,那么我們先做個簡單概括,如下: 1、語法分析: 分別對加載完成的代碼塊進行語法...
摘要:如果對語法分析和預編譯,還有疑問引擎執行的過程的理解語法分析和預編譯階段。參與執行過程的線程分別是引擎線程也稱為內核,負責解析執行腳本程序的主線程例如引擎。以上便是引擎執行宏任務的整個過程。一、概述 js引擎執行過程主要分為三個階段,分別是語法分析,預編譯和執行階段,上篇文章我們介紹了語法分析和預編譯階段,那么我們先做個簡單概括,如下: 1、語法分析: 分別對加載完成的代碼塊進行語法檢驗,語...
摘要:建筑的頂層代表全局作用域。實際的塊級作用域遠不止如此塊級作用域函數作用域早期盛行的立即執行函數就是為了形成塊級作用域,不污染全局。這便是閉包的特點吧經典面試題下面的代碼輸出內容答案個如何處理能夠輸出閉包方式方式下一篇你不知道的筆記 下一篇:《你不知道的javascript》筆記_this 寫在前面 這一系列的筆記是在《javascript高級程序設計》讀書筆記系列的升華版本,旨在將零碎...
摘要:的抽象語法樹中可能如下圖所示代碼生成將轉換為可執行代碼的過程被稱為代碼生成。如果是,編譯器會忽略該聲明,繼續進行編譯,否則它會要求在當前作用域的集合中聲明一個新的變量,并命名為。 在學習 javascript 的過程中,我們第一步最應該了解和掌握的就是作用域,與之相關還有程序是怎么編譯的,變量是怎么查找的,js 引擎是什么,引擎和作用域的關系又是什么,這些是 javascript 這門...
閱讀 1669·2021-10-13 09:39
閱讀 2098·2021-09-07 10:20
閱讀 2678·2019-08-30 15:56
閱讀 2944·2019-08-30 15:56
閱讀 932·2019-08-30 15:55
閱讀 624·2019-08-30 15:46
閱讀 3494·2019-08-30 15:44
閱讀 2552·2019-08-30 11:15