深入學習JS執行--創建執行上下文(變量對象,作用域鏈,this)


一、介紹

本篇繼上一篇深入理解js執行--單線程的JS,這次我們來深入了解js執行過程中的執行上下文。

本篇涉及到的名詞:預執行,執行上下文,變量對象,活動對象,作用域鏈,this等

二、預執行

在上一篇說到,在js代碼被執行,執行上下文會被壓進執行棧中,但是在此之前還有一步工作要做,就是創建好執行上下文,因為創建好才能被壓進去啊。

創建執行上下文就是預執行過程: 接下來說說創建執行上下文的細節部分。

三、創建執行上下文

(1)執行上下文組成

執行上下文:也叫一個執行環境,有全局執行環境和函數執行環境兩種。每個執行環境中包含這三部分:變量對象/活動對象作用域鏈this的值

代碼模擬

//可以把執行上下文看作一個對象
exeContext = {
    VO = [...],  //VO代表變量對象,保存變量和函數聲明
    scopeChain = [...];  //作用域鏈
    thisValue = {...};  //this的值
}

創建執行上下文就是創建變量對象,作用域鏈和this過程

接下來就分別細說創建變量對象/活動對象,作用域鏈,this值的過程。

(2)變量對象(variable object)

變量對象中存儲了在上下文(環境)中定義的變量和函數聲明

創建變量對象(VO)時就是將各種變量和函數聲明進行提升的環節:

//用下面代碼為例子
console.log(a);
console.log(b);
console.log(c);
console.log(d);
var a = 100;
b = 10;
function c(){};
var d = function(){};

上述代碼的變量對象:

//這里用VO表示變量對象
VO = {
    a = undefined; //有a,a使用var聲明,值會被賦值為undefined
    //沒有b,因為b沒用var聲明
    c = function c (){}  //有c,c是函數聲明,並且c指向該函數
    d = undefined; //有d,d用var聲明,值會被賦值為undefined
}

解說:執行上述代碼的時候,會創建一個全局執行上下文,上下文中包含上面變量對象,創建完執行上下文后,這個執行上下文才會被壓進執行棧中。開始執行后,因為js代碼一步一步被執行,后面賦值的代碼還沒被執行到,所以使用console.log函數打印各個變量的值是變量對象中的值。

在運行到第二行時會報錯(報錯后就不再執行了),因為沒有b(b is no defined)。把第二行注釋掉后,再執行各個結果就是VO里面的對應的值。

講到這里我想大家對變量對象理解了吧,以及對變量提升和函數提升有個深入了解。

(3)活動對象(activation object)

活動對象是在函數執行上下文里面的,其實也是變量對象,只是它需要在函數被調用時才被激活,而且初始化arguments,激活后就是看做變量對象執行上面一樣的步驟。

//例子
function fn(name){
    var age = 3;
    console.log(name);
}
fn('ry');

當上面的函數fn被調用,就會創建一個執行上下文,同時活動對象被激活

//活動對象
AO = {
    arguments : {0:'ry'},  //arguments的值初始化為傳入的參數
    name : ry,  //形參初始化為傳進來的值
    age : undefined  //var 聲明的age,賦值為undefined
}

活動對象其實也是變量對象,做着同樣的工作。其實不管變量還是活動對象,這里都表明了,全局執行和函數執行時都有一個變量對象來儲存着該上下文(環境內)定義的變量和函數。

(4)作用域鏈(scope chain)

在創建執行上下文時還要創建一個重要的東西,就是作用域鏈。每個執行環境的作用域鏈由當前環境的變量對象及父級環境的作用域鏈構成。

創建作用域鏈過程:

//以本段代碼為例
function fn(a,b){
    var x = 'string',
}
fn(1,2);

1.函數被調用前,初始化function fn,fn有個私有屬性[[scope]],它會被初始化為當前全局的作用域,fn.[[scope]="globalScope"。

2.調用函數fn(1,2),開始創建fn執行上下文,同時創建作用域鏈fn.scopeChain = [fn.[[scope]]],此時作用域鏈中有全局作用域。

3.fn活動對象AO被初始化后,把活動對象作為變量對象推到作用域鏈前端,此時fn.scopeChain = [fn.AO,fn.[[scope]]],構建完成,此時作用域鏈中有兩個值,一個當前活動對象,一個全局作用域。

fn的作用域鏈構建完成,作用域鏈中有兩個值,第一個是fn函數自身的活動對象,能訪問自身的變量,還有一個是全局作用域,所以fn能訪問外部的變量。這里就說明了為什么函數中能夠訪問函數外部的變量,因為有作用域鏈,在自身找不到就順着作用域鏈往上找。

(5)this的值

上面說過執行上下文有兩種,一個全局執行上下文,一個函數執行上下,下面分別說說這兩種上下文的this。

a.全局執行上下文的this

指向window全局對象

b.函數執行上下文的this(主要講函數的this)

在《JavaScript權威指南》中有這么幾句話:
1.this是關鍵字,不是變量,不是屬性名,js語法不允許給this賦值。
2.關鍵字this沒有作用域限制,嵌套的函數不會從調用它的函數中繼承this。
3.如果嵌套函數作為方法調用,其this指向調用它的對象。
4.如果嵌套函數作為函數調用,其this值是window(非嚴格模式),或undefined(嚴格模式下)。

解讀一下: 上面說的概括了this兩種值的情況:
1.函數直接作為某對象的方法被調用則函數的this指向該對象。
2.函數作為函數直接獨立調用(不是某對象的方法),或是函數中的函數,其this指向window。

我們看幾個栗子便可理解:
栗子1:(這個例子我相信都能理解)當函數被獨立運行時,其this的值指向window對象。

function a(){
    console.log(this);
}
//獨立運行
a();  //window

栗子2:(函數中函數,這里嵌套了個外圍函數)這里也是指向window對象,也相當於函數作為函數調用,就是獨立運行。其實這個例子也說明閉包的this指向Window。

//外圍函數
function a(){
    //b函數在里面
    function b(){
        console.log(this);
    }
    //雖然在函數中,但b函數獨立運行,不是那個對象的方法
    b();
}
a();  //window

栗子3:(再寫復雜點的話)x函數即使在對象里面,但它是函數中的函數,也是作為函數運行,不是Object的方法。getName才是objcet的方法,所以getName的this指向object(在下個栗子有)。

//一個對象
var object = {
    //getName是Object的方法
    getName : function(){
        //x是getName里面的函數,它是作為函數調用的,this就是window啦
        function x(){
            console.log(this);
        }
        x();
    }
}
object.getName();  //window

以上三個都是輸出window,下面是this指向某個對象的情況。

栗子4:函數作為某個對象的方法被調用。

//一個對象
var object = {
    name : "object",
    //getName是Object的方法
    getName : function(){
        console.log(this === object);
    }
}
object.getName(); //true , 說明this指向了object

這里的getName中的this是指向objct對象的,因為getName是object的一個方法,它作為對象方法被調用。

栗子5:再來個栗子。

var name = "window";
var obj = {
    name : "obj"
};
function fn (){
    console.log(this.name);
}

//將fn通過call或bind或apply直接綁定給obj,從而成為obj的方法。
fn.call(obj);  //obj

再總結一下this的值

全局執行上下文:this的值是window
函數執行上下文:this的值兩種:
1.函數中this指向某對象,因為函數作為對象的方法:怎么看函數是對象的方法,一種是直接寫在對象里面(不是嵌套在對象方法中的函數,不懂再看看栗子3),另一種是通過call等方法直接綁定在對象中。

2.函數中this指向window:函數獨立運行,不是對象的方法,函數中的函數(閉包),其this指向window。

四、總結整個js代碼執行過程

(1)JS執行過程

js代碼執行分成了兩部分:預執行和執行

  1. 預執行:創建好執行上下文,有兩種,一種是開始執行js代碼就創建全局的執行上下文,一種是當某個函數被調用時創建它自己的函數執行上下文。這里也就是本節主要講的東西,創建執行上下文的三個重要成分。
  2. 執行:在執行棧中執行,棧頂的執行上下文獲得執行權,並按順序執行當前上下文中的代碼,執行完后彈棧銷毀上下文,執行權交給下一個棧頂執行上下文。

(2)放上圖示

某個執行上下文生命周期:

五、后話

整個js的執行過程就這樣了,一開始可能有點難理解,但看多幾遍就慢慢領會了。希望大家能夠理解。如果覺得寫得好,記得點贊,關注哦。

本文出自博客園:http://www.cnblogs.com/Ry-yuan/
作者:Ry(淵源遠願)
歡迎訪問我的個人首頁:我的首頁
歡迎訪問我的github:https://github.com/Ry-yuan/demoFiles
歡迎轉載,轉載請標明出處,保留該字段。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM