標籤:out 變數覆蓋 jquery ima cache log 不同的 數列 分享圖片
什麼是閉包?———>是一個函數,一個可以訪問其他函數內部資料的函數。
栗子一:
function foo() { var a = 1;}function fn() { console.log(a);//報錯,因為這裡是無法訪問到a的}
function foo() { var a = 1; function fn() { console.log(a);//這樣是可以訪問到a的 } return fn;//fn就是閉包,其在foo內部調用沒有意義,所以將其返回,交由外部來決定調用時機,更具開發意義,當執行外部函數時,才會建立閉包fn}
一、閉包基本結構:1.定義外層函數;2.定義內建函式;3.內層函數引用外層函數定義的資料;4.要將內層函數作為外層函數的傳回值;
function outer() { var data = {name: "xiaoming”};//外層函數的內部資料會一直緩衝在記憶體中 function inner() { return data; } return inner;}var closure1 = outer();//拿到閉包之後就可以決定什麼時候執行它;console.log(closure1());var closure2 = outer();console.log(closure2 == closure1);//false,由於調用了兩次outer函數,從而建立了兩個data對象,因此兩個閉包訪問到的資料(data)在記憶體中的地址是不同的;var d1 = closure1();Var d2 = closure1();Console.log(d1 == d2);//true
一個閉包是共用一個資料的,兩個閉包則是兩個資料,所以不必建立兩個閉包(即執行兩次外部函數);
閉包實際應用: 點擊每個li列印出每個li對應的數字
<ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li></ul><script> var lists = document.getElementsByTagName("li"); var i = 0, l = lists.length; for (; i<l; i++) { lists[i].onclick = function() {//給每個li綁定了點擊事件 console.log(i);//結果都列印5,因為當點擊li的時候,i已經變成了5 } }</script>
運用閉包來解決問題:
var lists = document.getElementsByTagName("li");var i = 0, l = lists.length;for (; i<l; i++) { lists[i].onclick = (function(i) {//2.接收參數到外層函數內部,並作為內部資料緩衝在記憶體中 function fn() { console.log(i);//3.這裡的i為外層函數的內部資料,分別為0,1,2,3,4 } return fn;//4.返回到外部作為點擊事件的處理函數,當發生點擊事件時,觸發這個閉包的執行,那時列印出來的i為外層函數緩衝的i值 })(i);//1.自執行函數,將全域變數i依次傳入外層函數中}
iife(立即執行函數運算式):(Function(){})();!function(){}();+function(){}();
二、閉包的作用1.避免全域汙染
var $ = function() {};//定義的事全域變數$//但如果在這之前引入了一個jquery.js,那麼jquery的$函數就會被這個全域變數$覆蓋
在日常開發中,這種事情會很常見,因為你不能保證其他開發人員會定義怎樣的變數,這時一個頁面先引入了的js檔案中的變數就有可能被後面的js檔案中定義的同名的全域變數覆蓋;另外,全域變數生命週期是隨著頁面的存在而存在的,頁面在,變數就會一直佔用記憶體,耗費效能,所以不推薦過多的使用全域變數;
function outer() {//外層函數中的$和attr其實就相當於全域變數,只要閉包存在,這些變數就會一直在 var $ = function() {}; var attr = 10; return {//這裡面的$和attr不會被任何其他的全域變數汙染 $ : $, getAttr : function() { return attr; } }}var query = outer();query.$(“divs”);console.log(query.getAttr);
以上栗子中將$和attr變數放在一個外層函數裡面作為內部資料,而將閉包(指$和getAttr兩個方法)返回並賦值給一個全域變數query,當全域變數較多時就能夠極大的降低全域汙染的機率。
2.快取資料1)可用全域變數做緩衝———>生命週期太長耗費效能2)可用cookie\localStorage等做緩衝——>io流沒有在記憶體中訪問速度快3)用閉包來做緩衝 舉個栗子:求fibonancci數列第n項值
function fib(n) { if (n < 1) { throw new Error("value not increct"); } if (n == 1 || n == 2) { return 1; } else { return fib(n - 1) + fib(n - 2); }}
遞迴方式求Fib(6)值的過程圖解:
這裡面有太多的重複計算,當要求的數值很大時,效能就會特別低;用閉包來進行最佳化:
function createFib() { var cache = [, 1, 1];//儲存計算結果,計算過的結果得以緩衝在記憶體中以便下次直接使用 return function(n) {//閉包這個函數的作用是求得地n項的值,閉包函數可拿到緩衝中已經計算過結果的那些項的值; var res = cache[n];//如果緩衝中有這一項,那麼直接用 if (!res) {//若沒有這一項的值,就要重新計算 res = cache[n] = fib(n - 1) + fib(n - 2);//第n項的值為他的前兩項值的和,依舊用遞迴,這裡不同的是,計算過的值不用再重新計算,而是直接拿緩衝中的結果,另外本次計算完成後,也要將結果儲存到cache中以便下次使用,並且將值賦給res變數用來返回; } return res; };}var fib = createFib();//需要遞迴的是fib這個閉包而不是外層函數createFib,因為閉包才是真正的執行求第n項值的功能函數,而外層函數的作用是用內部資料來做緩衝;console.log(fib(50));//大大提高計算效率
閉包的方式計算fib(6)值的過程如下:
3、高階函數滿足以下條件之一就是高階函數
- 函數類型作為參數(比如es5中forEach, map,filter,回呼函數)
- 返回函數(閉包)
舉個栗子:求員工工資(底薪+提成),普通員工底薪1000,提成每個人都不一樣,經理底薪2000,提成每個經理都不同;
function calSalary(base, ext) { var base = base * 10 + 20;//每次計算都要重複 return base + ext;}var s1 = calSalary(1000, 100);var s2 = calSalary(1000, 200);::var p1 = calSalary(2000, 100);var p2 = calSalary(2000, 300);
像這種有基數的計算,可以用閉包來進行最佳化:
function calSalary(base) { var base = base * 10 + 20;//一次計算後作為緩衝 return function(ext) { return base + ext;//這裡直接拿緩衝中的base,省去了很多重複計算 }}var s = calSalary(1000);//拿到閉包var s1 = s(500);//每次只執行閉包var s2 = s(1000);var p = calSalary(2000);//建立另一個閉包,因為共用資料不同var p1 = p(500);//同樣每次只執行閉包var p2 = p(500);
4、回呼函數傳參:栗子:給定時器的回呼函數傳參,實現div1秒後背景變成紅色;
setTimeout((function(color){//立即執行函數,接收color參數,緩衝在記憶體中 var div = document.getElementById("div"); return function() {//返回一個函數作為延時回呼函數,1秒後執行 div.style.background = color; }})(‘red‘), 1000)
擴充:
javascript閉包以及閉包的作用