javascript 作用域與作用域鏈

javascript 作用域與作用域鏈

一、作用域
1.1、作用域的定義

官方解釋:作用域(scope),程序設計概念,通常來說,一段程序代碼中所用到的名字並不總是有效/可用的,而限定這個名字的可用性的代碼範圍就是這個名字的作用域。
通俗解釋:作用域指你當前代碼的上下文環境,也就是說,當前變量,對象、函數等能夠被應用的範圍。

1.2、作用域分類

我們通常說的函數作用域有以下幾種,前三種爲常用說法,但後兩種也滲透在前三種中:

  • 全局作用域

在代碼的任何地方都可以訪問到的變量對象,就位於全局作用域中,window下面的變量都在全局作用域範圍裏,全局作用域常見的範例如下。

    // 例1 聲明爲全局變量
    var a = 66;
    function bb(){
        console.log(a) //66
    }
    console.log(a) //66
    bb();
    // js引擎在解析此段代碼時,會創建全局作用域global scope 和函數作用域bb scope
    // 此處的函數bb、變量a就在全局作用域中,因此函數bb,以及變量a可在任何地方被訪問到,包括函數作用域bb scope內部

    // 例2 未聲明直接賦值
    function cc(){
        mm = 10;
        var n = 8;
        console.log(mm+n);//18
    }
    cc();
    console.log(mm);//10
    console.log(n);//Uncaught ReferenceError: n is not defined
    //未被聲明直接賦值的變量,js引擎在解析的時候自動提升爲全局變量,
    //未被聲明直接賦值的變量,限定在全局作用域範圍內,因此雖然是在函數cc內部被賦值,但在函數外部同樣可以訪問到
  • 函數作用域

每定義一個函數,就會創建一個函數作用域,函數作用域限定了定義在函數內部的變量等標識符只能在此函數內部使用。

    // 例1 函數內部定義的變量
    function dd(){
        var pp = 1212;
        console.log(pp);//1212
    }
    console.log(pp); // Uncaught ReferenceError: pp is not defined
    // 函數內部聲明的變量,其作用使用範圍,被限定只能在函數內部使用,只就是函數作用域的作用

  • 塊級作用域(es6)

es6之前的javascript是沒有塊級作用域的說法的,塊級作用域可通過新增命令 let 和 const 聲明,塊級作用域可以在一個函數內部、在一個由一對大括號包裹的代碼塊中被創建。

    // 例1 塊級作用域
    function blockscope(){
        const len = 10;

        // 用var聲明的變量j,此變量被提升,在函數作用域內都可訪問到
        for(var j = 0;j<len;j++){
            console.log(j);//
        }
        console.log(j)//10

        // 用let聲明的變量i,此變量屬於塊級作用域,只能在for循環內部使用,
        for(let i =0;i<len;i++){
            console.log(i); //0 1 2 3 4 5 6 7 8 9
        }
        console.log(i)//scope.html:54 Uncaught ReferenceError: i is not defined

        console.log(len);//10
    }
    blockscope();


    // 例2 塊級作用域的應用
    //如下爲html中的四個div元素
    // <div></div>
    // <div></div>
    // <div></div>
    // <div></div>

    var divs = document.getElementsByTagName("div");
    for(var i=0;i<divs.length;i++){
        divs[i].onmouseover = function() {
            console.log(`這是第${i}個div元素`);//這是第4個div元素
        }
    }
    //此段代碼被js引擎解析的時候,先創建一個全局作用域,全局作用域裏有變量divs、i、和四個函數divs[0],divs[1],divs[2],divs[3],
    //然後創建每個函數的作用域,函數作用域裏面無任務變量等表示符
    //for循環綁定事件後,i已經變爲4,
    //無論你懸浮到那一個div,當觸發對應事件函數的時候,由於函數最用於中找不到i,依次到全局作用域中找,找到i=4;因此每次都會輸出4

    for(let i=0;i<divs.length;i++){
            divs[i].onmouseover = function() {
            console.log(`這是第${i}個div元素`);
             // 這是第0個div元素
             // 這是第1個div元素
             // 這是第2個div元素
             // 這是第3個div元素
        }
    }
    // js引擎解析,先創建一個全局作用域,由於for循環中的變量i是用let聲明的,因此全局作用域中無標識符,
    // 由for循環構建四個塊級作用域,記作for-0、for-1、for-2、for-3、每個塊級作用域中都包含一個變量i,其值依次爲0,1,2,3,塊級作用域中還包含四個函數表示符divs[0],divs[1],divs[2],divs[3],
    // 最後創建每個函數的函數作用域,函數作用域中無任何變量等表示符
    // 因此,當觸發每個div對應的函數的時候,先從自己的函數作用域裏面去找,無變量i,再到對應的塊級作用域裏面找,能夠找到對應的變量i,因此會依次打印出上述結果

  • 詞法作用域(靜態作用域)

簡單地說,詞法作用域就是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫代碼時將變量和塊作用域寫在哪裏來決定的,因此當詞法分析器處理代碼時會保持作用域不變(大部分情況下是這樣的)。

    // 例1 靜態作用域
    function ff(){
        console.log(pp)
    }
    var pp = 100;
    function mm(func){
        var pp = 66;
        return func();
    }
    mm(ff); //100
    // 分析,先創建全局作用域global-scope,包含變量pp=100,函數ff和函數,mm,
    // 然後創建兩個函數作用域ff-scope和mm-scope,ff-scope中無變量標識符,mm-scope中包含變量pp=66,參數func,指向函數ff
    // 函數執行階段,當函數ff被調用時,先在ff-scope中查找pp,沒有找到,然後沿着作用域鏈到全局作用域中,找到pp=100,因此輸出值爲100
    // 此處打印出的pp的值,不是函數被調用的位置所在的作用域mm-scope中的pp=60,而是函數被定義的位置所在的作用域global-scope中的變量pp=100

    //例2 靜態作用域
    function aa(){
        var _b = 100;
        function bb(){
            alert(_a+_b)
        }
        return bb;
    }
    var _a = 50;

    var x = aa();
    var _b = 60;
    x(); //150
    // js解釋階段,執行階段的原理同上述例1
  • 動態作用域

在JS裏,動態作用域和this機制息息相關。它的作用域詩是在運行的過程中確定的
詞法作用域是在寫代碼或者說定義時確定的,而動態作用域是在運行時確定的。(this 也是!)
詞法作用域關注函數在何處聲明,而動態作用域關注函數從何處調用。

    // 例1 動態作用域
    var aaa = 1;
    function f() {
        var aaa = 666;
        console.log( this.a );
    }
    f(); // 1
    // 從結果看,f中打印出aaa的值不是由寫代碼的位置確定的,而是取決於f執行的位置。也就是說,
    // 打印出的a的值不是console.log代碼輸出所在的函數作用域中aaa的值,而是函數f被調用時所處的全局作用域中的aaa的值

1.3、作用域與執行上下文

作用域是在變量、函數被定義時就創建的,變量、函數被創建時js引擎就會通過詞法分析、語法分析等手段創建標識符指向這些變量或者函數;
執行上下文是在代碼加載,函數被調用的時候創建的,全局執行上下文只有一個,函數每次調用,都會創建新的函數執行上下文;
二、作用域鏈
2.1、作用域鏈的理解
作用域鏈是由當前作用域與上層一系列父級作用域組成,作用域的頭部永遠是當前作用域,尾部永遠是全局作用域。作用域鏈保證了當前上下文對其有權訪問的變量的有序訪問。
作用域鏈是一個數組,控制變量作用域的有序訪問;作用域鏈存儲在函數的執行上下文中,作用域鏈中存放的是執行環境中的變量對象(varible objec-----簡稱vo)或者激活對象(active object-----簡稱ao);
當前函數的作用域對像位於作用域鏈的最前端,全局作用域對象在作用域鏈的最末端。js引擎是通過變量或者函數標識符從作用域鏈的最前端開始查找,依次到作用域鏈的最後段,直到找到爲止,若沒有找到,則拋出異常
2.2、作用域鏈與執行上下文
執行上下文被創建的時,會同時在此上下文中創建[[scope]],指向此上下文的父級作用域,這樣一系列的嵌套使用,就構成了作用域鏈

圖解作用域鏈的執行過程

   // 代碼示例
    var v_1 = 20;
    function fun (pra){
        var b = 66;
        function f(){
            var c = 20
            console.log(pra+b+c)
        }
        console.log(f)
        return f;

    }
    console.log(fun)

    var res = fun(v_1)
    res();
    // js引擎解析時,創建全局執行上下文global ec以及全局作用域global scope;全局上下文中定義的函數fun被聲明時,通過其屬性[[scope]]指定其作用域global scope;
    // 函數 fun 被調用時,創建函數執行上下文fun ec,執行上下文fun ec中的函數f被定義時創建其函數作用域(此處也叫閉包作用域)fun scope,通過函數f的屬性[[scope]]執行其作用域fun scope,
    // 返回的閉包被調用,即函數f被調用時,創建函數執行上下文f ec,並創建local作用域f scope;
    //圖解如下圖所示:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4OaERBMs-1569657487362)(…/images/scopechain.png)]

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