閉包
-
變數的作用域
- 如果該變數前面沒有帶上關鍵字 var,這個變數就會成為全域性變數
- 用 var 關鍵字在函式中宣告變數,這時候的變數即是區域性變數,只有在該函式內部才能訪問到這個變數,在函式外面是訪問不到的
var func = function(){ var a = 1; alert ( a ); // 輸出: 1 }; func(); alert ( a ); // 輸出:Uncaught ReferenceError: a is not defined 複製程式碼
-
變數的生存週期
- 對於全域性變數來說,全域性變數的生存週期當然是永久的,除非我們主動銷燬這個全域性變數。
- 而對於在函式內用 var 關鍵字宣告的區域性變數來說,當退出函式時,它們都會隨著函式呼叫的結束而被銷燬
var func = function(){ var a = 1; // 退出函式後區域性變數 a 將被銷燬 alert ( a ); }; func(); 複製程式碼
- 閉包可以延續變數的生存週期
var func = function(){ var a = 1; return function(){ a++; alert ( a ); } }; var f = func(); f(); // 輸出:2 f(); // 輸出:3 f(); // 輸出:4 f(); // 輸出:5 複製程式碼
-
閉包的更多作用
- 封裝變數
var mult = (function(){ var cache = {}; var calculate = function(){ // 封閉 calculate 函式 var a = 1; for(var i = 0, l = arguments.length; i < l; i++ ){ a = a * arguments[i]; }; return a; } return function(){ var args = Array.prototype.join.call( arguments, ',' ); if ( args in cache ){ return cache[ args ]; } return cache[ args ] = calculate.apply( null, arguments ); } })(); alert ( mult( 1,2,3 ) ); // 輸出:6 alert ( mult( 1,2,3 ) ); // 輸出:6 複製程式碼
- 延續區域性變數的壽命
-
閉包實現命令模式
<html>
<body>
<button id="execute">點選我執行命令</button>
<button id="undo">點選我執行命令</button>
<script>
var Tv = {
open: function(){
console.log( '開啟電視機' );
},
close: function(){
console.log( '關上電視機' );
}
};
var createCommand = function( receiver ){
var execute = function(){
return receiver.open();// 執行命令,開啟電視機
}
var undo = function(){
return receiver.close();// 執行命令,關閉電視機
}
return {
execute: execute,
undo: undo
}
};
var setCommand = function( command ){
document.getElementById( 'execute' ).onclick = function(){
command.execute(); // 輸出:開啟電視機
}
document.getElementById( 'undo' ).onclick = function(){
command.undo(); // 輸出:關閉電視機
}
};
setCommand(createCommand(Tv));
</script>
</body>
</html>
複製程式碼
-
閉包與記憶體管理
- 可能會引起記憶體洩漏
如果兩個物件之間形成了迴圈引用,那麼這兩個物件都無法被回收,但迴圈引用造成的記憶體洩露在本質上也不是閉包造成的
高階函式
高階函式是指至少滿足下列條件之一的函式
- 函式可以作為引數被傳遞
- 函式可以作為返回值輸出
-
函式作為引數傳遞
- 回撥函式
- 非同步請求
var getUserInfo = function( userId, callback ){ $.ajax( 'http://xxx.com/getUserInfo?' + userId, function( data ){ if ( typeof callback === 'function' ){ callback( data ); } }); } getUserInfo( 13157, function( data ){ alert ( data.userName ); }); 複製程式碼
- 委託
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'; }); 複製程式碼
- 回撥函式
-
函式作為返回值輸出
- 判斷資料的型別
var Type = {}; for ( var i = 0, type; type = [ 'String', 'Array', 'Number' ][ i++ ]; ){ (function( type ){ Type[ 'is' + type ] = function( obj ){ return Object.prototype.toString.call( obj ) === '[object '+ type +']'; } })(type) }; Type.isArray( [] ); // 輸出:true Type.isString( "str" ); // 輸出:true 複製程式碼
- getSingle
var getSingle = function ( fn ) { var ret; return function () { return ret || ( ret = fn.apply( this, arguments ) ); }; }; var getScript = getSingle(function(){ return document.createElement( 'script' ); }); var script1 = getScript(); var script2 = getScript(); alert ( script1 === script2 ); // 輸出:true 複製程式碼
-
高階函式實現AOP (面向切面程式設計 )
AOP(面向切面程式設計)的主要作用是把一些跟核心業務邏輯模組無關的功能抽離出來,這些 跟業務邏輯無關的功能通常包括日誌統計、安全控制、異常處理等。把這些功能抽離出來之後, 再通過“動態織入”的方式摻入業務邏輯模組中。
Function.prototype.before = function( beforefn ){
var __self = this; // 儲存原函式的引用
return function(){ // 返回包含了原函式和新函式的"代理"函式
beforefn.apply( this, arguments );
return __self.apply( this, arguments );
}
};
Function.prototype.after = function( afterfn ){
var __self = this;
return function(){
// 執行新函式,修正 this // 執行原函式
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(); // 1 2 3
複製程式碼
-
高階函式的其他應用
- currying 又稱部分求值。一個 currying 的函式首先會接受一些引數,接受了這些引數之後, 該函式並不會立即求值,而是繼續返回另外一個函式,剛才傳入的引數在函式形成的閉包中被保 存起來。待到函式被真正需要求值的時候,之前傳入的所有引數都會被一次性用於求值。
var currying = function( fn ){ var args = []; return function(){ if ( arguments.length === 0 ){ return fn.apply( this, args ); }else{ [].push.apply( args, arguments ); return arguments.callee; } } }; var cost = (function(){ var money = 0; return function(){ for ( var i = 0, l = arguments.length; i < l; i++ ){ money += arguments[ i ]; } return money; } })(); var cost = currying( cost ); // 轉化成 currying 函式 cost( 100 ); // 未真正求值 cost( 200 ); // 未真正求值 cost( 300 );// 未真正求值 alert ( cost() ); // 求值並輸出:600 複製程式碼
- uncurrying
Function.prototype.uncurrying = function () { var self = this; // self 此時是 Array.prototype.push return function() { var obj = Array.prototype.shift.call( arguments );// 相當於 Array.prototype.push.apply( obj, 2 ) }; }; }; var push = Array.prototype.push.uncurrying(); var obj = { "length": 1, "0": 1 }; push( obj, 2 ); console.log( obj );// 輸出:{0: 1, 1: 2, length: 2} 複製程式碼
- 函式節流:將即將被執行的函式用 setTimeout 延遲一段時間執行。如果該次延遲執行還沒有完成,則忽略接下來呼叫該函式的請求
var throttle = function ( fn, interval ) { var __self = fn, // 儲存需要被延遲執行的函式引用 timer, // 定時器 firstTime = true; // 是否是第一次呼叫 return function () { var args = arguments, __me = this; if ( firstTime ) { // 如果是第一次呼叫,不需延遲執行 __self.apply(__me, args); return firstTime = false; } if ( timer ) { // 如果定時器還在,說明前一次延遲執行還沒有完成 return false; } timer = setTimeout(function () { // 延遲一段時間執行 clearTimeout(timer); timer = null; __self.apply(__me, args); }, interval || 500 ); }; }; window.onresize = throttle(function(){ console.log( 1 ); }, 500 ); 複製程式碼