Contents
JavaScript function currying
1. Definition:
Currying is a technique that transforms a function that accepts multiple parameters into a function that accepts a single parameter (the first parameter of the original function), and returns a new function that accepts the remaining parameters and returns a result.
Explain with a simple example:
function add (a, b) { return a + b } add( 1 , 2 ); // 3
Convert function add into curried function _add :
function _add (a){ return function (b){ return a + b } } _add ( 1 )( 2 ); // 3
Function add and function _add are equivalent.
_add is able to handle all remaining parameters of add , so currying is also called partial evaluation .
In fact, the two parameters a and b of the add function are changed into using a function to receive a and then returning a function to process the b parameter.
Now the idea should be clearer: only pass a part of the parameters to the function to call, and let it return a function to handle the remaining parameters.
2. The role of currying function
1. Parameter reuse
Case: splicing address
Follow common ideas to splice an address
// Splicing address function getUrl ( protocol, hostname, pathname ) { return ` ${protocol} ${hostname} ${pathname} ` ; } const url1 = getUrl ( 'https://' , 'www.baidu.com' , '/hasa' ); const url2 = getUrl ( 'https://' , 'www.zhihu.com' , '/saandsa' ); const url3 = getUrl ( 'https://' , 'www.segmentfault.com' , '/hasak' ); console .log ( url1 , url2, url3)
Each time you call the getUrl parameter, you must repeatedly pass in the parameter ‘https://’.
After currying and encapsulating:
function curry ( protocol ) { return function ( hostname, pathname ) { return ` ${protocol} ${hostname} ${pathname} ` ; } } const url_curry = curry ( 'https://' ); const url1 = url_curry ( 'www.baidu.com' , '/hasa' ); const url2 = url_curry ( 'www.zhihu.com' , '/saandsa' ); const url3 = url_curry ( 'www.segmentfault.com' , '/hasak' ); console .log ( url1 , url2, url3)
It is obvious that after currying and encapsulation, when the address is spliced later, the number of parameters is reduced and the code duplication rate is reduced.
2. Confirm in advance/return in advance
Case: Listening method compatible with IE browser events ( IE is dead )
traditional method:
/* * @param element Object DOM element object * @param type String event type * @param listener Function event handling function * @param useCapture Boolean whether to capture */ var addEvent = function (element, type, listener, useCapture) { if (window.addEventListener) { element.addEventListener(type, function (e) { listener.call(element, e) }, useCapture) } else { element.attachEvent( 'on' + type, function (e) { listener.call(element, e) }) } }
The flaw is that you need to make a new judgment every time you bind an event to a DOM element. In fact, as soon as the browser has determined the event monitoring web page, you can know which listening method the browser needs. So we can make the judgment only executed once.
After currying and encapsulating:
var addEvent = ( function () { if (window.addEventListener) { return function (element, type , listener, useCapture) { // return function element.addEventListener( type , function () { listener.call(element) }, useCapture) } } else { return function (element, type , listener) { element.attachEvent( 'on ' + type , function () { listener.call(element) }) } } })() addEvent(element, "click", listener)
Execute the function immediately, and even if multiple events are triggered, only one if conditional judgment will be triggered.
3. Delayed execution
Case: fishing statistics weight
traditional method:
let fishWeight = 0 ; const addWeight = function ( weight ) { fishWeight += weight; }; addWeight ( 2.3 ); addWeight ( 6.5 ); addWeight ( 1.2 ); addWeight ( 2.5 ); console .log ( fishWeight);
Each time the addWeight method is executed, the weights of the fish are added.
After currying and encapsulating:
function curryWeight ( fn ) { let _fishWeight = []; return function () { if ( arguments . length === 0 ) { return fn. apply ( null , _fishWeight); } else { _fishWeight = _fishWeight. concat ([... arguments ]); } } } function addWeight () { let fishWeight = 0 ; for ( let i = 0 , len = arguments . length ; i < len; i++) { fishWeight += arguments [i]; } return fishWeight; } const _addWeight = curryWeight (addWeight); _addWeight ( 6.5 ); _addWeight ( 1.2 ); _addWeight ( 2.3 ); _addWeight ( 2.5 ); console . log ( _addWeight ())
When the _addWeight method is executed , the weights of the fish are not added, and then the sum is only done when _addWeight() is executed for the last time. The effect of delayed execution of the addWeight method is achieved .
—– Extension: Sort the fish by weight —–
function curryWeight ( fn ) { let _fishWeight = []; return function () { if ( arguments . length === 0 ) { return fn. apply ( null , _fishWeight); } else { _fishWeight = _fishWeight. concat ([... arguments ]); } } } function sortWeight () { return [... arguments ]. sort ( ( a, b ) => a - b); } const _addWeight = curryWeight (sortWeight); _addWeight ( 6.5 ); _addWeight ( 1.2 ); _addWeight ( 2.3 ); _addWeight ( 2.5 ); console . log ( _addWeight ())
It’s very simple, just modify the addWeight function.
3. Implementation of Currying Function
Done1:
step1: Implement a function add(1)(2)
function add ( num ){ let sum = num; return function ( num ){ return sum +num } } console .log (add( 1 ) ( 2 ));
step2: How to implement add(1)(2)(3)?
It cannot be nested infinitely, so return a function name and determine whether there are any parameters passed in inside the function.
function add ( num ){ let sum =num; return adds = function ( num1 ){ if (num1) { sum = sum +num1; return adds } return sum } } console .log (add( 1 ) ( 2 )( 3 )());
step3: What if you implement an add(1,2,3)(2,1)(3)(2,1,3)?
Because the parameters passed in each call are now of variable length. Therefore, the formal parameters cannot be written directly.
function add () { // Collect parameters let params = [... arguments ]; const getParams = function () { // When the parameter passed in is empty, traverse and sum the parameter array. if ([... arguments ]. length === 0 ) return params. reduce ( ( pre, next ) => pre + next, 0 ); // Collect parameters params. push (... arguments ); return getParams ; } // Repeat the call until the incoming parameters are empty return getParams; } let a = add ( 1 , 2 )( 2 , 1 )( 3 )(); console . log (a);
Done2:
function curry () { let args = [... arguments ]; let inner = function () { args. push (... arguments ); return inner; } // Core content: implicit conversion, calling the internal toString inner. toString = function () { return args. reduce ( function ( pre, next ) { return pre + next; }) } return inner; } const result = curry ( 1 )( 2 ); console . log (+result);
Regarding implicit conversion
1. In JavaScript, both the toString() method and the valueOf() method can be rewritten, and will be automatically called if used to operate the JavaScript parser. Therefore, rewriting toString at the end of the currying function is just enough to execute it after collecting all parameters.
2. Posts about JavaScript implicit calls
4. Summary of Currying
The core idea of function currying is to collect parameters , which need to rely on parameters and recursion. By splitting parameters, a multi-parameter function method is called to reduce code redundancy and increase readability.
advantage:
- After currying, we have not lost any parameters: log can still be called normally;
- We can easily generate partial functions, such as those used to generate today’s log;
- Single entrance;
- Easy to test and reuse.
shortcoming
- There are many nested functions;
- Occupies memory and may cause memory leaks (because it is essentially implemented with closures);
- Poor efficiency (because of the use of recursion);
- Variable access is slow and accessibility is poor (because of the use of arguments);
Application scenarios:
- Reduce repeated transmission of unchanged parameters;
- Pass curried callback parameters to other functions.