JS 中閉包的變量 閉包與this


閉包與變量:

作用域鏈的一個副作用,閉包只能取得包含函數中任何變量的最后一個值。別忘了閉包所保存的是整個變量對象,而不是某個特殊的變量。

    function fn1(){
        //創建一個數組
        var arr = new Array();
        //為數組賦值,此時i是保存在fn1 這個作用域中
        for (var i = 0; i <10; i++) {
            arr[i] = function(){
                return i
            }
        }
    return arr;
   }

   var fs = fn1();
   for (var i = 0; i < fs.length; i++) {
       fs[i]();   //此時通過閉包來調用函數,會去上一級作用域中找,這是i的值已經是10,所以會連續輸出10個  10;
   };

解決方法:通過創建另一個匿名函數強制讓閉包的行為符合預期,

 function fn1(){       
        var arr = new Array();     
        for (var i = 0; i <10; i++) {
            arr[i] = (function(num){
                return function(){
                    return num; } })(i); //此時 有大量的作用域
        }
    return arr;
   }

   var fs = fn1();
   for (var i = 0; i < fs.length; i++) {
       fs[i]();    //此時就是 0,1,2,3,4,5,6,7,8,9  ,
   };

消耗大量的內存,

閉包的this問題:

在閉包中使用 this 對象也可能會導致一些問題,this 對象是在運行時基於函數的執行環境綁定的:
在全局函數中, this 等於 window,當函數作為某個對象的方法調用時, this 等於那個對象。不過,匿名函數的執行環境具有全局性,因此其 this 對象通常指向 window。

var name ="A";
   var object = {
        name: "obj",
        say: function(){
            return function(){
                return this.name;
            }
        }
   }
   object.say()();  //此時,輸出的是A,
當調用object.say()后,object的函數 內存空間已經釋放,但是里面的閉包 依然存在,此時這個this就是指向window

解決方法:

 var name ="A";
   var object = {
        name: "obj",
        say: function(){
            //此時 that 就指向了 object 
            var that = this;
            return function(){
                return that.name;
            }
        }
   }
 object.say()();  //此時,輸出的是obj,

內存的泄露:

如果閉包的作用域鏈中保存着一個HTML 元素,那么就意味着該元素將無法被銷毀。

  function assignHandler(){
            var element = document.getElementById("someElement");
            element.onclick = function(){
      alert(element.id);
    };
  }

創建了一個作為 element 元素事件處理程序的閉包,匿名函數保存了一個對 assignHandler()的活動對象的引用,只要匿名函數存在, element 的引用數至少也是 1,因此它所
占用的內存就永遠不會被回收。可以通過稍微改寫一下代碼來解決,

function assignHandler(){
        var element = document.getElementById("someElement");
        var id = element.id;
        element.onclick = function(){
            alert(id);
        };
        element = null;
  }

把 element.id 的一個副本保存在一個變量中,並且在閉包中引用該變量消除了循環引用。僅僅做到這一步,還是不能解決內存泄漏。閉包會引用包含函數
的整個活動對象,而其中包含着 element。即使閉包不直接引用 element,包含函數的活動對象中也仍然會保存一個引用。因此,有必要把 element 變量設置為 null。

塊級作用域:

JavaScript 沒有塊級作用域的概念,不管是使用循環還是判斷之后,這個變量會一直存在,所以當全局中使用了 循環 或者判斷后,這個變量可能會影響到函數的變量,所以除非特殊情況,不要使用全局變量,且 全局變量在作用域鏈最頂部,訪問最慢,

for (var i = 0; i < 10; i++) {
      
  };
  alert(i);   //此時i就是 10;

解決的方式是匿名函數,用作塊級作用域(通常稱為私有作用域)的匿名函數的語法如下所示。

 (function(){
    //這里是塊級作用域
    })();

定義並立即調用了一個匿名函數。在一個由很多開發人員共同參與的大型應用程序中,過多的全局變量和函數很容易導致命名沖突。而通過創建私有作用域,每個開發人員既可
以使用自己的變量,又不必擔心搞亂全局作用域。
這種做法可以減少閉包占用的內存問題,因為沒有指向匿名函數的引用。只要函數執行完畢,就可以立即銷毀其作用域鏈了。

私有變量:

私有變量包括函數的參數、局部變量和在函數內部定義的其他函數。

function add(num1, num2){
    var sum = num1 + num2;
    return sum;
}這個函數內部,有 3 個私有變量: num1、 num2 和 sum。在函數內部可以訪問這幾個變量,但在函數外部則不能訪問它們。如果在這個函數內部創建一個閉包,那么閉包通過自己的作用域鏈也可以訪
問這些變量。

有權訪問私有變量和私有函數的公有方法稱為特權方法(privileged method)。

function Person(name){
    //特權方法
    this.setName = function(value){
        name = value;
    }
    //特權方法
    this.getName = function(){
        return name;
    }
 }
 var p = new Person("Linda");   p.setName("Joke");   p.getName(); //Joke

兩個特權方法: getName()和 setName()。這兩個方法都可以在構造函數外部使用,而且都有權訪問私有變量 name。但在 Person 構造函數外部,沒有任何辦法訪問 name。
它們作為閉包能夠通過作用域鏈訪問 name。私有變量 name在 Person 的每一個實例中都不相同,因為每次調用構造函數都會重新創建這兩個方法。
構造函數模式的缺點是針對每個實例都會創建同樣一組新方法,而使用靜態私有變量來實現特權方法就可以避免這個問題。

靜態私有變量:

多查找作用域鏈中的一個層次,就會在一定程度上影響查找速度。而這正是使用閉包和私有變量的一個顯明的不足之處

模塊模式

模塊模式通過為單例添加私有變量和特權方法能夠使其得到增強,

var singleton = (function(){
    //私有變量和私有函數
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }

    //特權/公有 方法和屬性
    return {
        publicProperty: true,
        publicMethod : function(){
                privateVariable++;
                return privateFunction();
        }
    }
 })();
這個模塊模式使用了一個返回對象的匿名函數。匿名函數內部,定義了私有變量和函數。返回的對象 字面量中只包含可以公開的屬性和方法。由於這個對象是在匿名函數內部定義的,因此它的公有方法 有權 訪問私有變量和函數。這個
對象字面量定義的是單例的公共接口。這種模式在需要對單例進行某些初始化,同時又需要維護其私有變量時是非常有用的:
 var application = (function(){
    //私有變量和函數
    var components = new Array();
    components.push(new BaseComponent());
    //公有
    return{
        getComponent:function(){
            return components.length;
        },
        registerComponent:function(component){
            if(typeof component == "object")
            {
                components.push(component);
            }
        }
    }
 })();

如果必須創建一個對象並以某些數據對其進行初始化,同時還要公開一些能夠訪問這些私有數據的方法,那么就可以使用模塊模式。以這種模式創建的每個單例都是 Object 的實例,

增強的模塊模式:

這種增強的模塊模式適合那些單例必須是某種類型的實例,同時還必須添加某些屬性和(或)方法對其加以增強的情況。

var singleton = (function(){
    //私有變量和私有函數
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //創建對象
    var object = new CustomType();
    //添加特權/公有屬性和方法
    object.publicProperty = true;
    object.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
    //返回這個對象
    return object;
})();

總結:

函數表達式不同於函數聲明。函數聲明要求有名字,但函數表達式不需要。沒有名字的函數表達式也叫做匿名函數。
遞歸函數應該始終使用 arguments.callee 來遞歸地調用自身,不要使用函數名——函數名可能會發生變化。


當在函數內部定義了其他函數時,就創建了閉包。閉包有權訪問包含函數內部的所有變量,原理:

閉包的作用域鏈包含着它自己的作用域、包含函數的作用域和全局作用域。

函數的作用域及其所有變量都會在函數執行結束后被銷毀。

當函數返回了一個閉包時,這個函數的作用域將會一直在內存中保存到閉包不存在為止。使用匿名函數可以在 JavaScript 中模仿塊級作用域(JavaScript 本身沒有塊級作用域的概念),要點如下。

 創建並立即調用一個函數,這樣既可以執行其中的代碼,又不會在內存中留下對該函數的引用。
 結果就是函數內部的所有變量都會被立即銷毀—

閉包還可以用於在對象中創建私有變量,相關概念和要點如下。
 即使 JavaScript 中沒有正式的私有對象屬性的概念,但可以使用閉包來實現公有方法,而通過公有方法可以訪問在包含作用域中定義的變量。
 有權訪問私有變量的公有方法叫做特權方法。
 可以使用構造函數模式、原型模式來實現自定義類型的特權方法,也可以使用模塊模式、增強的模塊模式來實現單例的特權方法。

JavaScript 中的函數表達式和閉包都是極其有用的特性,利用它們可以實現很多功能。因為創建閉包必須維護額外的作用域,所以過度使用它們可能會占用大量內存。

 

 

 

 

 


 


免責聲明!

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



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