匿名函數和閉包

  • 匿名函數就是沒有名字的函數,閉包是可訪問一個函數作用域裏變量的函數。

  • //普通函數
    function box() {                                       //函數名是box             
           return'Lee';
    }
     
    //匿名函數
    function () {                                          //匿名函數,會報錯
           return'Lee';
    }
     
    //通過表達式自我執行
    (function box() {                                      //封裝成表達式
           alert('Lee');
    })();                                                  //()表示執行函數,並且傳參
     
    //把匿名函數賦值給變量
    var box = function () {                                //將匿名函數賦給變量
           return 'Lee';
    };
    alert(box());                                          //調用方式和函數調用相似
     
    //函數裏的匿名函數
    function box () {
           return function () {                             //函數裏的匿名函數,產生閉包
                  return 'Lee';
           }
    }
    alert(box()());                                        //調用匿名函數
  • 閉包是指有權訪問另一個函數作用域中的變量的函數,創建閉包的常見的方式,就是在一個函數內部創建另一個函數,通過另一個函數訪問這個函數的局部變量。

  • //通過閉包可以返回局部變量
    function box() {
           varuser = 'Lee';
           return function () {                        //通過匿名函數返回box()局部變量
                  return user;
           };
    }
    alert(box()());                                //通過box()()來直接調用匿名函數返回值
     
    var b = box();
    alert(b());                                           //另一種調用匿名函數返回值
    /*
    把局部變量駐留在內存中,可以避免使用全局變量。(全局變量污染導致應用程序不可預測性,每個模塊都可調用必將引來災難,所以推薦使用私有的,封裝的局部變量)。
    */
  • //通過全局變量來累加
    var age = 100;                                                  //全局變量
    function box() {
           age++;                                   //模塊級可以調用全局變量,進行累加
    }
    box();                                          //執行函數,累加了
    alert(age);                                     //輸出全局變量
     
    //通過局部變量無法實現累加
    function box() {
           varage = 100;
           age++;                                   //累加
           return age;
    }
     
    alert(box());                                     //101
    alert(box());                                     //101,無法實現,因爲又被初始化了
                                              
    //通過閉包可以實現局部變量的累加
    function box() {
           varage = 100;
           return function () {
                  age ++;
                  return age;
           }
    }
    var b = box();                                    //獲得函數
    alert(b());                                       //調用匿名函數
    alert(b());                                       //第二次調用匿名函數,實現累加
    /*
    由於閉包裏作用域返回的局部變量資源不會被立刻銷燬回收,所以可能會佔用更多的內存。過度使用閉包會導致性能下降,建議在非常有必要的時候才使用閉包。
    */
  • //作用域鏈的機制導致一個問題,在循環中裏的匿名函數取得的任何變量都是最後一個值。
     
    //循環裏包含匿名函數
    function box() {
           vararr = [];
     
           for(var i = 0; i < 5; i++) {
                  arr[i] = function () {
                         return i;
                  };
           }
           return arr;
    }
     
    var b = box();                                         //得到函數數組
    alert(b.length);                                       //得到函數集合長度
    for (var i = 0; i < b.length; i++) {
           alert(b[i]());                           //輸出每個函數的值,都是最後一個值
    }
    //循環裏包含匿名函數-改1,自我執行匿名函數
    function box() {
           vararr = [];
     
           for(var i = 0; i < 5; i++) {
                  arr[i] = (function (num) {          //自我執行
                         return num;
                  })(i);                              //並且傳參
           }
           return arr;
    }
     
    var b = box();                             
    for (var i = 0; i < b.length; i++) {
           alert(b[i]);                              //這裏返回的是數組,直接打印即可
    }
    //循環裏包含匿名函數-改2,匿名函數下再做個匿名函數
    function box() {
           vararr = [];
     
           for(var i = 0; i < 5; i++) {
                  arr[i] = (function (num) {
                         return function () {            //直接返回值,改2變成返回函數
                                return num;             //原理和改1一樣
                         }
                  })(i);
           }
           return arr;
    }
     
    var b = box();                             
    for (var i = 0; i < b.length; i++) {
           alert(b[i]());                               //這裏通過b[i]()函數調用即可
    }
    /*
    改1和改2中,我們通過匿名函數自我執行,立即把結果賦值給a[i]。每一個i,是調用方通過按值傳遞的,所以最終返回的都是指定的遞增的i。而不是box()函數裏的i。
    */
  • 在閉包中使用this對象也可能會導致一些問題,this對象是在運行時基於函數的執行環境綁定的,如果this在全局範圍就是window,如果在對象內部就指向這個對象。而閉包卻在運行時指向window的,因爲閉包並不屬於這個對象的屬性或方法。

  • var user = 'The Window';
     
    var obj = {
           user: 'The Object',
           getUserFunction: function () {
                  return function () {            //閉包不屬於obj,裏面的this指向window
                         return this.user;
                  };
           }
    };
     
    alert(obj.getUserFunction()());                           //The window
     
    //可以強制指向某個對象
    alert(obj.getUserFunction().call(obj));                //TheObject
     
    //也可以從上一個作用域中得到對象
           getUserFunction: function () {
                  var that = this;                        //從對象的方法裏得對象
                  return function () {
                         return that.user;
                  };
           }
  • 內存泄漏 由於IEJScript對象和DOM對象使用不同的垃圾收集方式,因此閉包在IE中會導致一些問題。就是內存泄漏的問題,也就是無法銷燬駐留在內存中的元素。以下代碼有兩個知識點還沒有學習到,一個是DOM,一個是事件。

  • function box() {
           var oDiv = document.getElementById('oDiv');    //oDiv用完之後一直駐留在內存
           oDiv.onclick= function () {
                  alert(oDiv.innerHTML);                 //這裏用oDiv導致內存泄漏
           };
    }
    box();
     
    //那麼在最後應該將oDiv解除引用來避免內存泄漏。
    function box() {
           var oDiv = document.getElementById('oDiv');    
            var text =oDiv.innerHTML;
           oDiv.onclick= function () {
                  alert(text);                                  
           };
    oDiv = null;                                                     //解除引用
    }
     
    //如果並沒有使用解除引用,那麼需要等到瀏覽器關閉才得以釋放。
  • JavaScript沒有塊級作用域的概念。JavaScript不會提醒你是否多次聲明瞭同一個變量;遇到這種情況,它只會對後續的聲明視而不見(如果初始化了,當然還會執行的)。使用模仿塊級作用域可避免這個問題。

  • //模仿塊級作用域(私有作用域)
    (function () {
           //這裏是塊級作用域
    })();
     
    //使用塊級作用域(私有作用域)改寫
    function box(count) {
           (function() {
                  for (var i = 0; i<count; i++) {}
           })();
           alert(i);                                                    //報錯,無法訪問
    }
    box(2);
  • 使用了塊級作用域(私有作用域)後,匿名函數中定義的任何變量,都會在執行結束時被銷燬。這種技術經常在全局作用域中被用在函數外部,從而限制向全局作用域中添加過多的變量和函數。一般來說,我們都應該儘可能少向全局作用域中添加變量和函數。在大型項目中,多人開發的時候,過多的全局變量和函數很容易導致命名衝突,引起災難性的後果。如果採用塊級作用域(私有作用域),每個開發者既可以使用自己的變量,又不必擔心搞亂全局作用域。

  • (function () {
           var box = [1,2,3,4];
           alert(box);                                               //box出來就不認識了
    })();
  • 在全局作用域中使用塊級作用域可以減少閉包占用的內存問題,因爲沒有指向匿名函數的引用。只要函數執行完畢,就可以立即銷燬其作用域鏈了。

  •    JavaScript沒有私有屬性的概念;所有的對象屬性都是公有的。不過,卻有一個私有變量的概念。任何在函數中定義的變量,都可以認爲是私有變量,因爲不能在函數的外部訪問這些變量。而通過函數內部創建一個閉包,那麼閉包通過自己的作用域鏈也可以訪問這些變量。而利用這一點,可以創建用於訪問私有變量的公有方法。

  • function Box() {
           var age = 100;                                           //私有變量
           functionrun() {                                        //私有函數
                  return '運行中...';
           }
           this.get= function () {                               //對外公共的特權方法
                  return age + run();
           };
    }
     
    var box = new Box();
    alert(box.get());
     
    //可以通過構造方法傳參來訪問私有變量。
    function Person(value) {
           var user = value;                                       //這句其實可以省略
           this.getUser= function () {
                  return user;
           };
           this.setUser= function (value) {
                  user = value;
           };
    }
     
    //但是對象的方法,在多次調用的時候,會多次創建。可以使用靜態私有變量來避免這個問題。
  • 通過塊級作用域(私有作用域)中定義私有變量或函數,同樣可以創建對外公共的特權方法。

  • (function () {
           var age = 100;
           functionrun() {
                  return '運行中...';
           }
           Box= function () {};                           //構造方法
           Box.prototype.go= function () {                //原型方法
                  return age + run();
           };
    })();
     
     
    var box = new Box();
    alert(box.go());
     /*
    上面的對象聲明,採用的是Box = function () {} 而不是function Box(){} 因爲如果用後面這種,就變成私有函數了,無法在全局訪問到了,所以使用了前面這種。
    */
    (function () {
           var user = '';
           Person= function (value) {                        
                  user = value;
           };
           Person.prototype.getUser= function () {
                  return user;
           };
           Person.prototype.setUser= function (value) {
                  user = value;
           }
    })();
     
    /*
    使用了prototype導致方法共享了,而user也就變成靜態屬性了。(所謂靜態屬性,即共享於不同對象中的屬性)。
    */
  • 模塊模式 之前採用的都是構造函數的方式來創建私有變量和特權方法。那麼對象字面量方式就採用模塊模式來創建

  • var box = {                                        //字面量對象,也是單例對象
           age: 100,                                   //這是公有屬性,將要改成私有
           run: function () {                          //這時公有函數,將要改成私有
                  return '運行中...';
           };
    };
     
     
     
    //私有化變量和函數:
    var box = function () {
           var age = 100;
           functionrun() {
                  return '運行中...';
           }
           return{                                                //直接返回對象
                  go : function () {
                         returnage + run();
                  }
           };
    }();
     
    //上面的直接返回對象的例子,也可以這麼寫:
    var box = function () {
           var age = 100;
           functionrun() {
                  return '運行中...';
           }
           varobj =  {                                             //創建字面量對象                
                  go : function () {
                         returnage + run();
                  }
           };
           returnobj;                                                //返回這個對象
    }();
  • 字面量的對象聲明,其實在設計模式中可以看作是一種單例模式,所謂單例模式,就是永遠保持對象的一個實例。增強的模塊模式,這種模式適合返回自定義對象,也就是構造函數。

  • function Desk() {};
    var box = function () {
           var age = 100;
           functionrun() {
                  return '運行中...';
           }
           var desk = new Desk();                              //可以實例化特定的對象
           desk.go= function () {
                  return age + run();
           };
           return desk;
    }();
    alert(box.go());
  • 發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章