閉包與變量:
作用域鏈的一個副作用,閉包只能取得包含函數中任何變量的最后一個值。別忘了閉包所保存的是整個變量對象,而不是某個特殊的變量。
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 中的函數表達式和閉包都是極其有用的特性,利用它們可以實現很多功能。因為創建閉包必須維護額外的作用域,所以過度使用它們可能會占用大量內存。
,