Function currification
The concept of corrilization: only a part of parameters is passed to the function to call it, and it returns a function to process the remaining parameters
Take an example:
var add = function(x) { return function(y) { return x + y; }; }; var increment = add(1); var addTen = add(10); increment(2); // 3 addTen(2); // 12
We define an add function that takes a parameter and returns a new function. After calling add, the returned function remembers the first parameter of add by means of closure.
Because it's a bit tedious to call it once, using a special curry help function makes it easier to define and call such functions.
Packaging of curry
// Preliminary packaging var currying = function(fn) { // args gets all the parameters in the first method var args = Array.prototype.slice.call(arguments, 1) return function() { // Merge all parameters in the following methods with args var newArgs = args.concat(Array.prototype.slice.call(arguments)) // Take the merged parameter as the parameter of fn through apply and execute return fn.apply(this, newArgs) } }
Here is the initial encapsulation. The initial parameters are saved through the closure, and then the rest of the arguments (the arguments here is an implicit parameter of the function itself, which is represented as an array of all parameters) are obtained for splicing. Finally, the functions that need currying are executed.
But it seems that there are still some defects. In fact, only one more parameter can be extended when it is returned. If it is currying(a)(b)(c), it seems that it does not support (multi parameter calls are not supported). In general, this situation will think of using recursion to encapsulate another layer.
// Support multi parameter transmission function progressCurrying(fn, args) { var _this = this var len = fn.length; var args = args || []; return function() { var _args = Array.prototype.slice.call(arguments); Array.prototype.push.apply(args, _args); // If the number of parameters is less than the original fn.length, it is called recursively to continue collecting parameters if (_args.length < len) { return progressCurrying.call(_this, fn, _args); } // After parameter collection, execute fn return fn.apply(this, _args); } }
Expand a classic interview question
// An add method is implemented to make the calculation result meet the following expectations: // add(1)(2)(3) = 6; // add(1, 2, 3)(4) = 10; // add(1)(2)(3)(4)(5) = 15; function add() { // At first execution, an array is defined to store all parameters var _args = Array.prototype.slice.call(arguments); // Declare a function internally, save ﹐ args and collect all parameter values with the characteristics of closure var _adder = function() { console.log("Received a()"); _args.push(...arguments); //... arguments represents all the remaining parameters in ES6, and accepts all the remaining parameters return _adder; //Then return the adder for the next recursion, taking the last received result as the first parameter, //Accept the next parameter, which is equivalent to accepting the parameter in the next () until the receiving is completed }; // Using the property of toString implicit conversion, when the last execution, implicit conversion is performed, and the final value return is calculated _adder.toString = function() { // return _args.reduce(function(a, b) { // return a + b; // }); return _args.reduce((a, b) => a + b); //reduce is an API in ES5, which can traverse every element of the array. a+b here is equivalent to all current parameters } return _adder; //This is equivalent to returning the final calculated result } var res = add(1, 2)(1)(2)(5); console.log(res + ''); console.log(add(1)(2)(3) + ''); /* Received a () Received a () Received a () 11 Received a () Received a () 6 */
Why implicit transformation
Because when we participate in other calculations, the function will call toString method by default, and directly convert the function body to string to participate in the calculation.
function fn() { return 20 } console.log(fn + 10); // Output function fn() {return 20} 10
We can rewrite the toString method of the function, let the function participate in the calculation, and output the result we want: just like the above we wrote _adder.toString =function() {},
function fn() { return 20; } fn.toString = function() { return 30 } console.log(fn + 10); // 40
In addition, when we rewrite the valueOf method of a function, we can also change the implicit conversion result of the function:
function fn() { return 20; } fn.valueOf = function() { return 60 } console.log(fn + 10); // 70
The advantages of function currification
Parameter reuse
// Normal regular verification string reg.test(txt) // After function encapsulation function check(reg, txt) { return reg.test(txt) } check(/\d+/g, 'test') //false check(/[a-z]+/g, 'test') //true // After Currying function curryingCheck(reg) { return function(txt) { return reg.test(txt) } } var hasNumber = curryingCheck(/\d+/g) var hasLetter = curryingCheck(/[a-z]+/g) hasNumber('test1') // true hasNumber('testtest') // false hasLetter('21212') // false
The above example is a regular check. Normally, it's OK to call the check function directly. But if I have many places to check whether there is a number, I need to reuse the first parameter reg, so that other places can directly call functions such as hasNumber and hasLetter, so that the parameters can be reused and called more easily.
Early confirmation
var on = function(element, event, handler) { if (document.addEventListener) { if (element && event && handler) { element.addEventListener(event, handler, false); } } else { if (element && event && handler) { element.attachEvent('on' + event, handler); } } } var on = (function() { if (document.addEventListener) { return function(element, event, handler) { if (element && event && handler) { element.addEventListener(event, handler, false); } }; } else { return function(element, event, handler) { if (element && event && handler) { element.attachEvent('on' + event, handler); } }; } })(); //Another way of writing might be easier to understand. The above is to determine the isSupport parameter first var on = function(isSupport, element, event, handler) { isSupport = isSupport || document.addEventListener; if (isSupport) { return element.addEventListener(event, handler, false); } else { return element.attachEvent('on' + event, handler); } }
In the process of project construction, it can be said that it is common to encapsulate some dom operations. However, the first writing method above is also quite common. But let's look at the second writing method, which is relative to the first one, which is self executing and then returning a new function. In fact, it is to determine which method will be taken before, so as to avoid judging every time.
Delayed operation
Function.prototype.bind = function (context) { var _this = this var args = Array.prototype.slice.call(arguments, 1) return function() { return _this.apply(context, args) } }
Like bind, which is often used in js, the implementation mechanism is Currying
Higher order functions in Js
JavaScript functions actually point to a variable. Since the variable can point to the function and the parameter of the function can receive the variable, then one function can receive another function as the parameter, which is called high-order function.
One of the simplest higher-order functions:
function add(x, y, f) { return f(x) + f(y); }
When we call add(-5, 6, Math.abs), parameters x, y and f receive - 5, 6 and function Math.abs respectively. According to the function definition, we can deduce the calculation process as follows:
x = -5; y = 6; f = Math.abs; f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11; return 11;
map/reduce
Because the map() method is defined in the JavaScript Array, we call the map() method of Array, pass in our own function, and get a new Array as the result:
'use strict'; function pow(x) { return x * x; } var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; var results = arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81] console.log(results);
Look at the use of reduce. reduce() of Array applies a function to [x1, x2, x3...] of the Array. This function must take two parameters. reduce() continues to accumulate the result and the next element of the sequence. The effect is as follows:
var arr = [1, 3, 5, 7, 9]; arr.reduce(function (x, y) { return x + y; }); //It can also be written as an arrow function arr.reduce((a,b)=> a+b);
Filter (filter)
Filter is also a common operation. It is used to filter out some elements of Array and return the remaining elements.
Like map(), Array's filter() takes a function. Unlike map(), filter() applies the incoming function to each element in turn, and then decides whether to keep or discard the element based on whether the return value is true or false.
To delete an empty string in an Array, you can write as follows:
var arr = ['A', '', 'B', null, undefined, 'C', ' ']; var r = arr.filter(function (s) { return s && s.trim(); // Note: there is no trim() method in versions below IE9 }); r; // ['A', 'B', 'C']
sort
Fortunately, the sort() method is also a higher-order function that can receive a comparison function to implement a custom sort.
To sort by number size, we can write as follows:
'use strict'; var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return -1; } if (x > y) { return 1; } return 0; }); console.log(arr); // [1, 2, 10, 20] //Sort containing letters var arr = ['Google', 'apple', 'Microsoft']; arr.sort(function (s1, s2) { x1 = s1.toUpperCase(); x2 = s2.toUpperCase(); if (x1 < x2) { return -1; } if (x1 > x2) { return 1; } return 0; }); // ['apple', 'Google', 'Microsoft']
Array
every
The every() method can determine whether all elements of the array meet the test conditions.
var arr = ['Apple', 'pear', 'orange']; console.log(arr.every(function (s) { return s.length > 0; })); // true, because each element satisfies s.length > 0 console.log(arr.every(function (s) { return s.toLowerCase() === s; })); // false, because not all elements are lowercase
find
The find() method is used to find the first element that meets the condition. If it is found, it returns this element. Otherwise, it returns undefined:
var arr = ['Apple', 'pear', 'orange']; console.log(arr.find(function (s) { return s.toLowerCase() === s; })); // 'pear' because pear is all lowercase console.log(arr.find(function (s) { return s.toUpperCase() === s; })); // undefined because there are not all uppercase elements
findIndex
findIndex() is similar to find(), and it is also the first element that meets the conditions. The difference is that findIndex() will return the index of this element. If it is not found, it will return - 1:
var arr = ['Apple', 'pear', 'orange']; console.log(arr.findIndex(function (s) { return s.toLowerCase() === s; })); // 1, because the index of 'pear' is 1 console.log(arr.findIndex(function (s) { return s.toUpperCase() === s; })); // -1
forEach
Like map(), foreach () applies each element in turn to the incoming function, but does not return a new array. forEach() is often used to traverse arrays, so the passed in function does not need to return a value:
var arr = ['Apple', 'pear', 'orange']; arr.forEach(console.log); // Print each element in turn
Generator function async function
Concept:
- Generator function is an asynchronous programming solution provided by ES6
- The generator is defined by function * (pay attention to the extra * sign); inside the function, use yield expression to define different internal states
- When the Generator function is called, it does not execute and returns not the result of the function, but a pointer object pointing to the internal state
- The next method of the traverser object must be called to move the pointer to the next state and output the returned result
- The next method returns an object. Its value attribute is the value hello of the current yield expression, and the value false of the done attribute indicates that the traversal has not ended (that is, return has not been encountered).
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator(); hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }
The next method can pass parameters:
function* gen(x){ const y = yield x + 6; return y; } const g = gen(1); g.next() // { value: 7, done: false } g.next(2) // { value: 2, done: true } // The next parameter is the return result of the asynchronous task in the previous phase
Asynchronous application
Because yield can interrupt the execution of code, it can help us control the execution order of asynchronous code.
For example, there are two asynchronous functions A and B, and the parameter of B is the return value of A, that is, if A does not finish execution, we cannot execute B.
function* effect() { const { param } = yield A(); const { result } = yield B(param); console.table(result); } //See the result const iterator = effect() iterator.next() iterator.next()
async and await
These two are equivalent to another semantic representation of generator function
- The async function replaces the asterisk (*) of the Generator function with Async
- Replace yield with await
The improvement of async function on Generator function is reflected in the following three points:
1. Built in actuator
That is to say, the execution of async functions, like ordinary functions, only needs one line. You don't need to call the next method like the Generator function to actually execute.
const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; //The call only needs one line of code, and has its own executor, and does not need next() asyncReadFile();
2. Better semantics
async and await are much clearer than asterisks and yield. async indicates that there is asynchronous operation in the function, and await indicates that the expression immediately following needs to wait for the result.
3. The return value is promise
The return value of the async function is the Promise object, which is much more convenient than the return value of the Generator function is the Iterator object. You can use the then method to specify the next operation.
async function getStockPriceByName(name) { const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; } getStockPriceByName('goog').then(function (result) { console.log(result); });
First, execute the getStockSymbol(name) function after the first wait; after getting the name symbol of the stock, pass the symbol to the getStockPrice(symbol) after the second wait as the parameter; finally, return the stock price stockPrice.
Reference resources
https://www.jianshu.com/p/2975c25e4d71
https://delaprada.com/2020/01/14/Generator%E5%87%BD%E6%95%B0-async%E5%87%BD%E6%95%B0/#more