亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺析JavaScript作用域鏈、執(zhí)行上下文與閉包

 更新時間:2016年02月01日 10:03:05   投稿:mrr  
JavaScript 采用詞法作用域(lexical scoping),函數(shù)執(zhí)行依賴的變量作用域是由函數(shù)定義的時候決定,而不是函數(shù)執(zhí)行的時候決定,通過本文給大家介紹JavaScript作用域鏈、執(zhí)行上下文與閉包相關(guān)知識,感興趣的朋友一起學(xué)習(xí)吧

閉包和作用域鏈?zhǔn)荍avaScript中比較重要的概念,這兩天翻閱了一些資料,把相關(guān)知識點(diǎn)給大家總結(jié)了以下。

JavaScript 采用詞法作用域(lexical scoping),函數(shù)執(zhí)行依賴的變量作用域是由函數(shù)定義的時候決定,而不是函數(shù)執(zhí)行的時候決定。以下面的代碼片段舉例說明,通常來說(基于棧的實(shí)現(xiàn),如 C 語言) foo 被調(diào)用之后函數(shù)內(nèi)的本地變量 scope 會被釋放,但是從詞法上看 foo 的內(nèi)嵌匿名函數(shù)中 scope 應(yīng)該指的是 foo 的本地變量 scope ,并且實(shí)際上代碼的運(yùn)行結(jié)果跟詞法上的表達(dá)式一致的,f 被調(diào)用之后返回的是local scope。函數(shù)對象 f 在其主體函數(shù) foo 調(diào)用結(jié)束之后,依然保持著 foo 函數(shù)體作用域變量的引用,這就是所謂的閉包 。

var scope = 'global scope';
function foo() {
var scope = 'local scope';
return function () {
return scope;
}
}
var f = foo();
f(); // 返回 "local scope"

那么閉包到底是如何工作的呢?了解閉包首先需要了解變量作用域和作用域鏈,另外一個重要的概念是執(zhí)行上下文環(huán)境。

變量作用域

JavaScript 中全局變量擁有全局的作用域,函數(shù)體內(nèi)申明的變量的作用域是整個函數(shù)體內(nèi),是局部的,當(dāng)然也包括函數(shù)體內(nèi)定義的嵌套函數(shù)。函數(shù)體內(nèi)局部變量的優(yōu)先級高于全局變量,如果局部變量與全局變量重名,全局變量會被局部變量掩蓋;同樣嵌套函數(shù)內(nèi)定義的局部變量的優(yōu)先級高于嵌套函數(shù)所在函數(shù)的局部變量。這簡直是顯而易見的,幾乎所有人都了解。
接下來談?wù)効赡艽蠹冶容^陌生的。

函數(shù)聲明提升

用一句話來說明函數(shù)申明提升,指的是函數(shù)體內(nèi)部申明的變量再整個函數(shù)內(nèi)有效。也就是說,就是在函數(shù)體最底部申明的變量,也會被提升到最頂部。舉個例子:

var scope = 'global scope';
function foo() {
console.log(scope); // 這里不會打印出 "global scope",而是 "undefined"
var scope = 'local scope'; 
console.log(scope); // 很顯然,打印出 "local scope"
}
foo();

第一個console.log(scope)會打印出undefined而不是global scope,是因?yàn)榫植孔兞康纳昝鞅惶嵘?,只是還未賦值。

作為屬性的變量

在 JavaScript 中,有三種定義全局變量的方式,如下示例代碼中的 globalVal1 、globalVal2 和 globalValue3 。一個有趣的現(xiàn)象是,實(shí)際上全局變量僅僅只是全局對象 window/global (在瀏覽器中是 window,在 node.js 中是 global)的屬性而已。為了更加符合通常意義的變量定義, JavaScript 把用 var 定義的全局變量,設(shè)計成了不可刪除的全局對象屬性。 通過Object.getOwnPropertyDescriptor(this, 'globalVal1')可以得到,其 configurable 屬性為 false 。

var globalVal1 = 1; // 不可刪除的全局變量
globalVal2 = 2; // 可刪除的全局變量
this.globalValue3 = 3; // 同 globalValue2
delete globalVal1; // => false 變量沒有被刪除
delete globalVal2; // => true 變量被刪除
delete this.globalValue3; //=> true 變量被刪除

那么問題來了,函數(shù)體內(nèi)定義的局部變量是不是也作為某個對象的屬性呢?答案是肯定的。這個對象是跟函數(shù)調(diào)用相關(guān)的,在 ECMAScript 3中稱為“call object”、ECMAScript 5中稱為“declaravite environment record”的對象。這個特殊的對象對我們來說是一種不可見的內(nèi)部實(shí)現(xiàn)。

作用域鏈

從上一節(jié)我們知道,函數(shù)局部變量可與看做是某個不可見的對象的屬性。那么 JavaScript 的詞法作用域的實(shí)現(xiàn)可以這樣描述:每一段 JavaScript 代碼(全局或函數(shù))都有一個跟它關(guān)聯(lián)的作用域鏈,它可以是數(shù)組或鏈表結(jié)構(gòu);作用域鏈中的每一個元素定義了一組作用域內(nèi)的變量;當(dāng)我們要查找變量 x 的值,那么從作用域鏈的第一個元素中找這個變量,如果沒有找到者找鏈表中的下一個元素中查找,直到找到或抵達(dá)鏈尾。了解作用域鏈的概念對理解閉包至關(guān)重要。

執(zhí)行上下文

每段 JavaScript 代碼的執(zhí)行都與執(zhí)行上下文綁定,運(yùn)行的代碼通過執(zhí)行上下文獲可用的變量、函數(shù)、數(shù)據(jù)等信息。全局的執(zhí)行上下文是唯一的,與全局代碼綁定,每執(zhí)行一個函數(shù)都會創(chuàng)建一個執(zhí)行上下文與其綁定。JavaScript 通過棧的數(shù)據(jù)結(jié)構(gòu)維護(hù)執(zhí)行上下文,全局執(zhí)行上下文位于棧底,當(dāng)執(zhí)行一個函數(shù)的時候,新創(chuàng)建的函數(shù)執(zhí)行上下文將會壓入棧中,執(zhí)行上下文指針指向棧頂,運(yùn)行的代碼即可獲得當(dāng)前執(zhí)行的函數(shù)綁定的執(zhí)行上下文。如果函數(shù)體執(zhí)行嵌套的函數(shù),也會創(chuàng)建執(zhí)行上下文并壓入棧,指針指向棧頂,當(dāng)嵌套函數(shù)運(yùn)行結(jié)束后,與它綁定的執(zhí)行上下文被推出棧,指針重新指向函數(shù)綁定的執(zhí)行上下文。同樣,函數(shù)執(zhí)行結(jié)束,指針會指向全局執(zhí)行上下文。

執(zhí)行上下文可以描述成式一個包含變量對象(對應(yīng)全局)/活動對象(對應(yīng)函數(shù))、作用域鏈和 this 的數(shù)據(jù)結(jié)構(gòu)。當(dāng)一個函數(shù)執(zhí)行時,活動對象被創(chuàng)建并綁定到執(zhí)行上下文。活動對象包括函數(shù)體內(nèi)申明的變量、函數(shù)、arguments 等。作用域鏈在上一節(jié)以及提到,是按詞法作用域構(gòu)建的。需要注意的是 this 不屬于活動對象,在函數(shù)執(zhí)行的那一刻就以及確定。
執(zhí)行上下文的創(chuàng)建是有特定的次序和階段的,不同階段有不同的狀態(tài),具體的細(xì)節(jié)可以看一下參考資料,在結(jié)尾部分會列出。

閉包

了解了作用域鏈和執(zhí)行上下文,回過頭看篇首的那段代碼,基本上就可以解釋閉包式如何工作了。函數(shù)調(diào)用的時候創(chuàng)建的執(zhí)行上下文以及詞法作用域鏈保持函數(shù)調(diào)用所需要的信息, f 函數(shù)調(diào)用之后才可以返回local scope。

需要注意的是,函數(shù)內(nèi)定義的多個函數(shù)使用的是同一個作用域鏈,在使用 for 循環(huán)賦值匿名函數(shù)對象的場景比較容易引起錯誤,舉例如下:

var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = {
func: function() {
return i;
}
};
}
arr[0].func(); // 返回 10,而不是 0

arr[0].func()返回的是 10 而不是 0,跟感官上的語義有偏差。在 ECMAScript 6 引入 let 之前, 變量作用域范圍是在整個函數(shù)體內(nèi)而不是在代碼區(qū)塊之內(nèi),所以上面的例子中所有定義的 func 函數(shù)引用了同一個作用域鏈在 for 循環(huán)之后, i 的值已經(jīng)變?yōu)?10 。

正確的做法是這樣:

var arr = [];
for (var i = 0; i < 10; i++) {
arr[i] = {
func: getFunc(i)
};
}
function getFunc(i) {
return function() {
return i;
}
}
arr[0].func(); // 返回 0

以上內(nèi)容給大家介紹了JavaScript作用域鏈、執(zhí)行上下文與閉包的相關(guān)知識,希望對大家有所幫助。

相關(guān)文章

最新評論