摘要:傳入值現在結果是正確的了創建了一個匿名函數,通過把變量作為參數傳進去,這樣在執行的時候,由于內部的形參能夠訪問到變量,所以無需到父級作用域鏈上進行尋找,因此最后輸出達到預期目的。
變量對象 初步介紹下文根據湯姆大叔的深入javascript系列文章刪改,如果想深入理解請閱讀湯姆大叔的系列文章。
http://www.cnblogs.com/TomXu/...
變量對象(縮寫為VO)是一個與執行上下文相關的特殊對象,它存儲著在上下文中聲明的以下內容: 變量 (var, 變量聲明); 函數聲明 (FunctionDeclaration, 縮寫為FD); 函數的形參
我們可以用普通的ECMAScript對象來表示一個變量對象:
VO = {};
VO是執行上下文的屬性(property),所以:
activeExecutionContext = { VO: { // 上下文數據(var, FD, function arguments) } };
只有全局上下文的變量對象允許通過VO的屬性名稱來間接訪問(因為在全局上下文里,全局對象自身就是變量對象),在其它上下文中是不能直接訪問VO對象的,因為它只是內部機制的一個實現。
全局上下文中的變量對象只有全局上下文的變量對象允許通過VO的屬性名稱來間接訪問
在全局上下文中,有
VO(globalContext) === global;
因為我們在全局上下文中聲明的變量等都是存在全局的變量對象中,而在全局上下文中的全局變量對象又是全局對象本身。所以我們可以通過VO的屬性名稱間接訪問
var a = new String("test"); alert(a); // 直接訪問,在VO(globalContext)里找到:"test" alert(window["a"]); // 間接通過global訪問:global === VO(globalContext): "test" alert(a === this.a); // true var aKey = "a"; alert(window[aKey]); // 間接通過動態屬性名稱訪問:"test"函數上下文中的變量對象
在函數執行上下文中,VO是不能直接訪問的,此時由活動對象(activation object,縮寫為AO)扮演VO的角色。
VO(functionContext) === AO;
在理解函數上下文中的變量對象時,我們通過處理上下文代碼的2個階段來進行理解
1.進入執行上下文 2.執行代碼進入執行上下文
進入執行上文文的時候,也即是代碼執行之前,此時VO包含了下列屬性
函數形參 函數聲明 變量聲明
其中,函數聲明的等級最高,然后是函數形參,最后才是變量聲明。越高等級的聲明可以覆蓋低等級的聲明。
執行代碼這個周期內,AO/VO已經擁有了屬性(不過,并不是所有的屬性都有值,大部分屬性的值還是系統默認的初始值undefined )。這個時候會進行賦值操作以及執行代碼。
alert(x); // function var x = 10; alert(x); // 10 x = 20; function x() {}; alert(x); // 20
在進入上下文階段,由于函數具有最高的級別,所以第一次alert(x)輸出的是函數。之后進行變量賦值,分別alert 10 20。
function bar (x){ alert(x); var x = 2; } bar(3); //3
由于形參聲明比變量聲明級別高,所以alert(3),因為在進入執行上下文時變量無法覆蓋形參聲明,所以輸出的是3而不是undefined。
不使用var可以聲明一個全局變量,這句話是錯誤的。alert(a); // undefined alert(b); // "b" 沒有聲明,報錯 b = 10; var a = 20;作用域鏈
函數上下文的作用域鏈在函數調用時創建的,包含活動對象和這個函數內部的[[scope]]屬性。函數上下文包括以下內容:
activeExecutionContext = { VO: {...}, // or AO this: thisValue, Scope: [ // Scope chain // 所有變量對象的列表 // for identifiers lookup ] };
其scope定義如下:
Scope = AO + [[Scope]]
[[scope]]是所有父變量對象的層級鏈,處于當前函數上下文之上,在函數創建時存于其中。
注意這重要的一點--[[scope]]在函數創建時被存儲--靜態(不變的),永遠永遠,直至函數銷毀。即:函數可以永不調用,但[[scope]]屬性已經寫入,并存儲在函數對象中。
另外一個需要考慮的是--與作用域鏈對比,[[scope]]是函數的一個屬性而不是上下文。
因此我個人的理解是作用域鏈應該是函數本身的活動對象+父級的變量對象。其中函數本身的活動對象總是排在第一位,在尋找標識符的時候,如果在當前活動對象找不到,那么會遍歷作用域鏈上的父級變量對象。其中[[scope]]在函數創建時被存儲,與函數共存亡。
var x = 10; function foo() { alert(x); } (function () { var x = 20; foo(); // 10, but not 20 })();
說明函數的作用域鏈在函數創建的時候就已經定義好了,是靜態的,不因為調用的時候而改變。
閉包 作用域鏈的加深理解var firstClosure; var secondClosure; function foo() { var x = 1; firstClosure = function () { return ++x; }; secondClosure = function () { return --x; }; x = 2; // 影響 AO["x"], 在2個閉包公有的[[Scope]]中 alert(firstClosure()); // 3, 通過第一個閉包的[[Scope]] } foo(); alert(firstClosure()); // 4 alert(secondClosure()); // 3
firstClosure和secondClosure兩個函數創建的時候,內部的變量x都是從父級函數foo的變量對象x中引用,所以其實兩個函數都是共享一個作用域,因此導致x變量共通了。
經典閉包var data = []; for (var k = 0; k < 3; k++) { data[k] = function () { alert(k); }; } data[0](); // 3, 而不是0 data[1](); // 3, 而不是1 data[2](); // 3, 而不是2
解釋跟上面類似。function在創建的時候,內部的變量k通過訪問作用域鏈即是父級的變量對象k拿到,而當函數被調用的時候,for循環早已執行完畢,此時的K是3,所以三個函數調用的時候輸出的值都為3。
var data = []; for (var k = 0; k < 3; k++) { data[k] = (function _helper(x) { return function () { alert(x); }; })(k); // 傳入"k"值 } // 現在結果是正確的了 data[0](); // 0 data[1](); // 1 data[2](); // 2
創建了一個匿名函數,通過把k變量作為參數傳進去,這樣在執行function的時候,由于內部的形參能夠訪問到k變量,所以無需到父級作用域鏈上進行尋找,因此最后輸出達到預期目的。
閉包的理論定義這里說明一下,開發人員經常錯誤將閉包簡化理解成從父上下文中返回內部函數,甚至理解成只有匿名函數才能是閉包。
ECMAScript中,閉包指的是:
1.從理論角度:所有的函數。因為它們都在創建的時候就將上層上下文的數據保存起來了。哪怕是簡單的全局變量也是如此,因為函數中訪問全局變量就相當于是在訪問自由變量,這個時候使用最外層的作用域。 2.從實踐角度:以下函數才算是閉包: 1.即使創建它的上下文已經銷毀,它仍然存在(比如,內部函數從父函數中返回) 2.在代碼中引用了自由變量
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/50530.html
摘要:之前一篇文章我們詳細說明了變量對象,而這里,我們將詳細說明作用域鏈。而的作用域鏈,則同時包含了這三個變量對象,所以的執行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當前的函數調用棧,為當前正在被執行的函數的作用域鏈,為當前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學JavaScrip...
摘要:所以,當在函數中使用全局變量的時候,所產生的代價是最大的,因為全局對象一直處于作用域鏈的最末位置,讀取局部變量是最快的。 什么是作用域 在編程語言中,作用域控制著變量與參數的可見性及生命周期,它能減少名稱沖突,而且提供了自動內存管理(javascript 語言精粹) 靜態作用域 再者,js不像其他的編程語言一樣,擁有著塊級作用域,就像下面一段代碼。 function afunction...
摘要:所以,全局執行環境的變量對象始終都是作用域鏈中的最后一個對象。講到這里,可能你已經對執行環境執行環境對象變量對象作用域作用域鏈的理解已經他們之間的關系有了一個較清晰的認識。 JavaScript中的執行環境、作用域、作用域鏈、閉包一直是一個非常有意思的話題,很多博主和大神都分享過相關的文章。這些知識點不僅比較抽象,不易理解,更重要的是與這些知識點相關的問題在面試中高頻出現。之前我也看過...
摘要:執行返回的內部函數,依然能訪問變量輸出閉包中的作用域鏈理解作用域鏈對理解閉包也很有幫助。早期的版本里采用是計數的垃圾回收機制,閉包導致內存泄露的一個原因就是這個算法的一個缺陷。 關于閉包,我翻了幾遍書,看了幾遍視頻,查了一些資料,可是還是迷迷糊糊的,干脆自己動手來個總結吧 !歡迎指正... (~ o ~)~zZ 1. 什么是閉包? 來看一些關于閉包的定義: 閉包是指有權...
摘要:前言這段時間一直在消化作用域鏈和閉包的相關知識。而作用域鏈則是這套規則這套規則的具體運行。是變量對象的縮寫那這樣放有什么好處呢我們知道作用域鏈保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。 前言:這段時間一直在消化作用域鏈和閉包的相關知識。之前看《JS高程》和一些技術博客,對于這些概念的論述多多少少不太清楚或者不太完整,包括一些大神的技術文章。這也給我的學習上造成了一些困惑,...
摘要:該對象包含了函數的所有局部變量命名參數參數集合以及,然后此對象會被推入作用域鏈的前端。如果整個作用域鏈上都無法找到,則返回。此時的作用域鏈包含了兩個對象的活動對象和對象。 前端學習:教程&開發模塊化/規范化/工程化/優化&工具/調試&值得關注的博客/Git&面試-前端資源匯總 歡迎提issues斧正:閉包 JavaScript-閉包 閉包(closure)是一個讓人又愛又恨的somet...
閱讀 3736·2023-04-25 18:41
閱讀 1169·2021-11-11 16:55
閱讀 1823·2021-09-22 15:54
閱讀 3069·2021-09-22 15:51
閱讀 3545·2019-08-30 15:55
閱讀 1937·2019-08-30 14:19
閱讀 1277·2019-08-29 10:57
閱讀 1699·2019-08-29 10:56