JS偏函数、组合函数、缓存函数

一、JavaScript代码

[附件]/functools.js

 
  
  1. (function( window ) { 
  2. "use strict"
  3.  
  4. var functools = window.functools || {}; 
  5.  
  6. /** 
  7.  * partial function 
  8.  */ 
  9. functools.curry = function( f, ctx ) { 
  10.     var args = Array.prototype.slice.call( arguments, 2 ); 
  11.     return function() { 
  12.         var newArgs = args.concat(Array.prototype.slice.call( arguments )); 
  13.         return f.apply( ctx, newArgs ); 
  14.     } 
  15.  
  16. /** 
  17.  * right-left partial function 
  18.  */ 
  19. functools.rcurry = function( f, ctx ) { 
  20.     var args = Array.prototype.slice.call( arguments, 2 ); 
  21.     return function() { 
  22.         var newArgs = Array.prototype.slice.call( arguments ).concat( args ); 
  23.         return f.apply( ctx, newArgs ); 
  24.     } 
  25.  
  26. /** 
  27.  * composite function 
  28.  * @description `compose(f, g, x)(y) = f(g(y), x)` 
  29.  */ 
  30. functools.compose = function( f, g, f_ctx, g_ctx ) { 
  31.     var args_for_f = Array.prototype.slice.call( arguments, 4 ); 
  32.     return function() { 
  33.         var args_for_g = Array.prototype.slice.call( arguments ); 
  34.         var mid = g.apply( g_ctx, args_for_g ); 
  35.         args_for_f.unshift(mid); 
  36.         return f.apply( f_ctx, args_for_f); 
  37.     } 
  38.  
  39. /** 
  40.  * Memoizer, and allows multiple arguments. 
  41.  * @description Recursion needs to override function literal.  
  42.  *              e.g. `func = memoize( func, null )` 
  43.  */ 
  44. functools.memoize = function( f, ctx ) { 
  45.     var memo = {}; 
  46.     return function() { 
  47.         var key = Array.prototype.join.call(arguments, "_"); 
  48.         var res = memo[ key ]; 
  49.         if ( res === undefined ) { 
  50.             res = f.apply( ctx, arguments ); 
  51.             memo[ key ] = res; 
  52.         } 
  53.         return res; 
  54.     } 
  55.  
  56. /** 
  57.  * Return the result and runtime of the function. 
  58.  */ 
  59. functools.timeit = function( f, ctx ) { 
  60.     var args = Array.prototype.slice.call( arguments, 2 ); 
  61.     var started = +new Date(); 
  62.     var result = f.apply( ctx, args ); 
  63.     var runtime = +new Date() - started; 
  64.     return { 
  65.         result: result, 
  66.         runtime: runtime 
  67.     } 
  68.  
  69. function extend( a, b ) { 
  70.     for ( var prop in b ) { 
  71.         if ( b[ prop ] === undefined ) { 
  72.             delete a[ prop ]; 
  73.         // Avoid "Member not found" error in IE8 caused by setting window.constructor 
  74.         } else if ( prop !== "constructor" || a !== window ) { 
  75.             a[ prop ] = b[ prop ]; 
  76.         } 
  77.     } 
  78.     return a; 
  79.  
  80. if ( window.functools === undefined ) { 
  81.     extend( window, functools ); 
  82.     window.functools = functools; 
  83.  
  84. // get at whatever the global object is, like window in browsers 
  85. }( (function() {return this;}.call()) )); 


二、QUnit测试代码

[附件]/functools.html

 
  
  1. <!DOCTYPE html> 
  2. <html> 
  3. <head> 
  4.     <meta charset="utf-8"> 
  5.     <title>functools test case</title> 
  6.     <link rel="stylesheet" href="./qunit/qunit-1.11.0.css"> 
  7. </head> 
  8. <body> 
  9.     <div id="qunit"></div> 
  10.     <div id="qunit-fixture"></div> 
  11.     <script src="./qunit/qunit-1.11.0.js"></script> 
  12.     <script src="./functools.js"></script> 
  13.     <script> 
  14.         test( "curry test case", function() { 
  15.             function sum() { 
  16.                 var sum = 0
  17.                 for ( var i = 0; i < arguments.length; i++ ) { 
  18.                     sum +=  arguments[i]; 
  19.                 } 
  20.                 return sum; 
  21.             } 
  22.             var sumBase10 = curry( sum, null, 1, 2, 3, 4 ); 
  23.  
  24.             var value = sumBase10( 20, 30, 40 ); 
  25.             equal( value, 100, "We expect value to be 100" ); 
  26.         }); 
  27.  
  28.         test( "rcurry test case", function() { 
  29.             function between( value, low, high, allowEqual ) { 
  30.                 if ( value > low && value < high ) { 
  31.                     return true; 
  32.                 } 
  33.                 if ( allowEqual && ( value == low || value == high) ) { 
  34.                     return true; 
  35.                 } 
  36.                 return false; 
  37.             } 
  38.             var betweenClosed = rcurry( between, null, true ), 
  39.                 between10and20 = rcurry( betweenClosed, null, 10, 20 ); 
  40.  
  41.             ok( between10and20( 10 ) , "10 is between 10 and 20" ); 
  42.             ok( between10and20( 20 ) , "20 is between 10 and 20" ); 
  43.             equal( between10and20( 9 ), false, "9 is not between 10 and 20" ); 
  44.             equal( between10and20( 21 ), false, "21 is not between 10 and 20" ); 
  45.         }); 
  46.  
  47.         test( "compose test case", function() { 
  48.             /* only one argument */ 
  49.             function trim( s ) { 
  50.                 return s.replace( /(^\s+)|(\s+$)/g, "" ); 
  51.             } 
  52.             function upper( s ) { 
  53.                 return s.toUpperCase(); 
  54.             } 
  55.             var trimUpper = compose( upper, trim, null, null ); 
  56.  
  57.             var value = trimUpper( "  trim and upper  " ); 
  58.             equal( value, "TRIM AND UPPER", "trimUpper test case" ); 
  59.  
  60.             /* multiple arguments */ 
  61.             function f( x, y ) { 
  62.                 return x + y; 
  63.             } 
  64.             var g = f
  65.             var fg = compose( f, g, null, null, "F" ); 
  66.  
  67.             equal( fg( "X", "Y" ), "XYF", "We expect value to be XYF" ); 
  68.         }); 
  69.  
  70.         test( "memoize test case", function() { 
  71.             /* only one argument */ 
  72.             function fib( n ) { 
  73.                 if ( n == 0 ) return 0; 
  74.                 if ( n == 1 ) return 1; 
  75.                 return fib( n - 1) + fib( n - 2 ); 
  76.             } 
  77.             var res_old = timeit( fib, null, 30 ); 
  78.             fib = memoize( fib, null ); // override 
  79.             var res_new = timeit( fib, null, 30 ); 
  80.  
  81.             equal( res_new.result, res_old.result, 
  82.                 [ "It's expected value.", res_old.runtime, res_new.runtime ].join("|") 
  83.             ); 
  84.  
  85.             /* multiple arguments */ 
  86.             function func( a, b ) { 
  87.                 func.counter++; 
  88.                 if ( a == 1 ) return 1; 
  89.                 return a + b + func( a - 1, b - 1 ); 
  90.             } 
  91.             func = memoize( func, null ); // override 
  92.  
  93.             func.counter = 0; // declare 
  94.             var a = func( 5, 4 ), // = 25 
  95.                 a_cnt = func.counter; // = 5 
  96.             func.counter = 0; // reset 
  97.             var b = func( 6, 5 ), // 6 + 5 + func( 5, 4 ) << cache 
  98.                 b_cnt = func.counter; // = 1 
  99.  
  100.             equal( b, 36, [ "It's expected value.", a_cnt, b_cnt ].join("|") ); 
  101.         }); 
  102.     </script> 
  103. </body> 
  104. </html> 

ps:附件解压,打开functools.html即可测试。