目錄
什麼是執行上下文
執行上下文堆疊
執行上下文的建立過程
建立阶段以及代码执行阶段的详细分析
局部变量作用域提升的缘由
首頁 web前端 js教程 關於Javascript 執行語句context的討論

關於Javascript 執行語句context的討論

Sep 06, 2017 pm 02:51 PM
context javascript js

在這篇文章中,將比較深入闡述下執行情境 – 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引擎內部,這個上下文的創建過程具體分為兩個階段:

  1. 建立階段(發生在當調用一個函數時,但是在執行函數體內的具體程式碼以前)

    • 建立變量,函數,arguments對象,參數

    • 建立作用域鏈

    • 決定this的值

  2. 程式碼執行階段:

    • 變數賦值,函數引用,執行其它程式碼

实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:

    
          (executionContextObj = {
            variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
            scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },            
            this: {}
          }
登入後複製

建立阶段以及代码执行阶段的详细分析

确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 – 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。

上述第一个阶段的具体过程如下:

  1. 找到当前上下文中的调用函数的代码

  2. 在执行被调用的函数体中的代码以前,开始创建执行上下文

  3. 进入第一个阶段-建立阶段:

    • 建立variableObject对象:

    • 初始化作用域链

    • 确定上下文中this的指向对象

    1. 建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值

    2. 检查当前上下文中的函数声明:

      每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用

      如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。

  4. 代码执行阶段:

    执行函数体中的代码,一行一行地运行代码,给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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

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

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何使用WebSocket和JavaScript實現線上語音辨識系統 如何使用WebSocket和JavaScript實現線上語音辨識系統 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript實現線上語音辨識系統引言:隨著科技的不斷發展,語音辨識技術已成為了人工智慧領域的重要組成部分。而基於WebSocket和JavaScript實現的線上語音辨識系統,具備了低延遲、即時性和跨平台的特點,成為了廣泛應用的解決方案。本文將介紹如何使用WebSocket和JavaScript來實現線上語音辨識系

建議:優秀JS開源人臉偵測辨識項目 建議:優秀JS開源人臉偵測辨識項目 Apr 03, 2024 am 11:55 AM

人臉偵測辨識技術已經是一個比較成熟且應用廣泛的技術。而目前最廣泛的網路應用語言非JS莫屬,在Web前端實現人臉偵測辨識相比後端的人臉辨識有優勢也有弱勢。優點包括減少網路互動、即時識別,大大縮短了使用者等待時間,提高了使用者體驗;弱勢是:受到模型大小限制,其中準確率也有限。如何在web端使用js實現人臉偵測呢?為了實現Web端人臉識別,需要熟悉相關的程式語言和技術,如JavaScript、HTML、CSS、WebRTC等。同時也需要掌握相關的電腦視覺和人工智慧技術。值得注意的是,由於Web端的計

WebSocket與JavaScript:實現即時監控系統的關鍵技術 WebSocket與JavaScript:實現即時監控系統的關鍵技術 Dec 17, 2023 pm 05:30 PM

WebSocket與JavaScript:實現即時監控系統的關鍵技術引言:隨著互聯網技術的快速發展,即時監控系統在各個領域中得到了廣泛的應用。而實現即時監控的關鍵技術之一就是WebSocket與JavaScript的結合使用。本文將介紹WebSocket與JavaScript在即時監控系統中的應用,並給出程式碼範例,詳細解釋其實作原理。一、WebSocket技

股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟 股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟 Dec 17, 2023 pm 06:55 PM

股票分析必備工具:學習PHP和JS繪製蠟燭圖的步驟,需要具體程式碼範例隨著網路和科技的快速發展,股票交易已成為許多投資者的重要途徑之一。而股票分析是投資人決策的重要一環,其中蠟燭圖被廣泛應用於技術分析。學習如何使用PHP和JS繪製蠟燭圖將為投資者提供更多直觀的信息,幫助他們更好地做出決策。蠟燭圖是一種以蠟燭形狀來展示股票價格的技術圖表。它展示了股票價格的

如何利用JavaScript和WebSocket實現即時線上點餐系統 如何利用JavaScript和WebSocket實現即時線上點餐系統 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket實現即時線上點餐系統介紹:隨著網路的普及和技術的進步,越來越多的餐廳開始提供線上點餐服務。為了實現即時線上點餐系統,我們可以利用JavaScript和WebSocket技術。 WebSocket是一種基於TCP協定的全雙工通訊協議,可實現客戶端與伺服器的即時雙向通訊。在即時線上點餐系統中,當使用者選擇菜餚並下訂單

PHP與JS開發技巧:掌握繪製股票蠟燭圖的方法 PHP與JS開發技巧:掌握繪製股票蠟燭圖的方法 Dec 18, 2023 pm 03:39 PM

隨著網路金融的快速發展,股票投資已經成為了越來越多人的選擇。而在股票交易中,蠟燭圖是常用的技術分析方法,它能夠顯示股票價格的變動趨勢,幫助投資人做出更精準的決策。本文將透過介紹PHP和JS的開發技巧,帶領讀者了解如何繪製股票蠟燭圖,並提供具體的程式碼範例。一、了解股票蠟燭圖在介紹如何繪製股票蠟燭圖之前,我們首先需要先了解什麼是蠟燭圖。蠟燭圖是由日本人

JavaScript與WebSocket:打造高效率的即時天氣預報系統 JavaScript與WebSocket:打造高效率的即時天氣預報系統 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的即時天氣預報系統引言:如今,天氣預報的準確性對於日常生活以及決策制定具有重要意義。隨著技術的發展,我們可以透過即時獲取天氣數據來提供更準確可靠的天氣預報。在本文中,我們將學習如何使用JavaScript和WebSocket技術,來建立一個高效的即時天氣預報系統。本文將透過具體的程式碼範例來展示實現的過程。 We

簡易JavaScript教學:取得HTTP狀態碼的方法 簡易JavaScript教學:取得HTTP狀態碼的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教學:如何取得HTTP狀態碼,需要具體程式碼範例前言:在Web開發中,經常會涉及到與伺服器進行資料互動的場景。在與伺服器進行通訊時,我們經常需要取得傳回的HTTP狀態碼來判斷操作是否成功,並根據不同的狀態碼來進行對應的處理。本篇文章將教你如何使用JavaScript來取得HTTP狀態碼,並提供一些實用的程式碼範例。使用XMLHttpRequest

See all articles