《JavaScript設計模式與開發實踐》基礎篇(2)—— 閉包和高階函式

嗨呀豆豆呢發表於2018-12-10

閉包

  • 變數的作用域

    • 如果該變數前面沒有帶上關鍵字 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 );
    複製程式碼

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全

相關文章