国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Lexical environments: Common Theory

羅志環(huán) / 3386人閱讀

摘要:調用棧意味著中的執(zhí)行環(huán)境棧,激活記錄意味著的激活對象。此外,所有的函數(shù)是一等公民。換句話說,自由變量并不存在自身的環(huán)境中,而是周圍的環(huán)境中。值得注意的是,函數(shù)并沒有用到自由變量。在后面的情形中,我們將綁定對象稱為環(huán)境幀。

原文

ECMA-262-5 in detail. Chapter 3.1. Lexical environments: Common Theory.

簡介

在這一章,我們將討論詞法環(huán)境的細節(jié)——一種被很多語言應用管理靜態(tài)作用域的機制。為了能夠更好地理解這個概念,我們也會討論一些別的——動態(tài)作用域(ECMAScript中并沒有直接使用)。我們將看到環(huán)境是如何管理嵌套的代碼結構和閉包。ECMA-262-5規(guī)范介紹了詞法環(huán)境,盡管這是一個和ECMAScript相獨立的概念,被應用于很多函數(shù)。實際上,部分和這個話題相關的技術部分,我們已經(jīng)在之前的ES3系列中討論過了,例如變量和激活對象,作用域鏈。嚴格地來說,詞法環(huán)境相比ES3中的概念只是更加理論化,更加抽象。但這是一個ES5的年代,我建議用這些新的定義來討論和解釋ECMAScript。盡管,更加普遍的概念,例如激活記錄(activation record)(ES3中的激活對象)的調用棧(call-stack)(ES中執(zhí)行環(huán)境棧),等等,已經(jīng)在低級抽象的層面上討論過了。這一章節(jié)致力于環(huán)境的一般理論,也會涉及程序語言理論(programming languages theory)的部分。我們將從不同的角度,用不同的語言實現(xiàn),來理解為什么詞法作用域是需要的,以及這些結構是如何被創(chuàng)建的。事實上,如果我們完全理解來作用域的一般理論,那些ES中的作用域問題也就消失了。

一般理論

ES中的概念(激活對象,作用域鏈,詞法環(huán)境)都與作用域的概念相關。ES中提到的定義是作用域的一種本地實現(xiàn),及相關術語。

作用域

作用域是用來在程序的不同部分中管理變量的可見行和可訪問性。一些
封裝的抽象概念(例如命名空間,模塊)都和作用域相關,作用域被用來使系統(tǒng)更加模塊化以及避免命名變量的沖突。函數(shù)有本地變量,代碼塊有本地變量,作用域封裝了內在的數(shù)據(jù),提升了抽象程度。作用域使我們在一個程序中使用相同的變量,但是代表不同的含義,擁有不同的值。從這個角度來說,作用域是個閉合的上下文,里面的變量都與值相關聯(lián)。我們也可以說,作用域是某個變量它某個含義的邏輯邊界。例如,全局變量,局部變量等,都反映了這個變量的聲明周期。代碼塊和函數(shù)讓我們擁有了一個主要的作用域的屬性——嵌套其他作用域或者被嵌套。因此,我們可以看到并不是所有的實現(xiàn)都支持函數(shù)嵌套,同樣不是所有的實現(xiàn)都提供塊級作用域。讓我們來考慮以下的C代碼:

// global "x"
int x = 10;
 
void foo() {
   
  // local "x" of "foo" function
  int x = 20;
 
  if (true) {
    // local "x" of if-block
    int x = 30;
    printf("%d", x); // 30
  }
 
  printf("%d", x); // 20
 
}
 
foo();
 
printf("%d", x); // 10

它可以被下圖表示

ECMAScript在版本6之前并不支持塊級作用域

var x = 10;
 
if (true) {
  var x = 20;
  console.log(x); // 20
}
 
console.log(x); // 20

ES6標準中let關鍵字可以創(chuàng)建塊級變量

let x = 10;
if (true) {
  let x = 20;
  console.log(x); // 20
}
 
console.log(x); // 10

這個塊級作用域可以通過匿名自調用函數(shù)來實現(xiàn)

var x = 10;
 
if (true) {
  (function (x) {
    console.log(x); // 20
  })(20);
}
 
console.log(x); // 10
靜態(tài)(詞法)作用域

在靜態(tài)作用域中,標識符指向最近的詞法環(huán)境。單詞“l(fā)exical”在這個場合下指的是程序書寫的屬性,詞法上變量出現(xiàn)的源文字,變量被聲明的地方。在那個作用域中,變量將會在運行時被解析。單詞“static”意味著決定標識符作用域是在程序的詞法分析(parsing)的過程中。這也就是說,在程序啟動之前,我們通過閱讀代碼,就能判斷在哪個作用域下,變量將被解析。舉個例子

var x = 10;
var y = 20;
 
function foo() {
  console.log(x, y);
}
 
foo(); // 10, 20
 
function bar() {
  var y = 30;
  console.log(x, y); // 10, 30
  foo(); // 10, 20
}
 
bar();

在這個例子中,變量x在全局變量中被定義——意味著,運行時,它也將在全局對象中被解析。變量y有兩個定義,我們說過,考慮擁有變量最近的詞法作用域。變量自身所在的作用域擁有最高的優(yōu)先級。因此,在bar函數(shù)中,變量y被解析為30。bar函數(shù)中局部變量y覆蓋來同名的全局變量y。但是,同名變量y在foo函數(shù)中依然被解析為20,即使它在bar函數(shù)的內部被調用,而且在bar函數(shù)內部還有變量y。變量的解析和環(huán)境的調用是相互獨立的(in this case bar is a caller of foo, and foo is a callee)。因為foo函數(shù)被定義的位置,最近的含有變量y的詞法環(huán)境就是全局環(huán)境。如今,靜態(tài)作用域已經(jīng)被很多語言應用:C, Java, ECMAScript, Python, Ruby, Lua等等。

動態(tài)作用域

動態(tài)作用域并不在詞法環(huán)境中解析變量,而是動態(tài)形成變量棧。每當碰到變量聲明,就把變量放進棧中。變量的聲明周期結束時,將變量從棧中彈出。來看一段偽代碼。

// *pseudo* code - with dynamic scope
 
y = 20;
 
procedure foo()
  print(y)
end
 
 
// on the stack of the "y" name
// currently only one value 20
// {y: [20]}
 
foo() // 20, OK
 
procedure bar()
 
  // and now on the stack there
  // are two "y" values: {y: [20, 30]};
  // the first found (from the top) is taken
 
  y = 30
 
  // therefore:
  foo() // 30!, not 20
 
end
 
bar()

環(huán)境的調用影響了變量的解析。[譯者注:不是重點不譯了]

名稱綁定

在高級語言中,我們不在操作地址,這個地址指向內存中的數(shù)據(jù),我們直接使用變量名來指代那些數(shù)據(jù)。名稱綁定是標識符和對象的關聯(lián)。一個標識符可以綁定或解綁。如果標識符被綁定了個對象,那么它就指向這個對象。

重新綁定

// bind "foo" to {x: 10} object
var foo = {x: 10};

console.log(foo.x); // 10

// bind "bar" to the same object
// as "foo" identifier is bound

var bar = foo;

console.log(foo === bar); // true
console.log(bar.x); // OK, also 10

// and now rebind "foo"
// to the new object

foo = {x: 20};

console.log(foo.x); // 20

// and "bar" still points
// to the old object

console.log(bar.x); // 10
console.log(foo === bar); // false

可變性
// bind an array to the "foo" identifier
var foo = [1, 2, 3];
 
// and here is a *mutation* of
// the array object contents
foo.push(4);
 
console.log(foo); // 1,2,3,4
 
// also mutations
foo[4] = 5;
foo[0] = 0;
 
console.log(foo); // 0,2,3,4,5

環(huán)境

在這一部分,我們將提到詞法作用域實現(xiàn)的技術。我們將操作更抽象的實體,討論詞法作用域,在以后的解釋中,我們將用環(huán)境而不是作用域,因為ES5中也是這個術語,全局環(huán)境,函數(shù)的本地環(huán)境等等。正如我們提到的,環(huán)境說明了表達式中標識符的含義。ECMAScript用調用棧(call-stack)來管理函數(shù)的執(zhí)行。來考慮一些通用的模型來保存變量。一些事情很有趣,有閉包的系統(tǒng)和沒有閉包的系統(tǒng)。

激活記錄模型

如果沒有一等函數(shù),或者不允許內部函數(shù),最簡單存儲本地變量的方式就是調用棧本身。一個特殊的調用棧的數(shù)據(jù)結構叫做激活記錄(activation record),被用來保存環(huán)境綁定。有時候也叫調用棧幀(call-stack frame)。每當函數(shù)被調用,一個激活記錄(包含參數(shù)和本地變量)被壓入棧中。因此,當函數(shù)調用其他函數(shù),另一個棧幀被壓入棧中。當上下文結束來,激活記錄從棧中彈出,意味這所有本地變量被銷毀。這個模型在c語言中被使用。
例如

void foo(int x) {
  int y = 20;
  bar(30);
}
 
void bar(x) {
  int z = 40;
}
 
foo(10);

調用棧會有如下變化

callStack = [];
 
// "foo" function activation
// record is pushed onto the stack
 
callStack.push({
  x: 10,
  y: 20
});
 
// "bar" function activation
// record is pushed onto the stack
 
callStack.push({
  x: 30,
  z: 40
});
 
// callStack at the moment of
// the "bar" activation
 
console.log(callStack); // [{x: 10, y: 20}, {x: 30, z: 40}]
 
// "bar" function ends
callStack.pop();
 
// "foo" function ends
callStack.pop();

當bar函數(shù)被調用時

很多相似的函數(shù)執(zhí)行的邏輯方法在ECMAScript被使用。然后,有些很重要的不同。調用棧意味著ES中的執(zhí)行環(huán)境棧,激活記錄意味著ES3的激活對象。和C不同的是,ECMAScript不會從內存中移除激活對象如果有個閉包。當這個閉包是個內部函數(shù),是用來外部函數(shù)中創(chuàng)建的變量,然后這個內部函數(shù)被返回到了外面。這就意味著激活對象不應該存在棧中,而是堆中(動態(tài)分配內存)。它會一直被保存,當閉包的引用使用激活對象中的變量。更重要的是,不僅是一個激活對象被保存,如果需要,所有的父級的激活對象。

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

如果foo函數(shù)創(chuàng)建了一個閉包,即使foo執(zhí)行結束了,它的幀不會從內存中移除,因為閉包中有它的引用。

環(huán)境幀模型

和c不同,ECMAScript含有內部函數(shù)和閉包。此外,所有的函數(shù)是一等公民。

一等函數(shù)

一等函數(shù)被當作普通的對象,可以被作為參數(shù),可以作為返回值。一個簡單的例子

// create a function expression
// dynamically at runtime and
// bind it to "foo" identifier
 
var foo = function () {
  console.log("foo");
};
 
// pass it to another function,
// which in turn is also created
// at runtime and called immediately
// right after the creation; result
// of this function is again bound
// to the "foo" identifier
 
foo = (function (funArg) {
 
  // activate the "foo" function
  funArg(); // "foo"
 
  // and return it back as a value
  return funArg;
 
})(foo);
函數(shù)參數(shù)和高階函數(shù)

當一個函數(shù)被作為參數(shù),稱之為“funarg”-- functional argument的縮寫。將函數(shù)作為參數(shù)的函數(shù)稱為高階函數(shù)(higher-order function),和數(shù)學上的算子概念相似。

自由變量

自由變量是函數(shù)中使用的變量,既不是函數(shù)的參數(shù),也不是函數(shù)的本地變量。換句話說,自由變量并不存在自身的環(huán)境中,而是周圍的環(huán)境中。

// Global environment (GE)
 
var x = 10;
 
function foo(y) {
 
  // environment of "foo" function (E1)
 
  var z = 30;
 
  function bar(q) {
    // environment of "bar" function (E2)
    return x + y + z + q;
  }
 
  // return "bar" to the outside
  return bar;
 
}
 
var bar = foo(20);
 
bar(40); // 100

在這個例子中,我們有三個環(huán)境:GE,E1和E2,分別對應于全局對象,foo函數(shù)和bar函數(shù)。因此,對于bar函數(shù)來說,變量x,y,z是自由變量,它們既不是函數(shù)參數(shù),也不是bar的本地變量。值得注意的是,foo函數(shù)并沒有用到自由變量。但是變量x在內部的bar函數(shù)中被使用,另外在foo函數(shù)運行的過程中創(chuàng)建bar函數(shù),盡管如此,還是保存了父環(huán)境的綁定,為了能夠將x的綁定傳遞給內部嵌套的函數(shù)。正確的并期望出現(xiàn)100,當執(zhí)行bar函數(shù)后,這意味著bar函數(shù)記住了foo函數(shù)激活時的環(huán)境,即使foo函數(shù)已經(jīng)結束了。這就是與基于棧的激活記錄模型的不同。當我們允許內部函數(shù),同時希望靜態(tài)詞法作用域,同時將函數(shù)作為一等公民,我們應該保存函數(shù)需要的所有自由變量,當函數(shù)被創(chuàng)建的時候。

環(huán)境定義

最直接最簡單的方法去實現(xiàn)這樣的算法是保存完整的父環(huán)境,在這個父環(huán)境中,函數(shù)被創(chuàng)建。然后,在函數(shù)自己執(zhí)行時,我們創(chuàng)建自己的環(huán)境,保存自己的本地變量和參數(shù),然后設置我們的外部環(huán)境為之前保存的那個,為了能夠在那找到自由變量。我們用術語環(huán)境指代多帶帶綁定對象,或者所有的綁定對象依據(jù)嵌套的深度。在后面的情形中,我們將綁定對象稱為環(huán)境幀。一個環(huán)境是一些列幀,每個幀是個記錄綁定,將變量名和值關聯(lián)起來。我們用抽象的概念記錄,而沒有具體說明它的實現(xiàn)結構,它可能是堆中的哈希表,棧內存,虛擬機注冊(registers of the virtual machine)等。例如,例子中,環(huán)境E2有三個幀:自己的bar,foo的和全局的。環(huán)境E1有兩個幀:foo自己的,和全局的。全局環(huán)境GE只有一個幀:全局。

一個幀中任何變量至多只有一個綁定。每個幀都有個指針,指向圍繞它的環(huán)境。全局幀的外部環(huán)境的鏈接是null。變量相對于環(huán)境的值是在包含該變量的綁定的環(huán)境中的第一幀中的變量的綁定給出的值(The value of a variable with respect to an environment is the value given by the binding of the variable in the first frame in the environment that contains a binding for that variable)(源自谷歌翻譯)。

var x = 10;
 
(function foo(y) {
   
  // use of free-bound "x" variable
  console.log(x);
 
  // own-bound "y" variable
  console.log(y); // 20
   
  // and free-unbound variable "z"
  console.log(z); // ReferenceError: "z" is not defined
 
})(20);

一系列的環(huán)境幀形成了我們所稱的作用域鏈。一個環(huán)境可能會包裹多個內部環(huán)境。

// Global environment (GE)
 
var x = 10;
 
function foo() {
 
  // "foo" environment (E1)
 
  var x = 20;
  var y = 30;
 
  console.log(x + y);
 
}
 
function bar() {
   
  // "bar" environment (E2)
 
  var z = 40;
 
  console.log(x + z);
}

偽代碼

// global
GE = {
  x: 10,
  outer: null
};
 
// foo
E1 = {
  x: 20,
  y: 30,
  outer: GE
};
 
// bar
E2 = {
  z: 40,
  outer: GE
};

變量x相對于環(huán)境E1的綁定遮蔽了同名變量在全局環(huán)境中的綁定。

函數(shù)創(chuàng)建和應用法則

一個函數(shù)是相對于給定的環(huán)境創(chuàng)建的。這導致函數(shù)對象是由函數(shù)本身的代碼(函數(shù)體)和指向創(chuàng)建函數(shù)本身的環(huán)境的指針構成。

// global "x"
var x = 10;
 
// function "foo" is created relatively
// to the global environment
 
function foo(y) {
  var z = 30;
  console.log(x + y + z);
}

相當于偽代碼

// create "foo" function
 
foo = functionObject {
  code: "console.log(x + y + z);"
  environment: {x: 10, outer: null}
};

注意,函數(shù)指向它的環(huán)境,其中一個環(huán)境與函數(shù)自身相綁定。一個函數(shù)被調用,一系列的參數(shù)構成了新的幀,在這個幀中綁定了本地變量,然后在創(chuàng)建的新的環(huán)境中執(zhí)行函數(shù)體。

// function "foo" is applied
// to the argument 20
 
foo(20);

與之對應的偽代碼

// create a new frame with formal 
// parameters and local variables
 
fooFrame = {
  y: 20,
  z: 30,
  outer: foo.environment
};
 
// and evaluate the code
// of the "foo" function 
 
execute(foo.code, fooFrame); // 60

閉包

閉包是由函數(shù)代碼和創(chuàng)建函數(shù)的環(huán)境構成的。閉包被發(fā)明用來解決函數(shù)參數(shù)的問題。

函數(shù)參數(shù)問題

當返回一個函數(shù)到外面,這個函數(shù)使用了創(chuàng)建它的父環(huán)境中的自由變量怎么辦?

(function (x) {
  return function (y) {
    return x + y;
  };
})(10)(20); // 30

正如我們所知,詞法作用域在堆中保存封閉的幀。這是問題的關鍵。像c一樣用棧來保存綁定是不可行的。被保存的代碼塊和環(huán)境就是個閉包。當我們把函數(shù)作為參數(shù)傳遞到其他函數(shù)中,這個函數(shù)參數(shù)中的自由變量是如何被解析的,是在函數(shù)定義的作用域中,還是函數(shù)執(zhí)行的作用域中?

var x = 10;
 
(function (funArg) {
 
  var x = 20;
  funArg(); // 10, not 20
 
})(function () { // create and pass a funarg
  console.log(x);
});

回答這個問題的關鍵就是詞法作用域。

組合環(huán)境幀模型

很明顯,如果一些變量不被內部函數(shù)需要,就沒有必要保存它們。

// global environment
 
var x = 10;
var y = 20;
 
function foo(z) {
 
  // environment of "foo" function
  var q = 40;
 
  function bar() {
    // environment of "bar" function
    return x + z;
  }
 
  return bar;
 
}
 
// creation of "bar"
var bar = foo(30);
 
// applying of "bar"
bar();

沒有函數(shù)使用變量y,因此,我們不需要在foo和bar的閉包中保存它。全局變量x沒有在函數(shù)foo中使用,但是我們仍應該保存它,因為更深的內部函數(shù)bar需要它。

bar = closure {
  code: <...>,
  environment: {
    x: 10,
    z: 30,
  }
}

[譯者注:后面是python等其他語言閉包的例子,不譯了]

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/89602.html

相關文章

  • JavaScript 進階 從實現(xiàn)理解閉包(校對版)

    摘要:嵌套函數(shù)在一個函數(shù)中創(chuàng)建另一個函數(shù),稱為嵌套。這在很容易實現(xiàn)嵌套函數(shù)可以訪問外部變量,幫助我們很方便地返回組合后的全名。更有趣的是,嵌套函數(shù)可以作為一個新對象的屬性或者自己本身被。 來源于 現(xiàn)代JavaScript教程閉包章節(jié)中文翻譯計劃本文很清晰地解釋了閉包是什么,以及閉包如何產(chǎn)生,相信你看完也會有所收獲 關鍵字Closure 閉包Lexical Environment 詞法環(huán)境En...

    wuyangnju 評論0 收藏0
  • Lexical environments: ECMAScript implementation

    摘要:全局環(huán)境是作用域鏈的終點。環(huán)境記錄類型定義了兩種環(huán)境記錄類型聲明式環(huán)境記錄和對象環(huán)境記錄聲明式環(huán)境記錄聲明式環(huán)境記錄是用來處理函數(shù)作用域中出現(xiàn)的變量,函數(shù),形參等。變量環(huán)境變量環(huán)境就是存儲上下文中的變量和函數(shù)的。解析的過程如下 原文 ECMA-262-5 in detail. Chapter 3.2. Lexical environments: ECMAScript implement...

    roadtogeek 評論0 收藏0
  • 談談Javascript中的delete操作符

    摘要:你覺得下列代碼中,哪些操作能成功人肉判斷一下,不要放進瀏覽器里執(zhí)行。故對于解析而言,得到的為上述所有的屬性在下均為。那么又有什么玄機呢的操作可理解為對于,調用其內部的方法。幾乎所有的都是不可刪除的。 你覺得下列代碼中,哪些delete操作能成功?人肉判斷一下,不要放進瀏覽器里執(zhí)行。 // #1 a = hello world; delete a; // #2 var b = hel...

    antz 評論0 收藏0
  • 溫故而知新:JS 變量提升與時間死區(qū)

    摘要:這意味著引擎在源代碼中聲明它的位置計算其值之前,你無法訪問該變量。因此它們同樣會受到時間死區(qū)的影響。正確理解提升機制將有助于避免因變量提升而產(chǎn)生的任何未來錯誤和混亂。 開始執(zhí)行腳本時,執(zhí)行腳本的第一步是編譯代碼,然后再開始執(zhí)行代碼,如圖 showImg(https://segmentfault.com/img/bVbnPrh?w=1233&h=141); 另外,在編譯優(yōu)化方面來說,最開...

    codecraft 評論0 收藏0
  • [譯] 透過重新實作來學習參透閉包

    摘要:不過到底是怎麼保留的另外為什麼一個閉包可以一直使用區(qū)域變數(shù),即便這些變數(shù)在該內已經(jīng)不存在了為了解開閉包的神秘面紗,我們將要假裝沒有閉包這東西而且也不能夠用嵌套來重新實作閉包。 原文出處: 連結 話說網(wǎng)路上有很多文章在探討閉包(Closures)時大多都是簡單的帶過。大多的都將閉包的定義濃縮成一句簡單的解釋,那就是一個閉包是一個函數(shù)能夠保留其建立時的執(zhí)行環(huán)境。不過到底是怎麼保留的? 另外...

    CoXie 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<