首頁  >  文章  >  web前端  >  JavaScript作用域鏈其三:作用域鏈特徵

JavaScript作用域鏈其三:作用域鏈特徵

黄舟
黄舟原創
2016-12-20 16:26:141147瀏覽

讓我們來看看與作用域鍊和函數[[scope]]屬性相關的一些重要特徵。

閉包

在ECMAScript中,閉包與函數的[[scope]]直接相關,正如我們提到的那樣,[[scope]]在函數創建時被存儲,與函數共存亡。實際上,閉包是函數程式碼和其[[scope]]的結合。因此,作為其物件之一,[[Scope]]包含在函數內建立的詞法作用域(父變數物件)。當函數進一步啟動時,在變數物件的這個詞法鏈(靜態的儲存於創建時)中,來自較高作用域的變數將被搜尋。例如:

var x = 10;
  
function foo() {
  alert(x);
}
  
(function () {
  var x = 20;
  foo(); // 10, but not 20
})();

我們再次看到,在標識符解析過程中,使用函數建立時定義的詞法作用域-變數解析為10,而不是30。此外,這個例子也清晰的表明,一個函數(這個例子中為從函數“foo”返回的匿名函數)的[[scope]]持續存在,即使是在函數創建的作用域已經完成之後。

關於ECMAScript中閉包的理論和其執行機制的更多細節以後會講解。

透過建構函式建立的函數的[[scope]]

在上面的例子中,我們看到,在函數建立時獲得函數的[[scope]]屬性,透過該屬性存取到所有父上下文的變量。但是,這個規則有一個重要的例外,它涉及到透過函數構造函數所建立的函數。

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();

我們看到,透過函式建構函式(Function constructor)所建立的函式“bar”,是不能存取變數“y”的。但這並不意味著函數“barFn”沒有[[scope]]屬性(否則它不能訪問到變數“x”)。問題在於透過函式建構函數所建立的函數的[[scope]]屬性總是唯一的全域物件。考慮到這一點,如透過這種函數創建除全局之外的最上層的上下文閉包是不可能的。

二維作用域鏈尋找

在作用域鏈中尋找最重要的一點是變數物件的屬性(如果有的話)須考慮其中-源自於ECMAScript 的原型特性。如果一個屬性在物件中沒有直接找到,查詢將在原型鏈中繼續。即常說的二維鏈查找。 (1)作用域鏈環節;(2)每個作用域鏈-深入原型鏈環節。如果在Object.prototype 中定義了屬性,我們可以看到這種效果。

function foo() {
  alert(x);
}
  
Object.prototype.x = 10;
  
foo(); // 10

活動物件沒有原型,我們可以在下面的例子中看到:

function foo() {
  
  var x = 20;
  
  function bar() {
    alert(x);
  }
  
  bar();
}
  
Object.prototype.x = 10;
  
foo(); // 20

如果函數「bar」上下文的活化物件有一個原型,那麼「x」將在Object.prototype 中被解析,因為它在AO中不被直接解析。但在上面的第一個例子中,在標識符解析中,我們到達全域物件(在某些執行中並不全是這樣),它從Object.prototype繼承而來,響應地,「x」解析為10。

同樣的情況出現在一些版本的SpiderMokey 的命名函數表達式(縮寫為NFE)中,在那裡特定的物件儲存從Object.prototype繼承而來的函數表達式的可選名稱,在Blackberry中的一些版本中,執行時啟動物件從Object.prototype繼承。

全局和eval上下文中的作用域鏈

這裡不一定很有趣,但必須提示一下。全域上下文的作用域鏈僅包含全域物件。程式碼eval的上下文與目前的呼叫上下文(calling context)擁有相同的作用域鏈。

globalContext.Scope = [
  Global
];
  
evalContext.Scope === callingContext.Scope;

程式碼執行時對作用域鏈的影響

在ECMAScript 中,在程式碼執行階段有兩個聲明能修改作用域鏈。這就是with聲明和catch語句。它們被加入到作用域鏈的最前端,物件須在這些聲明中出現的識別碼中尋找。如果發生其中的一個,作用域鏈簡要的作如下修改:

Scope = withObject|catchObject + AO|VO + [[Scope]]

在這個例子中添加對象,對像是它的參數(這樣,沒有前綴,這個對象的屬性變得可以訪問)。

var foo = {x: 10, y: 20};
  
with (foo) {
  alert(x); // 10
  alert(y); // 20
}

作用域鏈修改成這樣:

Scope = foo + AO|VO + [[Scope]]

我們再次看到,透過with語句,物件中標識符的解析添加到作用域鏈的最前端:

var x = 10, y = 10;
  
with ({x: 20}) {
  
  var x = 30, y = 30;
  
  alert(x); // 30
  alert(y); // 30
}
  
alert(x); // 10
alert(y); // 30

在进入上下文时发生了什么?标识符“x”和“y”已被添加到变量对象中。此外,在代码运行阶段作如下修改:

x = 10, y = 10;

对象{x:20}添加到作用域的前端;

在with内部,遇到了var声明,当然什么也没创建,因为在进入上下文时,所有变量已被解析添加;

在第二步中,仅修改变量“x”,实际上对象中的“x”现在被解析,并添加到作用域链的最前端,“x”为20,变为30;

同样也有变量对象“y”的修改,被解析后其值也相应的由10变为30;

此外,在with声明完成后,它的特定对象从作用域链中移除(已改变的变量“x”--30也从那个对象中移除),即作用域链的结构恢复到with得到加强以前的状态。

在最后两个alert中,当前变量对象的“x”保持同一,“y”的值现在等于30,在with声明运行中已发生改变。

同样,catch语句的异常参数变得可以访问,它创建了只有一个属性的新对象--异常参数名。图示看起来像这样:

try {
  ...
} catch (ex) {
  alert(ex);
}

作用域链修改为:

var catchObject = {
  ex: <exception object>
};
  
Scope = catchObject + AO|VO + [[Scope]]

在catch语句完成运行之后,作用域链恢复到以前的状态。

结论

在这个阶段,我们几乎考虑了与执行上下文相关的所有常用概念,以及与它们相关的细节。按照计划--函数对象的详细分析:函数类型(函数声明,函数表达式)和闭包。顺便说一下,在这篇文章中,闭包直接与[[scope]]属性相关,但是,关于它将在合适的篇章中讨论。

 以上就是JavaScript作用域链其三:作用域链特征的内容,更多相关内容请关注PHP中文网(www.php.cn)! 


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn