首頁  >  文章  >  web前端  >  深入了解JavaScript引擎如何執行JS程式碼

深入了解JavaScript引擎如何執行JS程式碼

WBOY
WBOY轉載
2022-03-29 11:49:391796瀏覽

本篇文章為大家帶來了關於javascript的相關知識,其中主要介紹了js引擎如何執行js程式碼的相關問題,js引擎在執行js程式碼時,也會從上到下進行詞法分析、語法分析、語意分析等處理,並在程式碼解析完成後產生AST,希望對大家有幫助。

深入了解JavaScript引擎如何執行JS程式碼

相關推薦:javascript教學

#我們大概常常能聽到「執行環境」、「作用域」、「原型(鏈)」、「執行上下文」等內容,它們都在描述什麼?

JS程式碼的運行

我們知道了js是弱型別語言,在執行時才會確定變數類型。 js引擎在執行js程式碼時,也會從上到下進行詞法分析語法分析語意分析 等處理,並在程式碼解析完成後生成AST(抽象語法樹),最終根據AST產生CPU可以執行的機器碼並執行。

除此之外,JS引擎在執行程式碼時還會進行其它處理,如V8 中還有兩個階段:

  • 編譯階段:該階段會進行執行上下文的創建,包括創建變數物件(VO)(此時會被初始化為undefined)、建立作用域鏈、確定this 指向等。每進入一個不同的運作環境。 V8 都會建立一個新的執行上下文。
  • 執行階段:將編譯階段中建立的執行上下文壓入呼叫堆疊,並成為正在執行的執行上下文。程式碼執行結束後,將其彈出呼叫堆疊。 (這裡有一個VO - AO的過程:JavaScript對變數賦值時變數被用到,此時變數物件會轉為活動對象,轉換後的活動對象才可被存取)

這就引出了兩個概念:「執行上下文」 和「作用域鏈」。


JavaScript執行上下文

由上面我們可以知道:當js程式碼執行一段可執行程式碼時,會建立對應的執行上下文。
首先,js中可執行程式碼對應著有一個概念:「執行環境」 —— 全域環境、函數環境 和 eval
其次,對於每個執行上下文,都有三個重要屬性:

  • 變數物件(即「VO」)
  • 作用域鏈
  • this

我們來看兩段程式碼:

var 深入了解JavaScript引擎如何執行JS程式碼="global 深入了解JavaScript引擎如何執行JS程式碼";function check深入了解JavaScript引擎如何執行JS程式碼(){
	var 深入了解JavaScript引擎如何執行JS程式碼="local 深入了解JavaScript引擎如何執行JS程式碼";
	function f(){
		return 深入了解JavaScript引擎如何執行JS程式碼;
	}
	return f();}check深入了解JavaScript引擎如何執行JS程式碼();
var 深入了解JavaScript引擎如何執行JS程式碼="global 深入了解JavaScript引擎如何執行JS程式碼";function check深入了解JavaScript引擎如何執行JS程式碼(){
	var 深入了解JavaScript引擎如何執行JS程式碼="local 深入了解JavaScript引擎如何執行JS程式碼";
	function f(){
		return 深入了解JavaScript引擎如何執行JS程式碼;
	}
	return f;}check深入了解JavaScript引擎如何執行JS程式碼()();

它們會印什麼?
深入了解JavaScript引擎如何執行JS程式碼

為什麼?答案是它們的執行上下文堆疊不一樣!

什麼是「執行上下文堆疊」?
當執行一個可執行程式碼時,就會提前做準備工作,這裡的“準備工作”,專業的說法就是“執行上下文”。但隨著可執行程式碼如函數的增多,如何管理那麼多的執行上下文?所以JS引擎創建了執行上下文堆疊的概念。
我們完全可以用陣列去模擬其行為(棧底永遠有一個全域執行上下文globalContext)

我們定義一個EStack,首先

EStack=[globalContext];

然後來模擬第一段落程式碼:

EStack.push(<check> functionContext);EStack.push(<f> functionContext);EStack.pop();EStack.pop();</f></check>

而第二段程式碼是這樣的:

EStack.push(<check> functionContext);EStack.pop();EStack.push(<f> functionContext);EStack.pop();</f></check>

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

這裡順便說下「在前端模組化」中怎麼實現「長時間保存資料」?
快取?不。閉包!


JavaScript作用域與作用域鏈

首先,作用域是指程式中定義變數的區域。作用域規定如何找出變量,也就是確定了目前執行程式碼對變數的存取權限。
作用域有兩種:靜態作用域動態作用域
JS採用的靜態作用域,也叫「詞法作用域」。函數的作用域在函數定義的時候就確定了。

由上,詞法作用域中的變量,在編譯過程中會產生一個確定的作用範圍。這個作用範圍即「目前的執行上下文」。在ES5後我們用「詞法環境」取代作用域來描述該執行上下文。詞法環境由兩個成員組成:

  • 自身詞法環境記錄:用於記錄自身詞法環境中的變數物件
  • 外部詞法環境引用:用於記錄外層詞法環境中存在的引用

我們依然來看一個例子:

var 深入了解JavaScript引擎如何執行JS程式碼=1;function foo(){
	console.log(深入了解JavaScript引擎如何執行JS程式碼);}function bar(){
	var 深入了解JavaScript引擎如何執行JS程式碼=2;
	foo();}bar();

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

深入了解JavaScript引擎如何執行JS程式碼

讓我們分析下執行過程:
執行foo()函數,先從foo函數內部找出是否有局部變數深入了解JavaScript引擎如何執行JS程式碼。如果沒有,就依照定義時的位置,找出上面一層的程式碼,也就是深入了解JavaScript引擎如何執行JS程式碼=1.所以結果會列印1。

這裡面當然不是如此簡單能概括的,你可以從執行上下文的角度來分析一下。

建立作用域链

上面我们说了词法环境(作用域)的两个组成。再结合执行上下文,我们不难发现:通过外部词法环境的引用,作用域可以顺着栈层层拓展,建立起从当前环境向外延伸的一条链式结构。

再来看一个例子:

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

由静态作用域,全局函数foo创建了一个自身对象的 [[深入了解JavaScript引擎如何執行JS程式碼]] 属性

foo[[深入了解JavaScript引擎如何執行JS程式碼]]=[globalContext];

而当我们执行foo()时,也会先后进入foo函数的定义期和执行期。在foo函数的定义期时,函数bar的 [[深入了解JavaScript引擎如何執行JS程式碼]] 将会包含全局内置深入了解JavaScript引擎如何執行JS程式碼和foo的内置深入了解JavaScript引擎如何執行JS程式碼

bar[[深入了解JavaScript引擎如何執行JS程式碼]]=[fooContext,globalContext];

这证明了这一点:“JS会通过外部词法环境引用来创建变量对象的一个作用域链,从而保证对执行环境有权访问的变量和函数的有序访问。”

让我们再回头看看执行上下文中的那道题,在前面我们说了它们有什么不同,这里说下为什么它们相同地打印了“local 深入了解JavaScript引擎如何執行JS程式碼”:还是那句话“JS采用的是词法作用域,函数的作用域取决于函数创建的位置” —— JS函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量深入了解JavaScript引擎如何執行JS程式碼一定是指局部变量,不管何时何地执行 f() ,这种绑定在执行 f() 时依然有效。

基于作用域链的变量查询

当某个变量无法在自身词法环境记录中找到时,可以根据外部词法环境引用向外层进行寻找,直到最外层的词法环境中外部词法环境引用为null
与此相似的是“对象中基于原型链的查找”:

  • 原型:每一个JS对象(null 除外)在创建时就会与另一个对象关联,这个对象就是我们说的原型。每一个对象都会从原型中“继承”属性。
  • 当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还找不到,就去找原型的原型,一直到最顶层(__proto__为null)为止

它们的区别也显而易见:原型链是通过 prototype 属性建立对象继承的链接;而作用域链是指内部函数能访问到外部函数的闭包。不管直接还是间接,所有函数的作用域链最终都链接到全局上下文。

相关推荐:javascript学习教程

以上是深入了解JavaScript引擎如何執行JS程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除