Speaking of Javascript, it is impossible not to mention the function
, not only that, it is also considered as a brand face of this language. But not many people really understand each type of function
. Below will be specific types of function
that you may have used but do not really know about it.
Pure Function
How is a pure function
?
Functions that satisfy both of the following conditions are called pure function
:
- That function will always return the same result if we pass the same parameters.
- No side effects occurred while running that function.
Example 1:
1 2 3 4 | <span class="token keyword">function</span> <span class="token function">circleArea</span> <span class="token punctuation">(</span> <span class="token parameter">radius</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> radius <span class="token operator">*</span> radius <span class="token operator">*</span> <span class="token number">3.14</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
As you can see, if you pass a radius of equal value then the circleArea
function will always return the same result. And running this circleArea
function has no effect outside of this function, so this is a pure function.
Example 2:
The above counter()
function will return a different result each time, so this is not a pure function.
Example 3:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">let</span> femaleCounter <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> <span class="token keyword">let</span> maleCounter <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">isMale</span> <span class="token punctuation">(</span> <span class="token parameter">user</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> user <span class="token punctuation">.</span> sex <span class="token operator">=</span> <span class="token string">'man'</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> maleCounter <span class="token operator">++</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">false</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
In the above example, the isMale
function, although always returns the same result when passing the same parameter, but besides, this function also performs the counter
function (changes the value of a global variable – out of the scope of the function) that leads to this function being no longer pure.
What is a pure function used for?
Why do we differentiate between a pure function from another? Because pure functions have so many advantages, no, we can also use pure functions to improve the quality of our code:
1. pure function
much clearer and easier to read.
Every purely written function always has a specific function, always returning a clear result. This partly makes your code easier to understand, easier to apply.
2. The compiler can optimize more on pure functions
Try the example with the following code:
1 2 3 4 | <span class="token keyword">for</span> <span class="token punctuation">(</span> int i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">1000</span> <span class="token punctuation">;</span> i <span class="token operator">++</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token function">fun</span> <span class="token punctuation">(</span> <span class="token number">10</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
If fun
not a pure function, then fun(10)
would need to be executed 1000
times while the code above is running.
But if fun
was a pure function, the editor would be able to optimize the code at compile time, to put it simply, the compiled code would look like this:
1 2 3 4 5 | <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token function">fun</span> <span class="token punctuation">(</span> <span class="token number">10</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span> int i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">1000</span> <span class="token punctuation">;</span> i <span class="token operator">++</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> result <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
3. pure function
is easier to test
Testing with a pure function is quite simple, since the function is not affected by external values. You simply pass a value and add it to a specific formula, then the output must be the result, no matter how many times you run it, you will show the correct result.
Higher-Order Function
What is considered a higher-order function?
- Take one or more functions as an argument
- A return is a function as its result.
Using a higher-order function
increases the flexibility of your code, allowing us to write more concise and efficient code.
A simple example is that the input is 1 array A, the output is an array B twice the value of array A. With normal writing, the code in javascript will look like this:
1 2 3 4 5 6 | <span class="token keyword">const</span> arr1 <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token number">1</span> <span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> arr2 <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span> <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">;</span> i <span class="token operator"><</span> arr1 <span class="token punctuation">.</span> length <span class="token punctuation">;</span> i <span class="token operator">++</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> arr2 <span class="token punctuation">.</span> <span class="token function">push</span> <span class="token punctuation">(</span> arr1 <span class="token punctuation">[</span> i <span class="token punctuation">]</span> <span class="token operator">*</span> <span class="token number">2</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
In javascript there is a built-in function to do this, which is map()
The map (callback) method helps create a new array with the elements resulting from the execution of a function on each element of the called array.
1 2 3 | <span class="token keyword">const</span> arr1 <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token number">1</span> <span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> arr2 <span class="token operator">=</span> arr1 <span class="token punctuation">.</span> <span class="token function">map</span> <span class="token punctuation">(</span> <span class="token parameter">item</span> <span class="token operator">=></span> item <span class="token operator">*</span> <span class="token number">2</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
Function Caching
Let’s say we have a pure function that looks like this:
1 2 3 4 5 6 7 8 | <span class="token keyword">function</span> <span class="token function">computed</span> <span class="token punctuation">(</span> <span class="token parameter">str</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Giả sử thời gian chạy đoạn code nãy sẽ mất khoảng 2000s</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">'2000s have passed'</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// Giả sử kết quả trả về sẽ là string a result</span> <span class="token keyword">return</span> <span class="token string">'a result'</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
To speed up the processing of this function, we need to save the result of that function (cache that result). When it is last called, if the parameter is the same as the previous one, the function will not be executed, and the cache
results will be returned, is this applicable?
In fact, we can write a cached
function around the target_function
we want to cached
. This cached
function will take target_function
as an argument and return a new function. Inside this cached
function, we need to cache
the result of the previous target_function
run as an Object
or Map
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <span class="token keyword">function</span> <span class="token function">cached</span> <span class="token punctuation">(</span> <span class="token parameter">fn</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Tạo một Object lưu lại kết quả trả về sau khi chạy target_function.</span> <span class="token keyword">const</span> cache <span class="token operator">=</span> Object <span class="token punctuation">.</span> <span class="token function">create</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// Returns the wrapped function</span> <span class="token keyword">return</span> <span class="token keyword">function</span> <span class="token function">cachedFn</span> <span class="token punctuation">(</span> <span class="token parameter">str</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Nếu không có cache thì target_function sẽ được thực thi</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token operator">!</span> cache <span class="token punctuation">[</span> str <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> result <span class="token operator">=</span> <span class="token function">fn</span> <span class="token punctuation">(</span> str <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// Lưu kết quả đã chạy vào cache</span> cache <span class="token punctuation">[</span> str <span class="token punctuation">]</span> <span class="token operator">=</span> result <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> cache <span class="token punctuation">[</span> str <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
And this is the result:
Lazy Function
The body of some functions usually contains a number of conditional statements. And sometimes these statements are only executed once.
In cases like these we can improve the performance of the function, simply discarding the conditional statement, so that the function does not need to execute these statements on subsequent executions. The above action has turned that function into a lazy function
.
For example, you need to write a function foo
, which will always return a Date
object on the first run. Note is lần chạy đầu tiên
.
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">let</span> fooFirstExecutedDate <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">foo</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> fooFirstExecutedDate <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> fooFirstExecutedDate <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> fooFirstExecutedDate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> fooFirstExecutedDate <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
As you can see, every time you run foo
, you will always run the conditional statement. It doesn’t matter if this is just a simple conditional statement, but what if the if-else
is based on the results of many functions and takes quite a bit of runtime? In this case, use the lazy function
to optimize your code:
1 2 3 4 5 6 | <span class="token keyword">var</span> <span class="token function-variable function">foo</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> t <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function-variable function">foo</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> t <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">foo</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
After the first execution, we overwrite the original function with the new function. When this function is executed in the future, the conditional statement will no longer be executed. This will somewhat improve the performance of the code we write.
Let’s try with a more specific example:
When we add DOM events to the element, in order to be compatible with modern browsers and IE, we need to make judgment about the browser environment:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">function</span> <span class="token function">addEvent</span> <span class="token punctuation">(</span> <span class="token parameter">type <span class="token punctuation">,</span> el <span class="token punctuation">,</span> fn</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> window <span class="token punctuation">.</span> addEventListener <span class="token punctuation">)</span> <span class="token punctuation">{</span> el <span class="token punctuation">.</span> <span class="token function">addEventListener</span> <span class="token punctuation">(</span> type <span class="token punctuation">,</span> fn <span class="token punctuation">,</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> window <span class="token punctuation">.</span> attachEvent <span class="token punctuation">)</span> <span class="token punctuation">{</span> el <span class="token punctuation">.</span> <span class="token function">attachEvent</span> <span class="token punctuation">(</span> <span class="token string">'on'</span> <span class="token operator">+</span> type <span class="token punctuation">,</span> fn <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Every time we call the addEvent function, we must make the judgment. This is when we use the lazy function
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">function</span> <span class="token function">addEvent</span> <span class="token punctuation">(</span> <span class="token parameter">type <span class="token punctuation">,</span> el <span class="token punctuation">,</span> fn</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> window <span class="token punctuation">.</span> addEventListener <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function-variable function">addEvent</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token parameter">type <span class="token punctuation">,</span> el <span class="token punctuation">,</span> fn</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> el <span class="token punctuation">.</span> <span class="token function">addEventListener</span> <span class="token punctuation">(</span> type <span class="token punctuation">,</span> fn <span class="token punctuation">,</span> <span class="token boolean">false</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> window <span class="token punctuation">.</span> attachEvent <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function-variable function">addEvent</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span> <span class="token parameter">type <span class="token punctuation">,</span> el <span class="token punctuation">,</span> fn</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> el <span class="token punctuation">.</span> <span class="token function">attachEvent</span> <span class="token punctuation">(</span> <span class="token string">'on'</span> <span class="token operator">+</span> type <span class="token punctuation">,</span> fn <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token function">addEvent</span> <span class="token punctuation">(</span> type <span class="token punctuation">,</span> el <span class="token punctuation">,</span> fn <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Function Currying
Currying is a technique of evaluating a function with multiple arguments, into a series of functions with a single argument.
In other words, when a function, instead of taking all the arguments at once, takes the first argument and returns a new function that takes the second argument and returns a new function that takes the third argument, etc. until all the arguments are over.
Why use currying function
?
- Currying helps you avoid switching back and forth of the same variable
- It helps to generate a
higher-order functio
. It is extremely useful in event handling. - The small pieces can be easily configured and reused.
Let’s take a look at a simple example of the add
function that takes 3 arguments:
1 2 3 4 | <span class="token keyword">function</span> <span class="token function">add</span> <span class="token punctuation">(</span> <span class="token parameter">a <span class="token punctuation">,</span> b <span class="token punctuation">,</span> c</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> a <span class="token operator">+</span> b <span class="token operator">+</span> c <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
Try calling that function with less than 3, or more than 3 parameters passed:
1 2 3 4 | <span class="token function">add</span> <span class="token punctuation">(</span> <span class="token number">1</span> <span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">)</span> <span class="token operator">--</span> <span class="token operator">></span> <span class="token number">6</span> <span class="token function">add</span> <span class="token punctuation">(</span> <span class="token number">1</span> <span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">)</span> <span class="token operator">--</span> <span class="token operator">></span> <span class="token number">NaN</span> <span class="token function">add</span> <span class="token punctuation">(</span> <span class="token number">1</span> <span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">,</span> <span class="token number">4</span> <span class="token punctuation">)</span> <span class="token operator">--</span> <span class="token operator">></span> <span class="token number">6</span> |
How to convert an existing function into a function currying
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function curry(fn) { if (fn.length <= 1) return fn; const generator = (...args) => { if (fn.length === args.length) { return fn(...args); } else { return (...args2) => { return generator(...args, ...args2); } } } return generator; } |
Function Compose
Let’s say we now need to write a function that does this:
Input ‘bitfish’, return ‘HELLO, BITFISH’.
We could write the code like this:
1 2 3 4 5 6 | let toUpperCase = function(x) { return x.toUpperCase(); }; let hello = function(x) { return 'HELLO, ' + x; }; let greet = function(x){ return hello(toUpperCase(x)); }; |
As you can see, there are only 2 steps to be performed in the above example (1. converting to uppercase, 2. linking words), but if the problem has more requirements, then write a function nested processing functions like this fn3(fn2(fn1(fn0(x))))
To do this, we can write a specific compose
function for the above processor functions:
1 2 3 4 5 6 7 8 9 | let compose = function(f,g) { return function(x) { return f(g(x)); }; }; let greet = compose(hello, toUpperCase); greet('kevin'); |
Using compose
functions to combine two functions into a single function causes code to run from right to left, instead of inside and out, making the code much more readable.