HTML5学习笔记(十八):闭包
高階函數(shù)
JavaScript的函數(shù)其實(shí)都指向某個變量。既然變量可以指向函數(shù),函數(shù)的參數(shù)能接收變量,那么一個函數(shù)就可以接收另一個函數(shù)作為參數(shù),也可以返回一個函數(shù),這種函數(shù)就稱之為高階函數(shù)。
函數(shù)作為參數(shù)
示例如下:
1 function absAdd(x, y, f) { 2 return f(x) + f(y); 3 } 4 console.log(absAdd(-1, 2, Math.abs)); // 3函數(shù)作為參數(shù)的好處是我們可以通過修改參數(shù)就可以改變函數(shù)的行為。
函數(shù)作為返回值
示例如下:
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每次調(diào)用arrSum方法返回的都是一個新創(chuàng)建的函數(shù),所以判斷是不相等的。
返回函數(shù)時,可以決定在何時執(zhí)行該函數(shù)。
閉包
我們注意到上面例子里返回的函數(shù)在其定義內(nèi)部引用了局部變量arr,所以,當(dāng)一個函數(shù)返回了一個函數(shù)后,其內(nèi)部的局部變量還被新函數(shù)引用,這種情況就稱為閉包。
我們看下一個例子:
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這幾個數(shù)字,但是實(shí)際上打印的都是3,這是由于返回的函數(shù)保存的是變量i,實(shí)際上在循環(huán)之后變量i就變成了3,所以會出現(xiàn)這樣的情況。
立即執(zhí)行函數(shù)
那么如何才能打印出0,1,2這幾個數(shù)字呢,這里需要用到立即執(zhí)行函數(shù),立即執(zhí)行函數(shù)的意思是在定義好函數(shù)之后立即執(zhí)行,一般這樣的函數(shù)都是匿名函數(shù)。
格式如下:
1 (function (x) { 2 return x * x; 3 })(3);即用一個括號將函數(shù)包含,后面緊跟另一個括號進(jìn)行調(diào)用,同時可以進(jìn)行參數(shù)傳遞。
我們再看下面的例子:
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我們來看看這個例子,每個閉包函數(shù)實(shí)際上引用的是index參數(shù),而index參數(shù)是在立即執(zhí)行函數(shù)執(zhí)行時傳入的i,所以不存在改變的情況,就可以打印出對應(yīng)的索引值了。
關(guān)于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我們發(fā)現(xiàn)返回的是全局的name屬性,而不是我們期望的obj的name屬性。
我們知道每個函數(shù)在調(diào)用時都會獲得this及arguments兩個參數(shù),而this參數(shù)指向調(diào)用該方法的對象。
所以我們可以看一下第14和15行,調(diào)用obj.func時this是指向obj對象的,返回的函數(shù)實(shí)際上被綁定到全局對象上了,所以當(dāng)調(diào)用f函數(shù)時,實(shí)際上是window進(jìn)行調(diào)用的,所以拿到的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利用了閉包會持有調(diào)用鏈上的變量的原理即可。
私有屬性
在JavaScript中,沒有私有屬性的概念,所有屬性都是公開的。
但是有私有變量的概念,在函數(shù)中聲明的變量,都是該函數(shù)私有的,函數(shù)以外的地方不能訪問。
我們利用閉包和私有變量的特性可以創(chuàng)建出類似于私有屬性的變量。
1 function Person(name) { 2 // 私有變量 3 var age = 0; 4 // 私有函數(shù) 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我們會發(fā)現(xiàn),在函數(shù)內(nèi)部是直接使用age來訪問私有變量的,而如果是this.age則表示當(dāng)前對象的age公開屬性,所以p.age和p.getAge會取得不同的數(shù)值。外部是無法訪問到內(nèi)部變量age和參數(shù)name的。
使用立即執(zhí)行函數(shù)創(chuàng)建
我們發(fā)現(xiàn)上面的方法只能將所有代碼都寫在構(gòu)造函數(shù)中才能訪問到私有變量,其實(shí)還有一種寫法:
1 (function(){ 2 // 使用 var 定義的變量外部無法訪問 3 var _name; 4 var age = 0; 5 // 定義的函數(shù)外部無法訪問 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通過一個立即執(zhí)行的匿名函數(shù)來包裹即可實(shí)現(xiàn)。
模塊模式
模塊模式可以實(shí)現(xiàn)對象的私有屬性和方法,如下:
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當(dāng)然,如果需要創(chuàng)建指定類型的實(shí)例,可以使用下面的代碼:
1 var instance = function(){ 2 var name = "Han Meimei"; 3 4 function foo(){ 5 console.log("call private function"); 6 } 7 8 // 這里可以創(chuàng)建指定類型的實(shí)例 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?
總結(jié)
以上是生活随笔為你收集整理的HTML5学习笔记(十八):闭包的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jQuery左右选择框
- 下一篇: Chapter 3 Phenomenon