本文之因此會寫這種老生常談的文章,是爲了接下來的設計模式作鋪墊。既然已經提筆了,就打算不改了,繼續寫下去,相信也必定有不少人對閉包這樣的概念有些模糊,那就瞧一瞧、看一看前端
畢竟閉包和高階函數這兩種概念,在開發中是很是有份量的。好處多多,妙處多多,那麼咱們就再也不兜圈子了,直接開始今天的主題,閉包&高階函數設計模式
閉包是前端er離不開的一個話題,並且也是一個難懂又必須明白的概念。提及閉包,它與變量的做用域和變量的生命週期密切相關。 這兩個知識點咱們也沒法繞開,那麼就一塊兒瞭解下吧瀏覽器
首先變量做用域分爲兩類:全局做用域和局部做用域,這個沒話說你們都懂。咱們常說的變量做用域其實也主要是在函數中聲明的做用域bash
function fn() {
var a = 110; // a爲局部變量
console.log(a); // 110
}
fn();
console.log(a); // a is not defined 外部訪問不到內部的變量
複製代碼
上面代碼展現了在函數中聲明的局部變量a在函數外部確實沒法拿到。小樣兒的還挺囂張,對於迎難而上的coder來講,還不信拿不下a了閉包
客官,莫急,且聽風吟。你們是否還記得在js中,函數但是「一等公民」啊,大大滴厲害異步
函數能夠創造函數做用域,在函數做用域中若是要查找一個變量的時候,若是在該函數內沒有聲明這個變量,就會向該函數的外層繼續查找,一直查到全局變量爲止模塊化
因此變量的查找是由內而外的,這也造成了所謂的做用域鏈函數
var a = 7;
function outer() {
var b = 9;
function inner() {
var c = 8;
alert(b);
alert(a);
}
inner();
alert(c); // c is not defined
}
outer(); // 調用函數
複製代碼
利用做用域鏈,咱們試着去拿到a,改造一下fn函數post
function fn() {
var a = 110; // a爲局部變量
return function() {
console.log(a);
}
console.log(a); // 110
}
var fn2 = fn();
fn2(); // 110
複製代碼
如此這般,這般如此,垂手可得,小case的事,就能夠從外面訪問到局部變量a了ui
那麼到此爲止,咱們已經發現了閉包的其中一個意義:閉包就是可以讀取其餘函數內部變量的函數,嗯,沒毛病,繼續往下看
在解決了上面如何拿到小樣兒a的問題,咱們不妨再把變量生命週期這個概念先簡單地過一遍。
function fn() {
var a = 123; // fn執行完畢後,變量a就將被銷燬了
console.log(a);
}
fn();
複製代碼
雖然以上垃圾回收的過程咱們沒法親眼看見,可是聽者傷心聞者流淚啊。可不能夠不要如此殘忍,我願傾其全部,換你三生三世。
悲傷的到來,咱們沒法拒絕,那就讓咱們想辦法去改變這一切。如今再讓咱們來看下這段代碼:
function add() {
var a = 1;
return function() {
a++;
console.log(a);
}
}
var fn = add();
fn(); // 2
fn(); // 3
fn(); // 4
複製代碼
這段代碼最神奇的地方就是,當add函數執行完後,局部變量a並無被銷燬,而是依然存在,這其中到底發生了什麼?讓咱們慢慢分析一下:
閉包是個好東西,能夠完成不少工做,其中就包括一道網上常考的經典題目
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
aLi[i].onclick = function() {
console.log(i); // ?
};
}
</script>
複製代碼
見過這道題的觀衆請舉手,確實這道題的目的就是爲了考對閉包的理解。上面的答案不管怎麼點結果都是4。
這是由於li節點的onclick事件屬於異步的,在click被觸發的時候,for循環以迅雷不及掩耳盜鈴的速度就執行完畢了,此時變量i的值已是4了
所以在li的click事件函數順着做用域鏈從內向外開始找i的時候,發現i的值已經全是4了
解決方法就須要經過閉包,把每次循環的i值都存下來。而後當click事件繼續順着做用域鏈查找的時候,會先找到被存下來的i,這樣每個li點擊均可以找到對應的i值了
<script>
var aLi = document.getElementsByTagName('li');
for (var i = 0; i < aLi.length; i++) {
(function(n) { // n爲對應的索引值
aLi[i].onclick = function() {
console.log(n); // 0, 1, 2, 3
};
})(i); // 這裏i每循環一次都存一下,而後把0,1,2,3傳給上面的形參n
}
</script>
複製代碼
閉包應用很是普遍,咱們這裏就說一下你們熟知的,好比能夠封裝私有變量,能夠把一些不須要暴露在全局的變量封裝成私有變量,這樣能夠防止形成變量的全局污染
var sum = (function() {
var cache = {}; // 將cache放入函數內部,避免被其餘地方修改
return function() {
var args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
var a = 0;
for (var i = 0; i < arguments.length; i++) {
a += arguments[i];
}
return cache[args] = a;
}
})();
複製代碼
除此以外相信不少人都見過一些庫如jQuery,underscore他們的最外層都是相似以下樣子的代碼
(function(win, undefined) {
var a = 1;
var obj = {};
obj.fn = function() {};
// 最後把想要暴露出去的內容能夠掛載到window上
win.obj = obj;
})(window);
複製代碼
是的,沒錯,利用閉包也能夠作到模塊化。另外還能夠將變量的使用延長,再來看一個例子
var monitor = (function() {
var imgs = [];
return function(src){
var img = new Image();
imgs.push(img);
img.src = src;
}
})();
monitor('http://dd.com/srp.gif');
複製代碼
上面的代碼是用於打點進行統計數據的情形,在以前的一些瀏覽器中,會出現打點丟失的狀況,由於img是函數內的局部變量,當函數執行完後img就被銷燬了,而此時可能http請求尚未發出。
因此遇到這種狀況的時候,把img變量用閉包封裝起來,就能夠解決了
不少人都聽過一個版本,就是閉包會形成內存泄漏,因此要儘可能減小閉包的使用
Just now就來爲閉包來正名,不是你想象那樣的:
上面就是咱們替閉包的正名,閉包也不容易,被人用還不討好。它明白,不是它的鍋,它是不須要背的!
雖然不是終點,但仍是要搞個總結性發炎的,否則怎麼對得起扁桃體兄
閉包:
說完了閉包,咱們先休息個一分鐘,稍微理理思路。
然而真正的緣由是因爲文章內容過長,拆分紅了姊妹篇
So還請客官移步,小憩片刻以後,來繼續進入下一個主題,高階函數