溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript引擎怎么執(zhí)行JS代碼

發(fā)布時(shí)間:2022-03-29 12:14:17 來源:億速云 閱讀:226 作者:小新 欄目:web開發(fā)

這篇文章主要為大家展示了“JavaScript引擎怎么執(zhí)行JS代碼”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“JavaScript引擎怎么執(zhí)行JS代碼”這篇文章吧。

JS代碼的運(yùn)行

我們知道了js是弱類型語言,在運(yùn)行時(shí)才確定變量類型。js引擎在執(zhí)行js代碼時(shí),也會(huì)從上到下進(jìn)行 詞法分析、語法分析語義分析 等處理,并在代碼解析完成后生成AST(抽象語法樹),最終根據(jù)AST生成CPU可以執(zhí)行的機(jī)器碼并執(zhí)行。

除此之外,JS引擎在執(zhí)行代碼時(shí)還會(huì)進(jìn)行其它處理,如 V8 中還有兩個(gè)階段:

  • 編譯階段:該階段會(huì)進(jìn)行執(zhí)行上下文的創(chuàng)建,包括創(chuàng)建變量對象(VO)(此時(shí)會(huì)被初始化為undefined)、建立作用域鏈、確定 this 指向等。每進(jìn)入一個(gè)不同的運(yùn)行環(huán)境。V8 都會(huì)創(chuàng)建一個(gè)新的執(zhí)行上下文。

  • 執(zhí)行階段:將編譯階段中創(chuàng)建的執(zhí)行上下文壓入調(diào)用棧,并成為正在運(yùn)行的執(zhí)行上下文。代碼執(zhí)行結(jié)束后,將其彈出調(diào)用棧。(這里有一個(gè)VO - AO的過程:JavaScript對變量賦值時(shí)變量被用到,此時(shí)變量對象會(huì)轉(zhuǎn)為活動(dòng)對象,轉(zhuǎn)換后的活動(dòng)對象才可被訪問)

這就引出了兩個(gè)概念:“執(zhí)行上下文” 和 “作用域鏈”。


JavaScript執(zhí)行上下文

由上面我們可以知道:當(dāng)js代碼執(zhí)行一段可執(zhí)行代碼時(shí),會(huì)創(chuàng)建對應(yīng)的執(zhí)行上下文。
首先,js中可執(zhí)行代碼對應(yīng)著有一個(gè)概念:“執(zhí)行環(huán)境” —— 全局環(huán)境、函數(shù)環(huán)境 和 eval。
其次,對于每個(gè)執(zhí)行上下文,都有三個(gè)重要屬性:

  • 變量對象(即“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()();

它們會(huì)打印什么?
JavaScript引擎怎么執(zhí)行JS代碼

為什么?答案是它們的執(zhí)行上下文棧不一樣!

什么是“執(zhí)行上下文?!??
當(dāng)執(zhí)行一個(gè)可執(zhí)行代碼時(shí),就會(huì)提前做準(zhǔn)備工作,這里的“準(zhǔn)備工作”,專業(yè)的說法就是“執(zhí)行上下文”。但隨著可執(zhí)行代碼如函數(shù)的增多,如何管理那么多的執(zhí)行上下文呢?所以JS引擎創(chuàng)建了執(zhí)行上下文棧的概念。
我們完全可以用數(shù)組去模擬其行為(棧底永遠(yuǎn)有一個(gè)全局執(zhí)行上下文globalContext)

我們定義一個(gè)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();

究其原因,你可能需要先研究一下“閉包”的概念了!

這里順便說下“在前端模塊化”中怎么實(shí)現(xiàn)“長時(shí)間保存數(shù)據(jù)”?
緩存?不。閉包!


JavaScript作用域和作用域鏈

首先,作用域是指程序中定義變量的區(qū)域。作用域規(guī)定了如何查找變量,也就是確定了當(dāng)前執(zhí)行代碼對變量的訪問權(quán)限。
作用域有兩種:靜態(tài)作用域動(dòng)態(tài)作用域。
JS采用的靜態(tài)作用域,也叫“詞法作用域”。函數(shù)的作用域在函數(shù)定義的時(shí)候就確定了。

由上,詞法作用域中的變量,在編譯過程中會(huì)產(chǎn)生一個(gè)確定的作用范圍。這個(gè)作用范圍即“當(dāng)前的執(zhí)行上下文”。在ES5后我們用“詞法環(huán)境”替代作用域來描述該執(zhí)行上下文。詞法環(huán)境由兩個(gè)成員組成:

  • 自身詞法環(huán)境記錄:用于記錄自身詞法環(huán)境中的變量對象

  • 外部詞法環(huán)境引用:用于記錄外層詞法環(huán)境中存在的引用

我們依然來看一個(gè)例子:

var value=1;function foo(){
	console.log(value);}function bar(){
	var value=2;
	foo();}bar();

回看上面的定義,該打印什么?

JavaScript引擎怎么執(zhí)行JS代碼

讓我們分析下執(zhí)行過程:
執(zhí)行foo()函數(shù),先從foo函數(shù)內(nèi)部查找是否有局部變量value。如果沒有,就根據(jù)定義時(shí)的位置,查找上面一層的代碼,也就是value=1.所以結(jié)果會(huì)打印1。

這里面當(dāng)然不是如此簡單能概括的,你可以從執(zhí)行上下文的角度分析一下。

建立作用域鏈

上面我們說了詞法環(huán)境(作用域)的兩個(gè)組成。再結(jié)合執(zhí)行上下文,我們不難發(fā)現(xiàn):通過外部詞法環(huán)境的引用,作用域可以順著棧層層拓展,建立起從當(dāng)前環(huán)境向外延伸的一條鏈?zhǔn)浇Y(jié)構(gòu)。

再來看一個(gè)例子:

function foo(){
	console.dir(bar);
	var a=1;
	function bar(){
		a=2;
	}}console.dir(foo);foo();

由靜態(tài)作用域,全局函數(shù)foo創(chuàng)建了一個(gè)自身對象的 [[scope]] 屬性

foo[[scope]]=[globalContext];

而當(dāng)我們執(zhí)行foo()時(shí),也會(huì)先后進(jìn)入foo函數(shù)的定義期和執(zhí)行期。在foo函數(shù)的定義期時(shí),函數(shù)bar的 [[scope]] 將會(huì)包含全局內(nèi)置scope和foo的內(nèi)置scope

bar[[scope]]=[fooContext,globalContext];

這證明了這一點(diǎn):“JS會(huì)通過外部詞法環(huán)境引用來創(chuàng)建變量對象的一個(gè)作用域鏈,從而保證對執(zhí)行環(huán)境有權(quán)訪問的變量和函數(shù)的有序訪問?!?/p>

讓我們再回頭看看執(zhí)行上下文中的那道題,在前面我們說了它們有什么不同,這里說下為什么它們相同地打印了“l(fā)ocal scope”:還是那句話“JS采用的是詞法作用域,函數(shù)的作用域取決于函數(shù)創(chuàng)建的位置” —— JS函數(shù)的執(zhí)行用到了作用域鏈,這個(gè)作用域鏈?zhǔn)窃诤瘮?shù)定義的時(shí)候創(chuàng)建的。嵌套的函數(shù) f() 定義在這個(gè)作用域鏈里,其中的變量scope一定是指局部變量,不管何時(shí)何地執(zhí)行 f() ,這種綁定在執(zhí)行 f() 時(shí)依然有效。

基于作用域鏈的變量查詢

當(dāng)某個(gè)變量無法在自身詞法環(huán)境記錄中找到時(shí),可以根據(jù)外部詞法環(huán)境引用向外層進(jìn)行尋找,直到最外層的詞法環(huán)境中外部詞法環(huán)境引用為null。
與此相似的是“對象中基于原型鏈的查找”:

  • 原型:每一個(gè)JS對象(null 除外)在創(chuàng)建時(shí)就會(huì)與另一個(gè)對象關(guān)聯(lián),這個(gè)對象就是我們說的原型。每一個(gè)對象都會(huì)從原型中“繼承”屬性。

  • 當(dāng)讀取實(shí)例的屬性時(shí),如果找不到,就會(huì)查找與對象關(guān)聯(lián)的原型中的屬性,如果還找不到,就去找原型的原型,一直到最頂層(__proto__為null)為止

它們的區(qū)別也顯而易見:原型鏈?zhǔn)峭ㄟ^ prototype 屬性建立對象繼承的鏈接;而作用域鏈?zhǔn)侵竷?nèi)部函數(shù)能訪問到外部函數(shù)的閉包。不管直接還是間接,所有函數(shù)的作用域鏈最終都鏈接到全局上下文。

以上是“JavaScript引擎怎么執(zhí)行JS代碼”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI