原文地址:javascript
2-1 從做用域鏈談閉包java
2-3 深刻javascript——做用域和閉包github
JavaScript Closures Explained by Mailing a Packagesegmentfault
原文地址瀏覽器
閉包是指有權訪問另一個函數做用域中的變量的函數 --- 紅寶書bash
關鍵於兩點:閉包
晦澀難懂有木有? 我的感受文中的例子描述的很是好,特此摘錄:函數
function getName() {
var name = "美女的名字";
console.log(name); //"美女的名字"
}
function displayName() {
console.log(name); //報錯
}
複製代碼
外部訪問不到函數做用域中的變量。可是爲了獲得美女的名字,不死心的單身汪把代碼做了一些修改便得逞了,以下:性能
function getName() {
var name = "美女的名字";
function displayName() {
console.log(name);
}
return displayName;
}
var 美女 = getName();
美女() //"美女的名字"
複製代碼
這時的‘美女’是一個閉包了,單身汪想怎麼玩就怎麼玩了(邪惡臉--)。
---爲何閉包就能訪問外部函數的變量呢?
複製代碼
首先回顧下上期學習的 --- 執行上下文
Javascript中有一個執行上下文(execution context)的概念,它定義了變量或函數有權訪問的其它數據,決定了他們各自的行爲。每一個執行環境都有一個與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。
做用域鏈:當訪問一個變量時,解釋器會首先在當前做用域查找標示符,若是沒有找到,就去父做用域找,直到找到該變量的標示符或者不在父做用域中,這就是做用域鏈。
做用域鏈 和 原型繼承 查找時的區別:若是去查找一個普通對象的屬性,可是在當前對象和其原型中都找不到時,會返回undefined;但查找的屬性在做用域鏈中不存在的話就會拋出ReferenceError。
那些圖例仍是沒怎麼搞懂,我須要回去再翻翻個人紅寶書!
閉包的做用須要在看一下這一篇文章
MDN 對閉包的定義爲:
閉包是指那些可以訪問自由變量的函數。
什麼是自由變量?
自由變量是指在函數中使用的,但既不是函數參數也不是函數的局部變量的變量。
ECMAScript中,閉包指的是:
從理論角度:全部的函數。由於它們都在建立的時候就將上層上下文的數據保存起來了。哪怕是簡單的全局變量也是如此,由於函數中訪問全局變量就至關因而在訪問自由變量,這個時候使用最外層的做用域。
從實踐角度:如下函數纔算是閉包: 即便建立它的上下文已經銷燬,它仍然存在(好比,內部函數從父函數中返回) 在代碼中引用了自由變量
舉個例子:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();
複製代碼
瞭解具體的執行過程, 咱們知道 f 執行上下文維護了一個做用域鏈:
fContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
複製代碼
對的,就是由於這個做用域鏈,f 函數依然能夠讀取到 checkscopeContext.AO
的值,說明當 f 函數引用了 checkscopeContext.AO
中的值的時候,即便 checkscopeContext
被銷燬了,可是 JavaScript 依然會讓 checkscopeContext.AO
活在內存中,f 函數依然能夠經過 f 函數的做用域鏈找到它,正是由於 JavaScript 作到了這一點,從而實現了閉包這個概念。
因此,讓咱們再看一遍實踐角度上閉包的定義:
做用域鏈的逐級查找,也會影響到程序的性能,變量做用域鏈越長對性能影響越大,這也是咱們儘可能避免使用全局變量的一個主要緣由。
在使用閉包時,因爲做用域鏈機制的影響,閉包只能取得內部函數的最後一個值,這引發的一個反作用就是若是內部函數在一個循環中,那麼變量的值始終爲最後一個值。
例子1:
//該實例不太合理,有必定延遲因素,此處主要爲了說明閉包循環中存在的問題
function timeManage() {
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
},1000)
};
}
//調用timeManage輸出都是5
複製代碼
例子2:
function createClosure(){
var result = [];
for (var i = 0; i < 5; i++) {
result[i] = function(){
return i;
}
}
return result;
}
//調用timeManage輸出仍都是5
複製代碼
以上兩個例子能夠看出閉包在帶有循環的內部函數使用時存在的問題:由於每一個函數的做用域鏈中都保存着對外部函數(timeManage、createClosure)的活躍對象,所以,他們都引用着同一變量i,當外部函數返回時,此時的i值爲5,因此內部的每一個函數i的值也爲5。
能夠經過 匿名包裹器 (匿名自執行函數表達式)來強制返回預期的結果:
function timeManage() {
for (var i = 0; i < 5; i++) {
(function(num) {
setTimeout(function() {
console.log(num);
}, 1000);
})(i);
}
}
複製代碼
或者 在閉包匿名函數中再返回一個匿名函數賦值 :
function timeManage() {
for (var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
}
//timeManager();輸出1,2,3,4,5
function createClosure() {
var result = [];
for (var i = 0; i < 5; i++) {
result[i] = function(num) {
return function() {
console.log(num);
}
}(i);
}
return result;
}
//1, 2, 3, 4, 5
複製代碼
不管是匿名包裹器仍是經過嵌套匿名函數的方式,原理上都是因爲函數是按值傳遞,所以會將變量i的值複製給實參num,在匿名函數的內部又建立了一個用於返回num的匿名函數,這樣每一個函數都有了一個num的副本,互不影響了。
因爲匿名函數的做用域是全局性的,所以閉包的this一般指向全局對象window:
var scope = "global";
var object = {
scope:"local",
getScope:function(){
return function(){
return this.scope;
}
}
}
複製代碼
調用object.getScope()()
返回值爲global
而不是咱們預期的local
,前面咱們說過閉包中內部匿名函數會攜帶外部函數的做用域,那爲何沒有取得外部函數的this
呢?每一個函數在被調用時,都會自動建立this
和arguments
,內部匿名函數在查找時,搜索到活躍對象中存在咱們想要的變量,所以中止向外部函數中的查找,也就永遠不可能直接訪問外部函數中的變量了。總之,在閉包中函數做爲某個對象的方法調用時,要特別注意,該方法內部匿名函數的this
指向的是全局變量。
解決辦法 :
var scope = "global";
var object = {
scope:"local",
getScope:function(){
var that = this;
return function(){
return that.scope;
}
}
}
複製代碼
-內存與性能
因爲閉包中包含與函數運行期上下文相同的做用域鏈引用,所以,會產生必定的負面做用,當函數中活躍對象和運行期上下文銷燬時,因爲必要仍存在對活躍對象的引用,致使活躍對象沒法銷燬,這意味着閉包比普通函數佔用更多的內存空間,在IE瀏覽器下還可能會致使內存泄漏的問題,以下:
function bindEvent(){
var target = document.getElementById("elem");
target.onclick = function(){
console.log(target.name);
}
}
複製代碼
上面例子中匿名函數對外部對象target產生一個引用,只要是匿名函數存在,這個引用就不會消失,外部函數的target對象也不會被銷燬,這就產生了一個循環引用。解決方案是經過建立target.name副本減小對外部變量的循環引用以及手動重置對象:
function bindEvent(){
var target = document.getElementById("elem");
var name = target.name;
target.onclick = function(){
console.log(name);
}
target = null;
}
複製代碼
閉包中若是存在對外部變量的訪問,無疑增長了標識符的查找路徑,在必定的狀況下,這也會形成性能方面的損失。解決此類問題的辦法咱們前面也曾提到過:儘可能將外部變量存入到局部變量中,減小做用域鏈的查找長度。
總結:閉包不是javascript獨有的特性,可是在javascript中有其獨特的表現形式,使用閉包咱們能夠在javascript中定義一些私有變量,甚至模仿出塊級做用域,但閉包在使用過程當中,存在的問題咱們也須要了解,這樣才能避免沒必要要問題的出現。