在這篇文章中,將比較深入闡述下執行情境 – JavaScript中最基礎也是最重要的一個概念。相信讀完這篇文章後,你就會明白javascript引擎內部在執行程式碼以前到底做了些什麼,為什麼某些函數以及變數在沒有被宣告以前就可以被使用,以及它們的最終的值是怎樣被定義的。
什麼是執行上下文
Javascript中程式碼的運行環境分為以下三種:
全域層級的程式碼– 這個是預設的程式碼運作環境,一旦程式碼被載入,引擎最先進入的就是這個環境。
函數層級的程式碼 – 當執行一個函數時,執行函數體中的程式碼。
Eval的程式碼 – 在Eval函數內執行的程式碼。
在網路上可以找到許多闡述作用域的資源,為了讓該文便於大家理解,我們可以將「執行上下文」看做目前程式碼的運作環境或作用域。下面我們來看一個範例,其中包含了全域以及函數層級的執行上下文:
上圖中,一共用4個執行上下文。紫色的代表全局的上下文;綠色代表person函數內的上下文;藍色以及橙色代表person函數內的另外兩個函數的上下文。請注意,不管在什麼情況下,只存在一個全域的上下文,該上下文能被任何其它的上下文所存取。也就是說,我們可以在person的上下文中存取到全域上下文中的sayHello變量,當然在函數firstName或lastName中同樣可以存取到該變數。
至於函數上下文的個數是沒有任何限制的,每到呼叫執行一個函數時,引擎就會自動新建出一個函數上下文,換句話說,就是新建一個局部作用域,可以在該局部作用域中宣告私有變數等,在外部的上下文中是無法直接存取到該局部作用域內的元素的。在上述例子的,內部的函數可以存取到外部上下文中的聲明的變量,反之則行不通。那麼,這到底是什麼原因呢?引擎內部是如何處理的呢?
執行上下文堆疊
在瀏覽器中,javascript引擎的工作方式是單執行緒的。也就是說,某一時刻只有唯一的事件是被啟動處理的,其它的事件被放入佇列中,等待被處理。下面的範例圖描述了這樣的一個堆疊:
我們已經知道,當javascript程式碼檔案被瀏覽器載入後,預設最先進入的是一個全域的執行上下文。當在全域上下文中呼叫執行一個函數時,程式流程就進入該被呼叫函數內,此時引擎就會為該函數建立一個新的執行上下文,並且將其壓入到執行上下文堆疊的頂部。瀏覽器總是執行當前在堆疊頂部的上下文,一旦執行完畢,該上下文就會從堆疊頂部被彈出,然後,進入其下的上下文執行程式碼。這樣,堆疊中的上下文就會被依序執行並且彈出堆疊,直到回到全域的上下文。請看下面一個例子:
(function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0));
上述foo被宣告後,透過()運算子強制直接運作了。函數程式碼就是呼叫了其自身3次,每次是局部變數i增加1。每次foo函數被自身呼叫時,就會有一個新的執行上下文被建立。每當一個上下文執行完畢,該上上下文就會被彈出堆疊,回到上一個上下文,直到再次回到全域上下文。真個過程抽像如下圖:
由此可見,對於執行上下文這個抽象的概念,可以歸納為以下幾點:
#單一執行緒
同步執行
#唯一的一個全域上下文
函數的執行上下文的個數沒有限制
每次某個函數被調用,就會有個新的執行上下文為其創建,即使是調用的自身函數,也是如此。
執行上下文的建立過程
我們現在已經知道,每當呼叫函數時,一個新的執行上下文就會被建立出來。然而,在javascript引擎內部,這個上下文的創建過程具體分為兩個階段:
建立階段(發生在當調用一個函數時,但是在執行函數體內的具體程式碼以前)
建立變量,函數,arguments對象,參數
建立作用域鏈
決定this的值
程式碼執行階段:
變數賦值,函數引用,執行其它程式碼
实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:
(executionContextObj = { variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ }, scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ }, this: {} }
建立阶段以及代码执行阶段的详细分析
确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 – 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。
上述第一个阶段的具体过程如下:
找到当前上下文中的调用函数的代码
在执行被调用的函数体中的代码以前,开始创建执行上下文
进入第一个阶段-建立阶段:
建立variableObject对象:
初始化作用域链
确定上下文中this的指向对象
建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值
-
检查当前上下文中的函数声明:
每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用
如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
代码执行阶段:
执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。
下面来看个具体的代码示例:
function foo(i) { var a = 'hello'; var b = function privateB() { }; function c() { } } foo(22);
在调用foo(22)的时候,建立阶段如下:
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, scopeChain: { ... }, this: { ... } }
由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下:
fooExecutionContext = { variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: 'hello', b: pointer to function privateB() }, scopeChain: { ... }, this: { ... } }
我们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。
局部变量作用域提升的缘由
在网上一直看到这样的总结: 在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说,就是一进入函数体,就可以访问到其中声明的变量以及函数。这是对的,但是知道其中的缘由吗?相信你通过上述的解释应该也有所明白了。不过在这边再分析一下。看下面一段代码:
(function() { console.log(typeof foo); // function pointer console.log(typeof bar); // undefined var foo = 'hello', bar = function() { return 'world'; }; function foo() { return 'hello'; } }());
上述代码定义了一个匿名函数,并且通过()运算符强制理解执行。那么我们知道这个时候就会有个执行上下文被创建,我们看到例子中马上可以访问foo以及bar变量,并且通过typeof输出foo为一个函数引用,bar为undefined。
为什么我们可以在声明foo变量以前就可以访问到foo呢?
因为在上下文的建立阶段,先是处理arguments, 参数,接着是函数的声明,最后是变量的声明。那么,发现foo函数的声明后,就会在variableObject下面建立一个foo属性,其值是一个指向函数的引用。当处理变量声明的时候,发现有var foo的声明,但是variableObject已经具有了foo属性,所以直接跳过。当进入代码执行阶段的时候,就可以通过访问到foo属性了,因为它已经就存在,并且是一个函数引用。
为什么bar是undefined呢?
因为bar是变量的声明,在建立阶段的时候,被赋予的默认的值为undefined。由于它只要在代码执行阶段才会被赋予具体的值,所以,当调用typeof(bar)的时候输出的值为undefined。
好了,到此为止,相信你应该对执行上下文有所理解了,这个执行上下文的概念非常重要,务必好好搞懂之!
以上是關於Javascript 執行語句context的討論的詳細內容。更多資訊請關注PHP中文網其他相關文章!

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

WebStorm Mac版
好用的JavaScript開發工具

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能