JavaScript設計模式與開發實踐之閉包和高階函數

閉包

變量的作用域

如果該變量前面沒有帶上關鍵字 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 );

最後,給大家推薦一個前端學習進階內推交流羣685910553前端資料分享),不管你在地球哪個方位,
不管你參加工作幾年都歡迎你的入駐!(羣內會定期免費提供一些羣主收藏的免費學習書籍資料以及整理好的面試題和答案文檔!)

如果您對這個文章有任何異議,那麼請在文章評論處寫上你的評論。

如果您覺得這個文章有意思,那麼請分享並轉發,或者也可以關注一下表示您對我們文章的認可與鼓勵。

願大家都能在編程這條路,越走越遠。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章