Javascript 運行上下文和做用域鏈

1、做用域Scope和上下文Context

    在javascript中,做用域scope和上下文context是兩個不一樣的概念。每一個函數調用都會伴隨着scope和context,從本質上來講,scope是和函數綁定的,而context是基於對象的。即scope用於在函數調用時提供變量訪問,且每次函數調用時,都不一樣;而context始終是關鍵詞this的值,它指向當前執行代碼所屬的對象。
scope 做用域
    在前一篇的「javascript變量」部分討論了javascript的做用域,分爲全局和局部,且javascript中不存在塊做用域。javascript

'this' context 上下文
    context 常常被函數所調用的方式所決定。(1)當函數被做爲一個對象的方法調用時,this 被設置爲該函數所屬的對象。如html

var obj = {
    foo: function() {
        return this;   
    }
};
obj.foo() === obj; // true。 this指向obj對象

 

(2)當使用new關鍵字去建立一個新的函數對象時,this的值也被設置爲新建立的函數對象。好比前端

function foo() {
    alert(this);
}
foo() // window
new foo() // foo

 

(3)當函數被普通調用時,this被爲全局contex或者瀏覽器的window對象。好比java

function foo() {
    alert(this);
}
foo() // window

 

2、函數生命週期

    函數生命週期能夠分爲建立和執行兩個階段。
    在函數建立階段,JS解析引擎進行預解析,會將函數聲明提早,同時將該函數放到全局做用域中或當前函數的上一級函數的局部做用域中。
    在函數執行階段,JS解析引擎會將當前函數的局部變量和內部函數進行聲明提早,而後再執行業務代碼,當函數執行完退出時,釋放該函數的執行上下文,並註銷該函數的局部變量。sql

3、變量對象

VO 和 AO
    VO (Variable Object)變量對象,對應的是函數建立階段,JS解析引擎進行預解析時,全部變量和函數的聲明(即在JS引擎的預解析階段,就肯定了VO的內容,只不過此時大部分屬性的值都是undefined)。VO與執行上下文相關,知道本身的數據存儲在哪裏,而且知道如何訪問。VO是一個與執行上下文相關的特殊對象,它存儲着在上下文中聲明的如下內容:
(1)變量 (var, 變量聲明);
(2)函數聲明 (FunctionDeclaration, 縮寫爲FD);
(3)函數的形參瀏覽器

function add(a,b){
    var sum = a + b;
    function say(){
        alert(sum);
    }
    return sum;
}
// sum,say,a,b 組合的對象就是VO,不過該對象的值基本上都是undefined

 

    AO(Activation Object)對應的是函數執行階段,當函數被調用執行時,會建立一個執行上下文,該執行上下文包含了函數所需的全部變量,該變量共同組成了一個新的對象就是Activation Object。該對象包括了:
(1)函數的全部局部變量
(2)函數的全部命名參數聲明(Function Declaration)
(3)函數的參數集合ide

function add(a,b){
    var sum = a + b;
         var x = 10;
    function say(){
        alert(sum);
    }
    return sum;
}
add(4,5);
//  AO = {
//        arguments : [4,5],
//        a : 4,
//        b : 5,
//          x: undefined
//        say : <reference to function>,
//        sum : undefined
//  }

 

更詳細的關於變量對象VO的知識,請訪問:http://www.cnblogs.com/TomXu/archive/2012/01/16/2309728.html函數

4、執行上下文

    執行上下文(execution context)是ECMAScript規範中用來描述 JavaScript 代碼執行的抽象概念。全部的 JavaScript 代碼都是在某個執行上下文中運行的。在當前執行上下文中調用 function會進入一個新的執行上下文。該function調用結束後會返回到原來的執行上下文中。若是function在調用過程當中拋出異常,而且沒有將其捕獲,有可能從多個執行上下文中退出。在function調用過程當中,也可能調用其餘的function,從而進入新的執行上下文,由此造成一個執行上下文棧。this

    每一個執行上下文都與一個做用域鏈(scope chain)關聯起來。該做用域鏈用來在function執行時求出標識符(identifier)的值。該鏈中包含多個對象,在對標識符進行求值的過程當中,會從鏈首的對象開始,而後依次查找後面的對象,直到在某個對象中找到與標識符名稱相同的屬性。在每一個對象中進行屬性查找時,會使用該對象的prototype鏈。在一個執行上下文中,與其關聯的做用域鏈只會被with語句和catch 子句影響。prototype

執行上下文屬性
    每一個執行上下文都有三個重要的屬性,變量對象(Variable Object), 做用域鏈(Scope Chain)和this,固然還有一些其餘屬性。
![][3]

    當一段javascript代碼被執行的時候,javascript解釋器會建立並使用Execution Context,這裏有兩個階段:
(1)建立階段(當函數被調用,但開始執行內部代碼以前)
(a) 建立 Scope Chain
(b) 建立VO/AO (函數內部變量聲明、函數聲明、函數參數)
(c) 設置this值
(2)激活階段/代碼執行階段
(a) 設置變量的值、函數的引用,而後解釋/執行代碼。

在階段(1)(b)建立VO/AO這一步,解釋器主要作了如下事情:
(1)根據函數的參數,建立並初始化參數列表
(2)掃描函數內部代碼,查找函數聲明。對於全部找到的內部函數聲明,將函數名和函數引用存儲 VO/AO中;若是 VO/AO中已經有同名的函數,那麼就進行覆蓋
(3)掃描函數內部代碼,查找變量聲明。對於全部找到的變量聲明,將變量名存入VO/AO中,並初始化爲 undefined;若是變量名稱和已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性(就是說變量無效)
好比如下代碼:

function foo(i) {
    var a = 'hello';
    var b = function privateB() {

    };
    function c() {

    }
}
foo(22);

 

在「建立階段」,能夠獲得下面的 Execution Context object:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: undefined,
        b: undefined
    },
    this: { ... }
}
 

在「激活/代碼執行階段」,Execution Context object 被更新爲:

fooExecutionContext = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: 'hello',
        b: pointer to function privateB()
    },
    this: { ... }
}

 

    函數在定義時就會肯定它的做用域和做用域鏈(靜態),只有在調用的時候纔會建立一個執行上下文,(1)其中包含了調用時的形參,函數內的函數聲明與變量,同時建立活動對象AO;(2)並將AO壓入執行上下文的做用域鏈的最前端,執行上下文的做用域鏈是經過它正在調用的函數的[[scope]]屬性獲得的(動態);(3)執行上下文對象中也包含this的屬性

5、做用域鏈 scope chain

    每一個運行上下文都有本身的變量對象,對於全局上下文,它是全局對象自己;對於函數,它是活動對象。做用域鏈是運行上下文全部變量對象(包括父變量對象)的列表。此鏈表用於查詢標識符。

var x = 10;
function foo() { 
  var y = 20; 
  function bar() {
    alert(x + y);
  } 
  return bar; 
}
foo()(); // 30

 

上面的例子中, bar 上下文的做用域鏈包括 AO(bar) --> AO(foo) -- > VO(global).

做用域鏈如何構造的
    上面提到,做用域鏈Scope Chain是執行上下文Execution Context的一個屬性。它是在函數被執行時,經過被執行函數的[[scope]]屬性獲得。
    函數建立時:在javascript中,函數也是一個對象,它有一個屬性[[scope]],該屬性是在函數被建立時寫入,它是該函數對象的全部父變量對象的層級鏈,它存在於函數這個對象中,直到函數銷燬。
    函數執行時:建立執行上下文Execution context, 執行上下文Execution context 把 AO 放在 函數[[scope]]最前面做爲該執行上下文的Scope chain。
即 Scope chain(運行上下文的屬性,動態) = AO|VO(運行上下文的屬性,動態) + [[Scope]](函數的屬性,靜態)

一個例子

var x = 10; 
function foo() {
  var y = 20; 
  function bar() {
    var z = 30;
    alert(x +  y + z);
  } 
  bar();
}
foo(); // 60

全局上下文的變量對象是:

globalContext.VO === Global = {
  x: 10
  foo: <reference to function>
};

在「foo」建立時,「foo」的[[scope]]屬性是:

foo.[[Scope]] = [
  globalContext.VO
];

在「foo」激活時(進入上下文),「foo」上下文的活動對象是:

fooContext.AO = {
  y: 20,
  bar: <reference to function>
};

「foo」上下文的做用域鏈爲:

fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.: 
fooContext.Scope = [
  fooContext.AO,
  globalContext.VO
];

內部函數「bar」建立時,其[[scope]]爲:

bar.[[Scope]] = [
  fooContext.AO,
  globalContext.VO
];

在「bar」激活時,「bar」上下文的活動對象爲:

barContext.AO = {
  z: 30
};

「bar」上下文的做用域鏈爲:

barContext.Scope = barContext.AO + bar.[[Scope]] // i.e.:

barContext.Scope = [
  barContext.AO,
  fooContext.AO,
  globalContext.VO
];

 

對「x」、「y」、「z」的標識符解析以下:

    "x"
    -- barContext.AO // not found
    -- fooContext.AO // not found
    -- globalContext.VO // found - 10

    "y"
    -- barContext.AO // not found
    -- fooContext.AO // found - 20

    "z"
    -- barContext.AO // found - 30

 

基於做用域鏈的變量查詢    在代碼執行時須要訪問某個變量值時,JS引擎會在Execution context的scope chain中從頭至尾查找,直到在scope chain的某個元素中找到或者沒法找到該變量。

相關文章
相關標籤/搜索