js閉包的理解


  閉包算是前端面試的基礎題,但我看了很多關於閉包的文章博客,但感覺很多對於閉包的理想還是有分歧的,現在網上對閉包的理解一般是兩種:

  1. 有些文章認為閉包必須要返回嵌套函數中里面用到外面函數局部變量的方法才叫閉包,有兩個條件:1)、函數嵌套,內部函數要用到外部函數的局部變量 2)、內部函數必須返回
  2. 有些文章認為只要函數嵌套內部函數用到了外部局部變量就是閉包,不要返回內部函數   

我們先看看閉包的定義到底是什么,然后在來分析我在學習js的時候不同階段對閉包的誤解。在高級程序設計中對閉包定義是這樣的:“閉包是指有權限訪問另一個函數作用域中的變量的函數。“這里沒有提到這個函數必須要return出來,我們在看看語言精粹中對閉包的定義是用一段很誤導人的代碼例子來解釋閉包:

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方法並不是訪問該參數的一個副本,它訪問的是該參數本身,只是可能的,因為該函數可以訪問它被創建時所處的上下文環境,這被稱為閉包. "

    這是很多解釋閉包的文章最常用的解釋案例,所以導致新手第一次看這種解釋產生一個誤導,說必須要return這個函數,但我們看看《javascript語言精粹》這段解釋中最后強調的是該函數可以訪問被創建時所處的上下文環境,強調的是訪問外部函數的局部變量,

而用這個例子是因為這個例子是閉包的更為復雜的應用,因為你在函數嵌套中,內部函數的運行只能在外部函數中執行,要在全局變量中運行不了,如果我們要在全局運行一個比較容易理解的方法是:

 var get_status;
        var quo=function(status){
            get_status=function(){
                return status;
            }
        }

        quo("amazed");

        document.writeln( get_status());

那這種是不是閉包呢?對上面代碼進行優化利用js 可以return函數代碼簡化了很多。所以在我的理解只要調用了外部函數變量的函數都是閉包,而之所以對閉包的介紹都用那個案例是因為那個算是閉包的經典復雜的應用所以基本介紹閉包的都會介紹那個案例,這樣反而誤導了剛學習閉包的同學必須要return出去,下面我在說說我理解閉包踩過的坑。

剛開始我對閉包理解就是匿名自執行函數,匿名自執行函數如下代碼:

(function(){})()
//or
var dem=(function(){}())

這個匿名自執行用到最多的當然是jQuery里面:

 (function ($) {
            
        })(jQuery)

在jQuery插件中經常會看到這種寫法,這樣寫的目的是為了$變量有可能會在網頁中被定義成其他值,而為了避免$符號的沖突而將jQuery這個對象賦值成局部$變量。這樣就不會影響全局$。

還有一種用法是:

  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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM