匿名函數就是沒有名字的函數,閉包是可訪問一個函數作用域裏變量的函數。
//普通函數 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; }; }
內存泄漏 由於IE的JScript對象和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());