閉包,寫過JS腳本的人對這個詞一定不陌生,都說閉包是JS中最奇幻的一個知識點, 雖然在工作中,項目裡經常都會用到~ 但是是不是你已經真正的對它足夠的了解~~
又或者是你代碼中出現的閉包,並不是你刻意而為之的行為~ 又或者是因為能達到效果,也知道是閉包,但是原理卻不知道?。。。。
一千個人就有一千個哈姆雷特~ 每個人也許都有自己對閉包的理解, 我也不例外, 曾經N次百度過閉包,卻沒有真正的消化過這個知識點, 也曾工作中無數次運用過閉包, 卻不知其所以然~~
所以,我想把我理解的閉包,自己總結一下,雖然很多都是自己的理解, 但是總結再更正,才會越來越好~~
首先是閉包的定義:
閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。
當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數實在當前詞法作用域之外執行。
上面的概念基本上已經說明了閉包是什麼,這只是一個概括,不知道的人看了之後還是不會知道,所以要知道閉包還是需要庖丁解牛一樣的一層一層的去理解~~
給自己幾個疑問? 然後對這些疑問一層一層的理解,直到理解為止!
閉包的環境:
既然是叫做閉包,裡面有個包字,那麼肯定就是被包圍的意思咯~ 竟然是被包圍,肯定是在一個空間中,而JS能代表空間的是什麼? 就是作用域,作用域又有全局作用域跟局部作用域。。
所以閉包產生的環境應該就是在作用域當中了, 所以有閉包肯定有局部作用域, 有局部作用域並不一定有閉包~~
閉包的作用:
竟然有閉包的存在肯定有其存在的價值, 那麼閉包的作用體現再哪裡呢~~
var a = 1; function fn1() { console.log(a); //1 var b = 2; } console.log(b); //b is not defined //當全局中有個變量a,我們在函數fn1中可以隨時訪問/修改這個變量,但是當我的函數fn1中有一個變量2的時候,我想在外面訪問到這個b,很明顯,失敗了, 因為外部作用域是無法訪問內部作用域的;
而閉包要做的事情就是讓我們可以訪問到變量b!
function fn1() { var b = 2; return function() { return b; } } var b = fn1()(); console.log(b); //2
顯然,我們拿到了b的值,這達到了我們想要的效果, 但是,為什麼這樣可以拿到b的值呢?
從上面這個小例子中我們就可以知道,閉包的作用就是可以讓外面不能拿到變量值得地方可以順利的拿到這些值,雖然外部函數不能拿到內部函數的值是JS對其作用域的一種保護,雖然閉包破壞了這種保護,但卻實現了一些通常情況下不能實現的功能。
閉包的一些使用場景:
閉包的使用場景真的是無處不在,雖然有可能你本身並沒有察覺到,但是並不能否定它的存在~
在說閉包的一些實際應用的地方之前,先看一段JS中的閉包經典小案例~~
for(var i=1; i<=5; i++) { setTimeout(function timer() { console.log(i); //結果並不是我們想要的1-5, 而是連續出現5次6 }, i*1000); }
為什麼會是這樣呢? 因為作用域問題, 之前說過有閉包就有作用域~ 這裡只有個全局作用域, 在全局中只有一個i,所以每次改變i都只是對其賦予一個新的值;
在setTImeout方法執行時, 循環已經結束了,所以每次循環都是i++直到循環完成,變成了6;
因為JS中沒有塊級變量, 為了讓每次i的值我們都可以拿到,所以我們要創造一個作用域用來存儲變量i的值, IIFE(函數自執行)可以創造一個塊級變量:
for(var i=1; i<=5; i++) { (function(j) { //2,將i的值賦值給J setTimeout(function timer() { console.log(j); //3,j輸出的就是每次循環中i的值 }, i*1000); })(i) //1,每次循環拿到i的值 } //執行結果 1,2,3,4,5
這樣就實現的我們想要的結果,前面說,JS沒有塊級變量,如果有的話,是不是會更簡單,ES5沒有,但是ES6中有, 那麼用塊級變量怎麼實現:
for(let i=1; i<=5; i++) { //就是let setTimeout(function timer() { console.log(i); }, i*1000); } //1,2,3,4,5
在實際的工作用,我們可能經常解除到的JS模塊化,模塊化的實現就是利用的JS中的閉包~~~
function moduleFn() { var name = 'just'; var arr = [1,2,3]; function nameFn() { console.log(name); } function arrFn() { console.log(arr.join("!")); } return { //產生閉包! 在當前函數中返回了一個對象,這個對象中有nameFn,和arrFn, 這兩個函數中又有了moduleFn中的變量。 nameX: nameFn, arrX: arrFn } } var foo = moduleFn(); foo.nameX(); //just foo.arrX(); //1!2!3 //利用閉包,我們就可以獲取作用域中的值了!~~
正是因為閉包的這種特性,在很多場景中可以利用閉包大展身手~ 但是,高手使用大招是需要廢除內力的~~而閉包的內力~~~
閉包的弊端:
能實現功能固然重要,但是保證性能也同樣重要,這其中的取與捨,只有在不斷的項目實戰中去自由拿捏~
如果能對閉包的使用駕輕就熟,那麼JS中的葵花寶典你也就修煉的差不多了~~
下面是一個在網上的閉包終極題目~ 如果你都能夠理解,那麼恭喜你, 練成了葵花寶典了!~
function fun(n,o) { console.log(o) return { fun:function(m){ return fun(m,n); } }; } var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,? var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,? var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
答案自宮之後便會知道!~