HTML5学习笔记(十八):闭包
高階函數
JavaScript的函數其實都指向某個變量。既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,也可以返回一個函數,這種函數就稱之為高階函數。
函數作為參數
示例如下:
1 function absAdd(x, y, f) { 2 return f(x) + f(y); 3 } 4 console.log(absAdd(-1, 2, Math.abs)); // 3函數作為參數的好處是我們可以通過修改參數就可以改變函數的行為。
函數作為返回值
示例如下:
1 function arrSum(arr) { 2 return function(){ 3 return arr.reduce(function (x, y) { 4 return x + y; 5 }); 6 }; 7 } 8 9 var f1 = arrSum([1, 2, 3, 4, 5]); 10 var f2 = arrSum([2, 4, 6, 8, 10]); 11 12 console.log(f1 === f2); // false 13 14 console.log(f1()); // 15 15 console.log(f2()); // 30每次調用arrSum方法返回的都是一個新創建的函數,所以判斷是不相等的。
返回函數時,可以決定在何時執行該函數。
閉包
我們注意到上面例子里返回的函數在其定義內部引用了局部變量arr,所以,當一個函數返回了一個函數后,其內部的局部變量還被新函數引用,這種情況就稱為閉包。
我們看下一個例子:
1 function foo() { 2 var r = []; 3 for (var i = 0; i < 3; i++) { 4 r[i] = function() { 5 return i; 6 }; 7 } 8 return r; 9 } 10 11 var arr = foo(); 12 for (var i = 0; i < 3; i++) { 13 console.log( arr[i]() ); 14 } 15 16 // 3 17 // 3 18 // 3我們希望打印0,1,2這幾個數字,但是實際上打印的都是3,這是由于返回的函數保存的是變量i,實際上在循環之后變量i就變成了3,所以會出現這樣的情況。
立即執行函數
那么如何才能打印出0,1,2這幾個數字呢,這里需要用到立即執行函數,立即執行函數的意思是在定義好函數之后立即執行,一般這樣的函數都是匿名函數。
格式如下:
1 (function (x) { 2 return x * x; 3 })(3);即用一個括號將函數包含,后面緊跟另一個括號進行調用,同時可以進行參數傳遞。
我們再看下面的例子:
1 function foo() { 2 var r = []; 3 for (var i = 0; i < 3; i++) { 4 r[i] = (function(index) { 5 return function() { 6 return index; 7 }; 8 })(i); 9 } 10 return r; 11 } 12 13 var arr = foo(); 14 for (var i = 0; i < 3; i++) { 15 console.log( arr[i]() ); 16 } 17 18 // 0 19 // 1 20 // 2我們來看看這個例子,每個閉包函數實際上引用的是index參數,而index參數是在立即執行函數執行時傳入的i,所以不存在改變的情況,就可以打印出對應的索引值了。
關于this對象
我們來看下面的例子:
1 var name = "Window"; 2 3 var obj = { 4 name: "Object", 5 func: function() { 6 return function() { 7 return this.name; 8 }; 9 } 10 }; 11 12 console.log( obj.func()() ); // Window 13 14 var f = obj.func(); 15 console.log( f() ); // Window我們發現返回的是全局的name屬性,而不是我們期望的obj的name屬性。
我們知道每個函數在調用時都會獲得this及arguments兩個參數,而this參數指向調用該方法的對象。
所以我們可以看一下第14和15行,調用obj.func時this是指向obj對象的,返回的函數實際上被綁定到全局對象上了,所以當調用f函數時,實際上是window進行調用的,所以拿到的name就是window.name。
解決方法如下:
1 var name = "Window"; 2 3 var obj = { 4 name: "Object", 5 func: function() { 6 var that = this; 7 return function() { 8 return that.name; 9 }; 10 } 11 }; 12 13 console.log( obj.func()() ); // Object 14 15 var f = obj.func(); 16 console.log( f() ); // Object利用了閉包會持有調用鏈上的變量的原理即可。
私有屬性
在JavaScript中,沒有私有屬性的概念,所有屬性都是公開的。
但是有私有變量的概念,在函數中聲明的變量,都是該函數私有的,函數以外的地方不能訪問。
我們利用閉包和私有變量的特性可以創建出類似于私有屬性的變量。
1 function Person(name) { 2 // 私有變量 3 var age = 0; 4 // 私有函數 5 function foo() { 6 console.log("call private function!"); 7 } 8 9 this.setName = function(value) { 10 name = value; 11 foo(); 12 }; 13 this.getName = function() { 14 return name; 15 }; 16 17 this.setAge = function(value) { 18 age = value; 19 foo(); 20 }; 21 this.getAge = function() { 22 return age; 23 }; 24 } 25 26 var p = new Person("Li Lei"); 27 p.age = 28; 28 console.log(p.getAge()); // 0 29 p.setAge(30); 30 console.log(p.getAge()); // 30 31 console.log(p.age); // 28我們會發現,在函數內部是直接使用age來訪問私有變量的,而如果是this.age則表示當前對象的age公開屬性,所以p.age和p.getAge會取得不同的數值。外部是無法訪問到內部變量age和參數name的。
使用立即執行函數創建
我們發現上面的方法只能將所有代碼都寫在構造函數中才能訪問到私有變量,其實還有一種寫法:
1 (function(){ 2 // 使用 var 定義的變量外部無法訪問 3 var _name; 4 var age = 0; 5 // 定義的函數外部無法訪問 6 function foo() { 7 console.log("call private function!"); 8 } 9 10 // 不使用 var 定義的對象外部可訪問 11 Person = function(name) { 12 _name = name; 13 } 14 15 Person.prototype.setName = function(value) { 16 _name = value; 17 foo(); 18 } 19 Person.prototype.getName = function() { 20 return _name; 21 } 22 23 Person.prototype.setAge = function(value) { 24 age = value; 25 foo(); 26 } 27 Person.prototype.getAge = function() { 28 return age; 29 } 30 })(); 31 32 var p = new Person("Li Lei"); 33 p.age = 28; 34 console.log(p.getAge()); // 0 35 p.setAge(30); 36 console.log(p.getAge()); // 30 37 console.log(p.age); // 28通過一個立即執行的匿名函數來包裹即可實現。
模塊模式
模塊模式可以實現對象的私有屬性和方法,如下:
1 var instance = function(){ 2 var name = "Han Meimei"; 3 4 function foo(){ 5 console.log("call private function"); 6 } 7 8 return { 9 setName: function(value) { 10 name = value; 11 foo(); 12 }, 13 getName: function() { 14 return name; 15 } 16 }; 17 }(); 18 19 instance.name = "Li Lei"; 20 console.log(instance.getName()); // Han Meimei 21 instance.setName("Uncle Wang"); 22 console.log(instance.getName()); // Uncle Wang 23 console.log(instance.name); // Li Lei當然,如果需要創建指定類型的實例,可以使用下面的代碼:
1 var instance = function(){ 2 var name = "Han Meimei"; 3 4 function foo(){ 5 console.log("call private function"); 6 } 7 8 // 這里可以創建指定類型的實例 9 var obj = new Object(); 10 11 // 添加方法 12 obj.setName = function(value) { 13 name = value; 14 foo(); 15 }; 16 obj.getName = function() { 17 return name; 18 }; 19 20 return obj; 21 }(); 22 23 instance.name = "Li Lei"; 24 console.log(instance.getName()); // Han Meimei 25 instance.setName("Uncle Wang"); 26 console.log(instance.getName()); // Uncle Wang 27 console.log(instance.name); // Li Lei?
總結
以上是生活随笔為你收集整理的HTML5学习笔记(十八):闭包的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jQuery左右选择框
- 下一篇: Chapter 3 Phenomenon