閉包算是前端面試的基礎題,但我看了不少關於閉包的文章博客,但感受不少對於閉包的理想仍是有分歧的,如今網上對閉包的理解通常是兩種:javascript
咱們先看看閉包的定義究竟是什麼,而後在來分析我在學習js的時候不一樣階段對閉包的誤解。在《javascript高級程序設計中》對閉包定義是這樣的:「閉包是指有權限訪問另外一個函數做用域中的變量的函數。「這裏沒有提到這個函數必需要return出來,咱們在看看《javascript語言精粹》中對閉包的定義是用一段很誤導人的代碼例子來解釋閉包:前端
var quo=function(status){ return{ get_status:function(){ return status; } } } var myQuo=quo("amazed"); document.writeln(myQuo.get_status());
"即便quo返回了,但get_status方法仍然享有訪問quo對象的status屬性的特權,get_status方法並非訪問該參數的一個副本,它訪問的是該參數自己,只是可能的,由於該函數能夠訪問它被建立時所處的上下文環境,這被稱爲閉包. "java
這是不少解釋閉包的文章最經常使用的解釋案例,因此致使新手第一次看這種解釋產生一個誤導,說必需要return這個函數,但咱們看看《javascript語言精粹》這段解釋中最後強調的是該函數能夠訪問被建立時所處的上下文環境,強調的是訪問外部函數的局部變量,node
而用這個例子是由於這個例子是閉包的更爲複雜的應用,由於你在函數嵌套中,內部函數的運行只能在外部函數中執行,要在全局變量中運行不了,若是咱們要在全局運行一個比較容易理解的方法是:es6
var get_status; var quo=function(status){ get_status=function(){ return status; } } quo("amazed"); document.writeln( get_status());
那這種是否是閉包呢?對上面代碼進行優化利用js 能夠return函數代碼簡化了不少。因此在個人理解只要調用了外部函數變量的函數都是閉包,而之因此對閉包的介紹都用那個案例是由於那個算是閉包的經典複雜的應用因此基本介紹閉包的都會介紹那個案例,這樣反而誤導了剛學習閉包的同窗必需要return出去,下面我在說說我理解閉包踩過的坑。面試
剛開始我對閉包理解就是匿名自執行函數,匿名自執行函數以下代碼:ajax
(function(){})() //or var dem=(function(){}())
這個匿名自執行用到最多的固然是jQuery裏面:數組
(function ($) { })(jQuery)
在jQuery插件中常常會看到這種寫法,這樣寫的目的是爲了$變量有可能會在網頁中被定義成其餘值,而爲了不$符號的衝突而將jQuery這個對象賦值成局部$變量。這樣就不會影響全局$。promise
還有一種用法是:瀏覽器
var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i); }; } a[6]();
由於js沒有塊做用域因此致使i實際上是全局變量,因此a中的因此方法裏面的i都是10,解決這個問題能夠用es6 中let,但目前有些瀏覽器不支持es6,谷歌已經支持let關鍵字了,如今作法是用es6的寫法經過node插件轉換成es5的寫法,還用一種解決方法是:
var a = []; for (var i = 0; i < 10; i++) { (function(i){ a[i] = function () { console.log(i); }; })(i) } a[6]();
用匿名自執行將i賦值到成局部變量i因此a數值中的方法是調用了匿名方法中的局部變量i,這裏也用到了閉包,因此當時我誤解爲閉包其實就是將全局變量轉換爲局部變量。面試的時候我也是這麼回答,面試官一臉懵逼啊。。。。。
第二階段的誤解就是必需要return的那個函數,因此對於上面的匿名執行方法我斬釘截鐵的說那個不是閉包啊!面試的時候面試官又一臉懵逼。。。。。
看了《javascript高級程序設計》和《javascript語言精粹》才慢慢對閉包有了全面的瞭解。這是我對閉包的理解可能也會有理解不到位的地方,歡迎留言指正,原本成長的過程就是對某件事物的理解從片面到慢慢全面的過程,而成長就要你們一塊兒討論辯論你們闡述本身的觀點才能認識的更全面。下面在幾個對閉包的複雜應用的案例。
閉包在實際項目中應用說白了就是一個函數中要頻繁操做一些變量,但這個變量在函數中定義在函數每次運行的時候都要從新聲明分配內存空間一來麻煩二來感受消耗內存(用過C#和java的都應該很熟悉垃圾處理機制,js的垃圾處理機制相似很少詳細介紹了)但設置成全局變量又會污染全局變量,在js性能優化中提到儘可能不要污染全局變量,天然外面套成函數的方式就出來了也就是閉包咯,但又要保證內部函數的運行靈活性不能限制在外層閉包的做用域,return這個函數的方式就出來了,外層函數不想用變量存儲並且還要單獨運行一次,那麼匿名自執行的的方式出來了,ok需求演變致使最後的組合書寫方式。在這裏在講講閉包的缺點,閉包一個缺點是讓調用的變量會一直存儲在內存得不到釋放,這個缺點在實際項目需求中有不一樣的解決方法:
第一場景:模塊
var serial_maker=function () { var prefix=''; var seq=0; return{ set_prefix:function (p) { prefix=new String(p) }, set_seq:function (s) { seq=s; }, gensym:function () { var result=prefix+seq; seq+=1; return result; } } } var seqer=serial_maker(); seqer,set_prefix('Q'); seqer.set_seq(1000); var unique=seqer.gensym(); //unique是"Q1000"
這是也很常見的閉包,用意就是不想把prefix和seq聲明爲全局變量,而污染全局變量,在變量生命週期的角度其實和全局變量是同樣的,prefix和seq會一直存在內存中直到頁面關閉,javascript高級程序設計中說變量會在函數執行完畢後就進行釋放,而全局變量會一直在內存中直到瀏覽器關閉,書還提到在瀏覽器關閉的時候要將用到的dom對象的變量賦值null,由於瀏覽器貌似不能釋放dom對象,這點我有點模糊記不太清楚了,我複習javascript高級程序設計的時候看到了在單獨寫一遍隨便。解決閉包的這個缺點只需在外面加一層匿名自執行函數就能夠了
(function () { var serial_maker=function () { var prefix=''; var seq=0; return{ set_prefix:function (p) { prefix=new String(p) }, set_seq:function (s) { seq=s; }, gensym:function () { var result=prefix+seq; seq+=1; return result; } } } })();
閉包中用到的變量的生聲明週期實際上是跟着調用這個函數的上一函數的生命週期的,這個例子的中的prefix和seq是在匿名函數執行後就被釋放了。這也是在平時寫js時候儘可能在外面套一層匿名自執行一來不會污染全局變量,二來在匿名函數執行完了裏面的的變量就釋放掉了,相對在性能優化上作了一點點貢獻和優化吧!
第二場景:一樣在事件綁定上也能夠用匿名閉包,性能肯定就存在了,你在設置全局變量和閉包其實一個內存佔用量。不可避免的。
第三場景:柯里化
柯里化就是將函數的參數傳遞給另外一個函數操做,通常用到柯里化是在調用ajax成功以後將數據綁到到頁面上時候用到,但隨着promise的出現,其實柯里化用的不多了。下面是一個更爲複雜的應用,讓我佩服的五體投地:
Function.prototype.curry=function () { var slice=Array.prototype.slice, args=slice.apply(arguments),//由於arguments不是真正的數組,只是相似數組的一個對象,因此這裏要將arguments轉換爲數組 that=this; debugger; return function () { return that.apply(null,args.concat(slice.apply(arguments))) } }; var add=function () { var total=0; for(var i=0;i<arguments.length;i++){ total+=arguments[i]; } return total; }; var add1=add.curry(1); document.writeln(add1(6));//7
以上場景都是參照《javascript語言精粹》。