許多模式都可以使用閉包和高階函數來實現
3.1 閉包
閉包的形成與變量的作用域以及變量的生命週期密切相關
3.1.1 變量的作用域
變量的作用域,就是指變量的有效範圍。我們最常談到的是在函數中聲明的變量作用域。
- 在 JavaScript 中,函數可以用來創造函數作用域。此時的函數像一層半透明的玻璃,在函數裏面可以看到外面的變量,而在函數外面則無法看到函數裏面的變量。
- 這是因爲當在函數中搜索一個變量的時候,如果該函數內並沒有聲明這個變量,那麼此次搜索的過程會隨着代碼執行環境創建的作用域鏈往外層逐層搜索,一直搜索到全局對象爲止。
- 變量的搜索是從內到外而非從外到內的。
3.1.2 變量的生存週期
- 全局變量生存週期是永久的,除非我們主動銷燬
- 而對於var聲明的局部變量,當退出函數時,它們會隨着函數調用的結束而被銷燬。
example 1:遍歷綁定點擊事件
for(var i = 0;i<;i++){
nodeArr[i].oncick = function(){
alert(i)
}
}
點擊事件爲異步事件,在點擊時循環已完畢,故每一個點擊事件都會得到i = 5
method:使用閉包把每次循環的i值封閉起來
for (var i =0;i<5;i++){
(nodeArr[i].onclick = funcioton(){
alert(i)
})(i)
}
也可使用es6 let。let聲明封閉作用域
3.1.3 閉包的更多作用
-
封裝變量:閉包可以幫助把一些不需要暴露在全局的變量封裝成”私有變量“
example 可緩存計算函數
var mult = function(){ var a = 1 for (var i = 0, l=arguments.length;i<;i++){ a = a*arguments[s] } return a }
上面爲最簡易進行參數乘積的函數,接下來進行對代碼提煉封裝,以及閉包封裝緩存
var mult = (function(){ var cahe = {} //封裝功能函數,有助於代碼複用性,用戶計算不定個數入參乘積 var calculate = function(){ var a = 1 for (var i = 0,l=arguments.length;i<l;i++){ a = a*arguments[i] } return a } return function(){ // 將入參通過join連接成字符串作爲object key存入緩存中,key部分可以擴展如 1,3,5 與3,1,5爲同一結果 var args = [].join.call(arguments,',') //如果緩存過直接返回值 if(args in cache){ return cache[args] } //將未緩存加入緩存並返回 return cache[args] = calculate.apply(null,arguments) } })()
-
延續局部變量的壽命
img 對象經常用於進行數據上報,如下所示:
var report = function( src ){
var img = new Image();
img.src = src;
};
report( 'http://xxx.com/getUserInfo' );
在這些瀏覽器下使用 report 函數進行數據上報會丟失 30%左右的數據,也就是說,report 函數並不是每一次都成功發起了 HTTP 請求。丟失數據的原因是 img 是 report 函數中的局部變量,當 report 函數的調用結束後,img 局部變量隨即被銷燬,而此時或許還沒來得及發出 HTTP 請求,所以此次請求就會丟失掉。
現在我們把 img 變量用閉包封閉起來,便能解決請求丟失的問題:
var report = (function(){
var imgs = [];
return function( src ){
var img = new Image();
imgs.push( img );
img.src = src;
}
})();
3.1.4 閉包和麪向對象設計
- 過程與數據的結合是形容面向對象中的“對象”時經常使用的表達。
- 對象以方法的形式包含了過程,而閉包則是在過程中以環境的形式包含了數據。
3.1.5 閉包與內存管理
- 閉包導致局部變量一直生存下去,使一些數據無法被及時銷燬
- 如果需要回收變量,可以手動把這些變量設爲null
- 使用閉包的同時容易形成循環引用
- 在ie中BOM 和 DOM 中的對象是使用 C++以 COM 對象的方式實現,而com對象的垃圾回收機制採用的使引用計數策略。如果兩個對象之間形成了循環引用,那麼這兩個對象都無法被回收。
- 垃圾收集器會將值爲null的變量刪除並回收他們佔用的內存
- 將變量設置爲null意味着切斷變量與它此前引用的值之間的連接
3.2 高階函數
高階函數至少滿足以下一個條件
- 函數可以作爲參數被傳遞
- 函數可以作爲返回值輸出
3.2.1 函數作爲參數傳遞
-
回調函數
- 在ajax異步請求中,回調函數使用頻繁。當我們想在ajax請求返回後做一些事情,最常見的方法使把callback函數當作參數傳入發起ajax請求的方法中
-
用於將邏輯代碼抽離
var appendDiv = function( callback ){ for ( var i = 0; i < 100; i++ ){ var div = document.createElement( 'div' ); div.innerHTML = i; document.body.appendChild( div ); if ( typeof callback === 'function' ){ callback( div ); } } }; appendDiv(function( node ){ node.style.display = 'none'; });
可以看到,隱藏節點的請求實際上是由客戶發起的,但是客戶並不知道節點什麼時候會創建好,於是把隱藏節點的邏輯放在回調函數中,“委託”給 appendDiv 方法。appendDiv 方法當然知道節點什麼時候創建好,所以在節點創建好的時候,appendDiv 會執行之前客戶傳入的回調函數。
3.Array.prototype.sort方法傳入一個回調作爲排序的規則,使sort成爲一個非常靈活的方法
3.2.2 函數作爲返回值輸出
將函數作爲返回值出,更能體現函數式編程的巧妙。讓函數返回一個可執行的函數,意味着運算過程使可延續的
-
判斷數據類型
用 Object.prototype.toString 來計算。Object.prototype.toString.call( obj )返回一個字符串,比如 Object.prototype.toString.call( [1,2,3] ) 總是返回 “[object Array]” , 而Object.prototype.toString.call( “str”)總是返回"[object String]"。
//通過return一個函數接收外部函數傳入的參數,進行參數的嵌套, var isType = function( type ){ return function( obj ){ return Object.prototype.toString.call( obj ) === '[object '+ type +']'; } }; //接收第一個參數賦值不同類別的判斷器 var isString = isType( 'String' ); var isArray = isType( 'Array' ); var isNumber = isType( 'Number' ); //接收第二個參數進行運算 console.log( isArray( [ 1, 2, 3 ] ) );
3.2.3 高階函數實現AOP
- AOP(面向切面編程)的主要作用是把一些跟核心業務邏輯模塊無關的功能抽離出來,這些跟業務邏輯無關的功能通常包括日誌統計、安全控制、異常處理等。
- 把這些功能抽離出來之後,再通過“動態織入”的方式摻入業務邏輯模塊中。
- 這樣做的好處首先是可以保持業務邏輯模塊的純淨和高內聚性,
- 其次是可以很方便地複用日誌統計等功能模塊。
- 通常js通過把一個函數”動態織入“另一個函數中
example
Function.prototype.before = function( beforefn ){
var __self = this; // 保存原函數的引用
return function(){ // 返回包含了原函數和新函數的"代理"函數
beforefn.apply( this, arguments ); // 執行新函數,修正 this
return __self.apply( this, arguments ); // 執行原函數
}
};
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
var ret = __self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
};
var func = function(){
console.log( 2 );
};
func = func.before(function(){
console.log( 1 );
}).after(function(){
console.log( 3 );
});
func();