JavaScript中的執行上下文,既然遇見了這篇圖文並茂的文章,乾脆看完吧!(系列四)

JavaScript中的執行上下文

本文主要會講解我們經常看到的上下文知識點,旨在幫助自己和大家加深對它理解。本篇文章可以避開了變量提升相關知識,是希望篇幅可以控制在一定範圍,方便大家瀏覽,劇透一下《變量對象》會在下一篇和大家見面~
持續更新,您的三連就是我最大的動力,虛心接受大佬們的批評和指點,共勉!

目錄

前言

在這裏插入圖片描述
又到了修煉JavaScript內功的時候了,繼上一篇《從作用域到作用域鏈》之後,我們來談一談執行上下文,在寫這篇文章的時候總感覺無法完整的將知識點串聯起來,所以希望大家也能提些建議哦,讓這篇文章更值得收藏、點贊哦~

一、怎麼描述執行上下文

1.1 本節知識導圖:
在這裏插入圖片描述

1.2 如果描述執行上下文

  1. 當函數執行時,會創建一個稱爲執行上下文的內部對象。一個執行上下文定義了一個函數執行時的環境;
  2. 當一個函數被調用時,會創建一個活動記錄(有時候也稱爲執行上下文)。這個記錄會包含函數在哪裏被調用(調用棧)、函數的調用方式、傳入的參數等信息 ;
  3. 每個函數在被定義時,就會有一個[[scope]]屬性,這個屬性裏保存着作用域鏈,而執行的前一刻都會創建一個OA對象,這個對象就是執行上下文,這個OA對象會被插入[[scope]]中作用域鏈的最頂端,這個對象裏保存着函數體聲明的所有變量、參數和方法。一個OA對象的有序列表。

上述三條描述都符合執行上下文的一些特點,但側重點都不一樣。

1.3 執行上下文的類型

  1. 全局執行上下文:只有一個,瀏覽器中的全局對象就是 window 對象,this 指向這個全局對象。

  2. 函數執行上下文:存在無數個,只有在函數被調用的時候纔會被創建,每次調用函數都會創建一個新的執行上下文。

  3. Eval 函數執行上下文: 指的是運行在 eval 函數中的代碼,很少用而且不建議使用。

在這裏插入圖片描述

二、執行棧(Execution context stack)

大家都明白,函數的執行順序和它的定義順序沒關係,但如何解釋,就需要從執行棧說起了。

2.1 本節知識導圖

在這裏插入圖片描述

2.2 描述執行棧

執行棧,也叫調用棧,具有 LIFO(後進先出)結構,用於存儲在代碼執行期間創建的所有執行上下文。

首次運行JS代碼時,會創建一個全局執行上下文並Push到當前的執行上下文棧中。每當發生函數調用,引擎都會爲該函數創建一個新的函數執行上下文並push到當前執行棧的棧頂。

當棧頂函數運行完成後,其對應的函數執行上下文將會從執行棧中pop出,上下文控制權將移到當前執行棧的下一個執行上下文。

接下來問題來了,我們寫的函數多了去了,如何管理創建的那麼多執行上下文呢?

三、形象化執行棧

我們利用圖片+文字描述的方式來解釋這樣幾段代碼:

3.1 爲了模擬執行上下文棧的行爲,讓我們定義執行上下文棧爲一個數組:

var ECStack = [];

試想當 JavaScript 開始要解釋執行代碼的時候,最先遇到的就是全局代碼,所以初始化的時候首先就會向執行上下文棧壓入一個全局執行上下文,我們用 globalContext 表示它,並且只有當整個應用程序結束的時候,ECStack 纔會被清空,所以程序結束之前, ECStack 最底部永遠有個globalContext

ECStack.push('globalContext');
ECStack // ["globalContext"]

現在 JavaScript 遇到下面的這段代碼了:

function fun1() {
    fun2();
}
function fun2() {
    fun3();
}
function fun3() {
    console.log('最後打印3')
}
fun1(); // 最後打印3

當執行一個函數的時候,就會創建一個執行上下文,並且壓入(push)執行上下文棧,當函數執行完畢的時候,就會將函數的執行上下文從棧中彈出(pop)。知道了這樣的工作原理,讓我們來看看如何處理上面這段代碼:

// 僞代碼

// fun1()
ECStack.push(<fun1> functionContext);

// fun1中竟然調用了fun2,還要創建fun2的執行上下文
ECStack.push(<fun2> functionContext);

// 擦,fun2還調用了fun3!
ECStack.push(<fun3> functionContext);

// fun3執行完畢
ECStack.pop();

// fun2執行完畢
ECStack.pop();

// fun1執行完畢
ECStack.pop();

// javascript接着執行下面的代碼,但是ECStack底層永遠有個globalContext

在這裏插入圖片描述

再看如下代碼:

console.log(1);

function father() {
    console.log(2);
    (function child() {
        console.log(3);
    }());
    console.log(4);
}
father();

console.log(5);
//會依次輸出 1 2 3 4 5

分析它的執行棧經歷了什麼:

在這裏插入圖片描述
其實到這裏我們已經大致瞭解了執行棧在函數執行前->執行後的流程了,但下一篇文章我們會詳細瞭解釋一下,感興趣的小夥伴不妨點個關注,不跑丟哦~
在這裏插入圖片描述

四、思考題

現在我們已經瞭解了執行上下文棧是如何處理執行上下文的,所以讓我們看看上篇文章《從作用域到作用域鏈》最後的問題:

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()();

兩段代碼執行的結果一樣,但是兩段代碼究竟有哪些不同呢?

答案就是執行上下文棧的變化不一樣。

讓我們模擬第一段代碼:

ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();

讓我們模擬第二段代碼:

ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();

在這裏插入圖片描述
如果像上一小結一樣畫個圖的話,兩段代碼確實很不同哦~

五、寫在最後

JavaScript內功基礎部分已經總結到第四篇了,總結這個系列是受到了冴羽大大的鼓勵和啓發,本系列大約會有15篇文章,都是我們在面試最高頻的,但在工作中常常被忽略的知識點。

JavaScript內功系列:

  1. this、call、apply詳解,系列(一)
  2. 從原型到原型鏈,系列(二)
  3. 從作用域到作用域鏈,系列(三)
  4. JavaScript中的執行上下文(本文)
  5. 下一篇預告:執行上下文的“伴生姐妹”——變量對象

關於我

  • 花名:餘光
  • WX:j565017805,歡迎交流

其他沉澱

如果您看到了最後,不妨收藏、點贊、評論一下吧!!!
持續更新,您的三連就是我最大的動力,虛心接受大佬們的批評和指點,共勉!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章