js閉包其實不難,你需要的只是了解何時使用它
- 究竟什么是閉包?
- 閉包在什么場景下使用?
- 寫前端程序需要用到閉包嗎?我用jQuery也能寫的好好滴呀?
對于初學者來說,常常會覺得閉包是個很難理解的概念,我認為之所以覺得難以理解,是因為沒有了解到閉包的用途以及它通常的使用場景,實際開發中,閉包的運用非常廣泛。
如果知道了使用閉包可以解決哪些問題,使用閉包會帶來哪些好處,完全掌握并熟練使用閉包就不再是一個難題了。還是那句話:學以致用,如果不知道如何使用,就是沒有學會。do it, make your hand dirty!
“閉包”的概念 ——《百度百科》
閉包是指可以包含自由(未綁定到特定對象)變量的代碼塊;這些變量不是在這個代碼塊內或者任何全局上下文中定義的,而是在定義代碼塊的環境中定義(局部變量)。“閉包” 一詞來源于以下兩者的結合:要執行的代碼塊(由于自由變量被包含在代碼塊中,這些自由變量以及它們引用的對象沒有被釋放)和為自由變量提供綁定的計算環境(作用域)。在PHP、Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python、Go、Lua、objective c、swift 以及Java(Java8及以上)等語言中都能找到對閉包不同程度的支持。--百度百科
從“閉包”的概念上可以知道,閉包其實是一個通用的概念,在很多領域中(比如“離散數學”,“計算機”等領域都有使用)。
“閉包”的概念 ——《javascript權威指南 第6版》
Like most modern programming languages, JavaScript uses lexical scoping. This means that functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked. In order to implement lexical scoping, the internal state of a JavaScript function object must include not only the code of the function but also a reference to the current scope chain.This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature. ——引用自“javascript權威指南 第6版”
在javascript語言中,閉包就是函數和該函數作用域的組合。從這個概念上來講,在js中,所有函數都是閉包(函數都是對象并且函數都有和他們相關聯的作用域鏈scope chain)。
既然所有函數都是閉包,還有必要專門提這個概念嗎?
大多數函數被調用時(invoked),使用的作用域和他們被定義時(defined)使用的作用域是同一個作用域,這種情況下,閉包神馬的,無關緊要。但是,當他們被invoked的時候,使用的作用域不同于他們定義時使用的作用域的時候,閉包就會變的非常有趣,并且開始有了很多的使用場景,這就是你之所以要掌握閉包的原因。
理解“閉包” step 1:掌握嵌套函數的詞法作用域規則(lexical scoping rules)
var scope = "global scope";
function checkScope() {
var scope = "local scope";
function f() {
return scope;
}
return f();
}
checkScope(); //=> "local scope"
分析一下上面的代碼,該代碼定義了一個全局變量 scope,以及一個函數checkScope,在函數checkScope中,定一個一個局部變量,同樣命名為scope,以及一個函數f(嵌套函數)。
- 在js中,函數可以用來創建函數作用域;
- 函數就像一層半透明玻璃,在函數內部可以看到函數外部的變量,但是在函數外部,看不到函數內部的變量。
- 變量的搜索是從內向外而不是從外向內搜索的。
代碼執行過程分析:
函數作用域.png
checkScope被invoke時,return f(),運行內部嵌套函數f,f沿著作用域鏈從內向外尋找變量scope,找到“local scope”,停止尋找,因此,函數返回 “local scope”;
接下來,代碼稍作修改:
var scope = "global scope";
function checkScope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
checkScope()(); //=> "這次返回什么?"
代碼執行過程分析:
checkScope被invoke時,將內部嵌套的函數f返回,因此checkScope()()這句執行時,其實運行的是f(),f函數返回scope變量,在這種情況下,f會從哪個作用域里去尋找變量scope呢?
remember 詞法作用域的基礎規則:函數被執行時(executed)使用的作用域鏈(scope chain)是被定義時的scope chain,而不是執行時的scope chain
嵌套函數f(), 被定義時,所在的作用域鏈中,變量scope是被綁定的值是“local scope”,而不是"global scope",因此,以上代碼的結果是啥?沒錯,是"local scope"!
這就是閉包的神奇特性:閉包可以捕獲到局部變量和參數的外部函數綁定,即便外部函數的調用已經結束。
只要記住一點:詞法作用域的規則,即函數被執行時(executed)使用的作用域鏈(scope chain)是 被定義 時的scope chain,而不是執行時的scope chain,就可以很容易的理解閉包的行為了。
理解“閉包” step 2:掌握閉包的使用場景
在js版本的設計模式中,很多模式的實現都需要借助于閉包,因此,掌握閉包的使用場景,可以結合設計模式一起理解學習。
閉包經典使用場景一:通過循環給頁面上多個dom節點綁定事件
場景描述:假如頁面上有5個button,要給button綁定onclick事件,點擊的時候,彈出對應button的索引編號。
循環給多個button綁定事件.png
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<button>Button0</button>
<button>Button1</button>
<button>Button2</button>
<button>Button3</button>
<button>Button4</button>
</body>
</html>
先隨手來一段for循環:
var btns = document.getElementsByTagName('button');
for(var i = 0, len = btns.length; i < len; i++) {
btns[i].onclick = function() {
alert(i);
}
}
通過執行該段代碼,發現不論點擊哪個button ,均alert 5;
why?
因為,onclick事件是被異步觸發的,當事件被觸發時,for循環早已結束,此時變量 i 的值已經是 5 。所以,當onlick事件函數順著作用域鏈從內向外查找變量 i 時,找到的值總是 5 。
那怎么能循環給button添加事件,并且還能alert出來不同的值呢?答案當然是:“閉包”!在閉包的作用下,定義事件函數的時候,每次循環的i值都被封閉起來,這樣在函數執行時,會查找定義時的作用域鏈,這個作用域鏈里的i值是在每次循環中都被保留的,因此點擊不同的button會alert出來不同的i。
上代碼:
Tip: 在js中,沒有塊級作用域 ,只有函數作用域。可以采用“立即執行函數Immediately-Invoked Function Expression (IIFE)”的方式創建作用域。
for(var i = 0, len = btns.length; i < len; i++) {
(function(i) {
btns[i].onclick = function() {
alert(i);
}
}(i))
}
運行以上代碼,是符合我們需求的。
閉包使用場景二:封裝變量
閉包可以將一些不希望暴露在全局的變量封裝成“私有變量”。
假如有一個計算乘積的函數,mult函數接收一些number類型的參數,并返回乘積結果。為了提高函數性能,我們增加緩存機制,將之前計算過的結果緩存起來,下次遇到同樣的參數,就可以直接返回結果,而不需要參與運算。這里,存放緩存結果的變量不需要暴露給外界,并且需要在函數運行結束后,仍然保存,所以可以采用閉包。
上代碼:
var mult = (function(){
var cache = {};
var calculate = function() {
var a = 1;
for(var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
return function() {
var args = Array.prototype.join.call(arguments, ',');
if(args in cache) {
return cache[args];
}
return cache[args] = calculate.apply(null, arguments);
}
}())</code></pre>
閉包使用場景三:延續局部變量的壽命
img對象經常用于數據上報,如下:
var report = function(src) {
var img = new Image();
img.src = src;
}
report('http://xxx.com/getUserInfo');
這段代碼在運行時,發現在一些低版本瀏覽器上存在bug,會丟失部分數據上報,原因是img是report函數中的局部變量,當report函數調用結束后,img對象隨即被銷毀,而此時可能還沒來得及發出http請求,所以此次請求就會丟失。
因此,我們使用閉包把img對象封閉起來,就可以解決數據丟失的問題:
var report = (function() {
var imgs = [];
return function(src) {
var img = new Image();
imgs.push(img);
img.src = src;
}
}())
閉包+設計模式
在諸多設計模式中,閉包都有廣泛的應用。對象==數據+方法。而閉包是在過程中以環境的形式包含了數據。因此,通常面向對象能實現的功能,使用閉包也可以實現。
涉及到設計模式,閉包就是一種理所當然的存在,必須熟練使用,才可以理解每種設計模式的意圖。這里先不引入設計模式了,內容太多,有空再總結吧 :)
來自:http://www.jianshu.com/p/132fb6d485ee