深刻學習JS執行--建立執行上下文(變量對象,做用域鏈,this)

1、介紹

本篇繼上一篇深刻理解js執行--單線程的JS,此次咱們來深刻了解js執行過程當中的執行上下文。html

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

2、預執行

在上一篇說到,在js代碼被執行,執行上下文會被壓進執行棧中,可是在此以前還有一步工做要作,就是建立好執行上下文,由於建立好才能被壓進去啊。git

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

3、建立執行上下文

(1)執行上下文組成

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

代碼模擬

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

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

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

(2)變量對象(variable object)

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

建立變量對象(VO)時就是將各類變量和函數聲明進行提高的環節:線程

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

上述代碼的變量對象:code

//這裏用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。

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

(1)JS執行過程

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

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

(2)放上圖示

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

5、後話

整個js的執行過程就這樣了,一開始可能有點難理解,但看多幾遍就慢慢領會了。但願你們可以理解。若是以爲寫得好,記得點贊,關注哦。

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

相關文章
相關標籤/搜索