What is declarative programming?
Before we get to the main content, what is declarative programming?
At its simplest, this means writing code declaratively:
For example, we use a for loop to iterate over an array, whereas in declarative programming we just call the array method forEach to iterate,
The processing logic written inside the for loop is also passed to the forEach method via a callback function variable,
ForEach calls the callback function each time it accesses an array element:
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
arr.forEach((i)=>console.log(i))
Copy the code
With forEach we can achieve the same goal with less code and more direct intent, which is the beauty of declarative programming.
Instead of telling the machine how to loop, how to process, we just tell the machine what to do.
How to handle arrays gracefully?
Array declarative methods
Array.prototype.forEach
Let’s start with the forEach we just used:
arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
Copy the code
ForEach accepts a callback function, which represents what to do and how to do it.
The callback function also takes three arguments, respectively
- The value currently traversed
- Index of the current array
- The current array reference
This covers most of our usage scenarios, of course it also accepts thisArg as the this point to which callback is called.
Parse forEach in depth
This is the forEach gasket (Polyfill) function given by MDN, and it is not difficult to see that it is essentially using the while loop to operate on the array.
Here’s how length >>> 0 works:
- Would like to
length
To an unsigned integer - All non-numeric values are converted to 0, including undefined, NaN, etc.
- All numbers greater than or equal to 0 are integers, and all numbers less than 0 are converted to unsigned numbers.
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this= =null) {
throw new TypeError(' this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeofcallback ! = ="function") {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = thisArg;
}
k = 0;
while (k < len) {
var kValue;
if (k inO) { kValue = O[k]; callback.call(T, kValue, k, O); } k++; }}; }Copy the code
This also means that our callback does not support asynchronous operations, and there are a few tricks we can use to do this, which we’ll cover later.
Array.prototype.map
var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])
Copy the code
The biggest difference between Map and forEach is that map concatenates the data returned by callback, returning a new array of results.
In-depth understanding of MAP
Only when you know the principle can you use it better. As always, let’s look at the spacer function of MAP on MDN
if (!Array.prototype.map) {
Array.prototype.map = function(callback/*, thisArg*/) {
var T, A, k;
if (this= =null) {
throw new TypeError('this is null or not defined');
}
var O = Object(this);
var len = O.length >>> 0;
if (typeofcallback ! = ='function') {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = arguments[1];
}
A = new Array(len);
k = 0;
while (k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
A[k] = mappedValue;
}
k++;
}
return A;
};
}
Copy the code
In essence, a map declares a new array and stores the value of each callback, even if the callback does not return, undefined or null will be added to the resulting array.
If we need only part of the array, use filter
Array.prototype.filter
var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
Copy the code
The filter method creates a new array containing all elements that the callback function returns true.
// As long as the element is greater than 2
arr.filter( a= > ( a > 2 )}
Copy the code
It will skip all array elements that result in filter.
In-depth understanding of filter
The internal implementation of filter is to check with an if-else and append to the result array if the callback is true.
if (!Array.prototype.filter){
Array.prototype.filter = function(func, thisArg) {
'use strict';
if(! ((typeof func === 'Function' || typeof func === 'function') && this))throw new TypeError(a);var len = this.length >>> 0,
res = new Array(len),
t = this, c = 0, i = -1;
if (thisArg === undefined) {while(++i ! == len){if (i in this) {if(func(t[i], i, t)){ res[c++] = t[i]; }}}}else{
while(++i ! == len){if (i in this) {if (func.call(thisArg, t[i], i, t)){
res[c++] = t[i];
}
}
}
}
res.length = c;
return res;
};
}
Copy the code
some, every
These two methods are somewhat similar, so I’ll introduce them together
-
The some method takes a callback function that returns a Boolean value
Stop traversal only if the first array element whose callback is true is encountered. Return false if all elements are false
An empty array returns false
-
The every method takes a callback function and returns a Boolean
Stop traversal only when the first array element whose callback is false is encountered. Returns true when all elements are true
An empty array returns true
Understand some and every
In essence, return interrupts the execution of the function, similar to break.
Both skip null values (undefined
), that is, references that are not assigned
Without further ado, let’s look at the implementation
-
some
if (!Array.prototype.some) { Array.prototype.some = function(fun/*, thisArg*/) { 'use strict'; if (this= =null) { throw new TypeError('Array.prototype.some called on null or undefined'); } if (typeoffun ! = ='function') { throw new TypeError(a); }var t = Object(this); var len = t.length >>> 0; var thisArg = arguments.length >= 2 ? arguments[1] : void 0; for (var i = 0; i < len; i++) { if (i in t && fun.call(thisArg, t[i], i, t)) { return true; }}return false; }; } Copy the code
-
every
if (!Array.prototype.every) { Array.prototype.every = function(callbackfn, thisArg) { 'use strict'; var T, k; if (this= =null) { throw new TypeError('this is null or not defined'); } var O = Object(this); var len = O.length >>> 0; if (typeofcallbackfn ! = ='function') { throw new TypeError(a); }if (arguments.length > 1) { T = thisArg; } k = 0; while (k < len) { var kValue; if (k in O) { kValue = O[k]; var testResult = callbackfn.call(T, kValue, k, O); if(! testResult) {return false; } } k++; } return true; }; } Copy the code
Array.prototype.reduce
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
Copy the code
This is perhaps the most difficult of all array methods to master. It takes a callback function and an initial value, and each subsequent execution depends on the value returned from the previous execution.
If no initialValue is provided, reduce executes the callback method starting at index 1, taking the value of the first index as the initialValue and skipping the first index. If initialValue is provided, start at index 0.
The callback function takes four arguments:
- Accumulator Total value, which is the sum of all previous values, is executed for the first time as the initial value of the parameter
- The current value currentValue
- Index Current index
- Array Array reference
Accumulator is equal to the return value of the previous callback function.
Each access to an array that is suitable for processing depends on the previous access, and of course Reduce accesses from beginning to end, while reduceRight accesses from end to head.
In-depth understanding of Reduce
If the array has only one element, it returns a unique value. If the array is empty and has no initial value, an error is reported.
if (!Array.prototype.reduce) {
Object.defineProperty(Array.prototype, 'reduce', {
value: function(callback /*, initialValue*/) {
if (this= = =null) {
throw new TypeError( 'Array.prototype.reduce ' +
'called on null or undefined' );
}
if (typeofcallback ! = ='function') {
throw new TypeError( callback +
' is not a function');
}
var o = Object(this);
var len = o.length >>> 0;
var k = 0;
var value;
// If the array is small or equal to 1, return a unique value or an error
if (arguments.length >= 2) {
value = arguments[1];
} else {
while(k < len && ! (kin o)) {
k++;
}
if (k >= len) {
throw new TypeError( 'Reduce of empty array ' +
'with no initial value' );
}
value = o[k++];
}
while (k < len) {
if (k in o) {
value = callback(value, o[k], k, o);
}
k++;
}
returnvalue; }}); }Copy the code
You have to know the tricks
Rational application method
First, I showed you each array method and its differences, and we need to maximize their power in different situations to reduce performance waste.
1. Judge the situation
First we need to determine what we want to return
Array methods | Returns the result |
---|---|
forEach | undefined |
some | boolean |
every | boolean |
filter | array |
map | array |
reducer | any |
Every and some, as well as forEach, can be used to traverse, but do not return a new array.
Every and some apply to arrays, return Boolean,
Map and filter are useful for generating results, returning arrays of results,
In general, forEach is used for traversal only.
2. Implement break or continue instead of for
If we only need to traverse, but we don’t need to traverse the array completely.
Use forEach and some or every to save money.
Some and every use the callback return to break the loop if we need it.
The callback return in forEach can be used to skip this call.
This serves the purpose of break and continue
3. Chain call
Both map and filter can return an array as a result. The difference is that filter can filter elements, whereas map cannot.
So we can process and filter elements by concatenating them to get the elements that we want.
// Get all numbers greater than 3, multiply by 2 and add
[1.2.3.4.5].filter(a= >a>3).map(a= >a*2).reduce((a,c) = >a+c, 0)
Copy the code
Use async/await in array methods
This is an important point, as you can see from all the method gaskets, the various declarative methods simply execute our callback function and return the result we need accordingly.
When we use Async/await functions in declarative methods, methods do not support await and instead directly access Async promise objects, so here are a few compatible method tricks
ForEach implements parallel and serial
The following code is intended to output 1, 4, 9 after 1s
But the end result is parallel output 1, 4, 9
var getNumbers = () = > {
return Promise.resolve([1.2.3])}var multi = num= > {
return new Promise((resolve, reject) = > {
setTimeout(() = > {
if (num) {
resolve(num * num)
} else {
reject(new Error('num not specified'}})),1000)})}async function test () {
var nums = await getNumbers()
nums.forEach(async x => {
var res = await multi(x)
console.log(res)
})
}
test()
Copy the code
ForEach itself does not wait for the Async function, but returns pending promises directly, with the result that all promises fire in parallel,
When the Promise state becomes progressively, the corresponding callback function returns 1, 4, 9
To implement serial triggering, we can do this
-
Rewrite the forEach function
async function asyncForEach(array, callback) { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array) } } async function test () { var nums = await getNumbers() asyncForEach(nums, async x => { var res = await multi(x) console.log(res) }) } Copy the code
-
Use iterators, i.e. for of loops (based on iterator)
Loop execution of the iterator itself supports asynchrony.
async function test () { var nums = await getNumbers() for(let x of nums) { var res = await multi(x) console.log(res) } } Copy the code
Asynchronous map implements parallelism
The general reason is similar to forEach, except that map returns a new array of promises, so we need to receive promise.all.
Thus, all Promise states that are the result of map are fulfilled when THEN is fulfilled.
var arr = [1.2.3.4.5];
var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
return await callAsynchronousOperation(item);
}));
Copy the code
Refer to the article
- MDN developer.mozilla.org/zh-CN/docs/…
- When async/await meets forEach objcer.com/2017/10/12/…