1. 程式人生 > >執行上下文(棧)/作用域(鏈)/with

執行上下文(棧)/作用域(鏈)/with

執行上下文(棧)context(context stack)

每一次程式碼執行和函式呼叫都會產生一個執行環境,稱為執行上下文。

一個執行上下文(caller)又可以啟用(呼叫)另一個執行上下文(callee),這時caller會暫停自身的執行把控制權交給callee進入callee的執行上下文,callee執行完畢後將控制權交回caller,callee可以用return或者丟擲Exception來結束自己的執行。

多個執行上下文會形成執行上下文棧,最頂層是當前執行上下文,底層是全域性執行上下文。

Figure 4. An execution context stack.

作用域(鏈)scope(scope chain)

作用域是每一個執行上下文自身持有的活動物件的集合,如在本執行上下文中宣告的變數和函式以及方法引數傳入的物件。

每一個執行上下文可以訪問的物件包括自身的作用域和父執行上下文的作用域和父父執行上下文作用域直到全域性作用域,這就產生了作用域鏈

作用域鏈的工作原理跟原型鏈十分相似:如果本身的作用域中查詢不到識別符號,那麼就查詢父作用域,直到頂層。

目前假設作用域的聯動是用的__parent__物件,它指向作用域鏈的下一個物件。(在ES5中,確實有一個outer連結)

Figure 9. A scope chain.

全域性上下文的作用域包含Object.prototype中的物件,with和catch會改變作用域鏈:

Object.prototype.x = 10; 
var w = 20;
var y = 30; 
// in SpiderMonkey global object
// i.e. variable object of the global
// context inherits from "Object.prototype",
// so we may refer "not defined global
// variable x", which is found in
// the prototype chain 
console.log(x); // 10 
(function foo() { 
  // "foo" local variables
  var w = 40;
  var x = 100; 
  // "x" is found in the
  // "Object.prototype", because
  // {z: 50} inherits from it 
  with ({z: 50}) {
    console.log(w, x, y , z); // 40, 10, 30, 50
  } 
  // after "with" object is removed
  // from the scope chain, "x" is
  // again found in the AO of "foo" context;
  // variable "w" is also local
  console.log(x, w); // 100, 40 
  // and that's how we may refer
  // shadowed global "w" variable in
  // the browser host environment
  console.log(window.w); // 20 
})();


在with中,查詢__parent__之前會先去查詢__proto__,會使作用域鏈增大。

Figure 10. A "with-augmented" scope chain.

只要所有外部函式的變數物件都存在,那麼從內部函式引用外部資料則沒有特別之處——我們只要遍歷作用域連結串列,查詢所需變數。

注意:通過建構函式建立的方法的作用域鏈中只有全域性變數!

var x = 10; 
function foo() { 
  var y = 20; 
  function barFD() {
    alert(x);
    alert(y);
  } 
  var barFE = function () {
    alert(x);
    alert(y);
  }; 
  var barFn = Function('alert(x); alert(y);'); 
  barFD(); // 10, 20
  barFE(); // 10, 20
  barFn(); // 10, "y" is not defined 

foo();


二維作用域鏈查詢
源於ECMAScript的原型特性。如果一個屬性在物件中沒有直接找到,查詢將在原型鏈中繼續。即常說的二維鏈查詢。(1)作用域鏈環節;(2)每個作用域鏈--深入到原型鏈環節:
function foo() {
  alert(x);

Object.prototype.x = 10; 
foo(); // 10

然而,如上文所提及,當一個上下文終止之後,其狀態與自身將會被 銷燬(destroyed) ,同時內部函式將會從外部函式中返回。

此外,這個返回的函式之後可能會在其他的上下文中被啟用,那麼如果一個之前被終止的含有一些自由變數的上下文又被啟用將會怎樣?通常來說,解決這個問題的概念在ECMAScript中與作用域鏈直接相關,被稱為 (詞法)閉包((lexical) closure)。

引出的閉包將在下章討論。