您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“JavaScript引擎怎么執行JS代碼”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“JavaScript引擎怎么執行JS代碼”這篇文章吧。
我們知道了js是弱類型語言,在運行時才確定變量類型。js引擎在執行js代碼時,也會從上到下進行 詞法分析、語法分析、語義分析 等處理,并在代碼解析完成后生成AST(抽象語法樹),最終根據AST生成CPU可以執行的機器碼并執行。
除此之外,JS引擎在執行代碼時還會進行其它處理,如 V8 中還有兩個階段:
編譯階段:該階段會進行執行上下文的創建,包括創建變量對象(VO)(此時會被初始化為undefined)、建立作用域鏈、確定 this 指向等。每進入一個不同的運行環境。V8 都會創建一個新的執行上下文。
執行階段:將編譯階段中創建的執行上下文壓入調用棧,并成為正在運行的執行上下文。代碼執行結束后,將其彈出調用棧。(這里有一個VO - AO的過程:JavaScript對變量賦值時變量被用到,此時變量對象會轉為活動對象,轉換后的活動對象才可被訪問)
這就引出了兩個概念:“執行上下文” 和 “作用域鏈”。
由上面我們可以知道:當js代碼執行一段可執行代碼時,會創建對應的執行上下文。
首先,js中可執行代碼對應著有一個概念:“執行環境” —— 全局環境、函數環境 和 eval
。
其次,對于每個執行上下文,都有三個重要屬性:
變量對象(即“VO”)
作用域鏈
this
我們來看兩段代碼:
var scope="global scope";function checkscope(){ var scope="local scope"; function f(){ return scope; } return f();}checkscope();
var scope="global scope";function checkscope(){ var scope="local scope"; function f(){ return scope; } return f;}checkscope()();
它們會打印什么?
為什么?答案是它們的執行上下文棧不一樣!
什么是“執行上下文棧”?
當執行一個可執行代碼時,就會提前做準備工作,這里的“準備工作”,專業的說法就是“執行上下文”。但隨著可執行代碼如函數的增多,如何管理那么多的執行上下文呢?所以JS引擎創建了執行上下文棧的概念。
我們完全可以用數組去模擬其行為(棧底永遠有一個全局執行上下文globalContext)
我們定義一個EStack,首先
EStack=[globalContext];
然后來模擬第一段代碼:
EStack.push(<checkscope> functionContext);EStack.push(<f> functionContext);EStack.pop();EStack.pop();
而第二段代碼是這樣的:
EStack.push(<checkscope> functionContext);EStack.pop();EStack.push(<f> functionContext);EStack.pop();
究其原因,你可能需要先研究一下“閉包”的概念了!
這里順便說下“在前端模塊化”中怎么實現“長時間保存數據”?
緩存?不。閉包!
首先,作用域是指程序中定義變量的區域。作用域規定了如何查找變量,也就是確定了當前執行代碼對變量的訪問權限。
作用域有兩種:靜態作用域 和 動態作用域。
JS采用的靜態作用域,也叫“詞法作用域”。函數的作用域在函數定義的時候就確定了。
由上,詞法作用域中的變量,在編譯過程中會產生一個確定的作用范圍。這個作用范圍即“當前的執行上下文”。在ES5后我們用“詞法環境”替代作用域來描述該執行上下文。詞法環境由兩個成員組成:
自身詞法環境記錄:用于記錄自身詞法環境中的變量對象
外部詞法環境引用:用于記錄外層詞法環境中存在的引用
我們依然來看一個例子:
var value=1;function foo(){ console.log(value);}function bar(){ var value=2; foo();}bar();
回看上面的定義,該打印什么?
讓我們分析下執行過程:
執行foo()函數,先從foo函數內部查找是否有局部變量value。如果沒有,就根據定義時的位置,查找上面一層的代碼,也就是value=1.所以結果會打印1。
這里面當然不是如此簡單能概括的,你可以從執行上下文的角度分析一下。
上面我們說了詞法環境(作用域)的兩個組成。再結合執行上下文,我們不難發現:通過外部詞法環境的引用,作用域可以順著棧層層拓展,建立起從當前環境向外延伸的一條鏈式結構。
再來看一個例子:
function foo(){ console.dir(bar); var a=1; function bar(){ a=2; }}console.dir(foo);foo();
由靜態作用域,全局函數foo創建了一個自身對象的 [[scope]]
屬性
foo[[scope]]=[globalContext];
而當我們執行foo()時,也會先后進入foo函數的定義期和執行期。在foo函數的定義期時,函數bar的 [[scope]]
將會包含全局內置scope和foo的內置scope
bar[[scope]]=[fooContext,globalContext];
這證明了這一點:“JS會通過外部詞法環境引用來創建變量對象的一個作用域鏈,從而保證對執行環境有權訪問的變量和函數的有序訪問。”
讓我們再回頭看看執行上下文中的那道題,在前面我們說了它們有什么不同,這里說下為什么它們相同地打印了“local scope”:還是那句話“JS采用的是詞法作用域,函數的作用域取決于函數創建的位置” —— JS函數的執行用到了作用域鏈,這個作用域鏈是在函數定義的時候創建的。嵌套的函數 f() 定義在這個作用域鏈里,其中的變量scope一定是指局部變量,不管何時何地執行 f() ,這種綁定在執行 f() 時依然有效。
當某個變量無法在自身詞法環境記錄中找到時,可以根據外部詞法環境引用向外層進行尋找,直到最外層的詞法環境中外部詞法環境引用為null
。
與此相似的是“對象中基于原型鏈的查找”:
原型:每一個JS對象(null 除外)在創建時就會與另一個對象關聯,這個對象就是我們說的原型。每一個對象都會從原型中“繼承”屬性。
當讀取實例的屬性時,如果找不到,就會查找與對象關聯的原型中的屬性,如果還找不到,就去找原型的原型,一直到最頂層(__proto__
為null)為止
它們的區別也顯而易見:原型鏈是通過 prototype 屬性建立對象繼承的鏈接;而作用域鏈是指內部函數能訪問到外部函數的閉包。不管直接還是間接,所有函數的作用域鏈最終都鏈接到全局上下文。
以上是“JavaScript引擎怎么執行JS代碼”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。