Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

preface

There are many higher-order functions in JavaScript, such as Map, filter, every, as well as ES6’s new find, which can greatly improve the efficiency of writing code after skilled use. Let’s take a look at these higher-order functions and implement them using native JS simulation.

So what is a higher-order function?

A function that satisfies at least one of the following conditions:

  • Takes one or more functions as arguments
  • Output a function

Most of the higher-order functions in JavaScript take a function as an argument, which is as follows:

Array.prototype.func = function(callback(currentValue[, index[, array]]){
}[, thisArg])
Copy the code
  • callback: a callback function that operates on array elements
    • currentValue: The current element being processed
    • The index of the current element
    • Call an array of higher-order functions
  • thisArg: Optional, executecallbackFunctionthis

forEach

usage

ForEach is mainly used for simple traversal of arrays. The basic usage is as follows

arr = [1.2.3.4]
arr.forEach((val, index) = > {
    console.log(val, index)
})
// Equivalent to the original for loop
for (var i = 0; i<arr.length; i++) {
    console.log(arr[i], i)
}
Copy the code

Analog implementation

Let’s recall what happened inside the forEach example above. It’s simply a for loop that runs the arr.length callback, which takes the corresponding element index, element value, and array itself. Then we can simulate the general running process.

for (var i = 0; i<arr.length; i++) {
    callback(arr[i], i, arr)
}
Copy the code

Since forEach can also accept thisArg arguments as the context for callback functions, the above code is modified slightly with Call /apply.

callback.call(thisArg, arr[i], i, arr)
Copy the code

With the above analysis, we can write the complete simulation code:

Array.prototype.myForEach = function (callbackFn) {
    // Check if this is valid
    if (this= = =null || this= = =undefined) {
        throw new TypeError("Cannot read property 'myForEach' of null");
    }
    // Check whether the callbackFn is valid
    if (Object.prototype.toString.call(callbackFn) ! = ="[object Function]") {
        throw new TypeError(callbackFn + ' is not a function')}// Get the array object that executes the method and the this object passed in
    var _arr = this, thisArg = arguments[1] | |window;
    for (var i = 0; i < _arr.length; i++) {
        // Execute the callback functioncallbackFn.call(thisArg, _arr[i], i, _arr); }}Copy the code

map

usage

The map function performs a callback on each element of the array and returns a new array containing the result of the operation, basically as follows:

const users = [ 
    { name: 'John'.age: 34 }, 
    { name: 'Amy'.age: 20 }, 
    { name: 'camperCat'.age: 10}];// Request: Fetch the name of the users owner and store it in a new array
// Do not use map
names = []
for (var i = 0; i<users.length; i++){
    names.push(users[i].name)
}

// Map operates on each element of the array, so the above code can be simplified using map
names = users.map(function (user) {
    return user.name
})
// If you know the arrow function, you can simplify it further
names = user.map(user= > user.name)
Copy the code

Analog implementation

Given the forEach experience above, the map only needs to be modified so that the result returns a new array (exception judgments are omitted here).

Array.prototype.myMap = function(callbackFn) {
    var _arr = this, thisArg = arguments[1] | |window, res = [];
    for (var i = 0; i<_arr.length; i++) {
        // Store the result of the operation
        res.push(callbackFn.call(thisArg, _arr[i], i, _arr));
    }
    return res;
}
Copy the code

filter

usage

Filter, which performs a callback on each element of the array, returns the element whose callback result is true.

// Return an even number
arr = [1.2.3.4.5];
arr.filter(val= > val % 2= =0)
Copy the code

Analog implementation

Much like map’s implementation, map returns all elements after the callback, while filter returns only elements whose callback result is true.

Array.prototype.myFilter = function(callbackFn) {
    var _arr = this, thisArg = arguments[1] | |window, res = [];
    for (var i = 0; i<_arr.length; i++) {
        // The callback function executes true
        if(callbackFn.call(thisArg, _arr[i], i, _arr)) { res.push(_arr[i]); }}return res;
}
Copy the code

every

usage

Every does not return an array. It returns a Boolean true/false. Each element of the array executes a callback function that returns true if all results are true, and false otherwise.

arr = [1.3.5.7.8.9]
// false, 8 is an even number
arr.every(ele= > ele % 2= =1) 

arr2 = [2.4.6]
// true is even
arr2.every(ele= > ele % 2= =0)
Copy the code

Analog implementation

Array.prototype.myEvery = function(callbackFn) {
    var _arr = this, thisArg = arguments[1] | |window;
    // The start identifier value is true
    // Return false on a callback
    // If the loop completes, it means that all callbacks return true and the final result is true
    var flag = true;
    for (var i = 0; i<_arr.length; i++) {
        // If the callback is false, the function is interrupted
        if(! callbackFn.call(thisArg, _arr[i], i, _arr)) {return false; }}return flag;
}
Copy the code

some

usage

Some, like every, returns a Boolean value. Some returns true as long as the result of the callback function has a true, and false otherwise.

Analog implementation

Array.prototype.mySome = function(callbackFn) {
    var _arr = this, thisArg = arguments[1] | |window;
    // Start identifier value is false
    // If a callback returns true, return true
    // If the loop completes, it means that all callbacks return false and the final result is false
    var flag = false;
    for (var i = 0; i<_arr.length; i++) {
        // If the callback is false, the function is interrupted
        if (callbackFn.call(thisArg, _arr[i], i, _arr)) {
            return true; }}return flag;
}
Copy the code

find/findIndex

usage

Find and findIndex are new array methods in ES6 that return the first array element/index that satisfies the callback function. When there is no element in the array that satisfies the callback function, undefined and -1 are returned, respectively.

const users = [ 
    { name: 'John'.age: 34 }, 
    { name: 'Amy'.age: 20 }, 
    { name: 'camperCat'.age: 10}];Copy the code
  1. Return the age of John whose name is

    If there is no find method, we need to iterate over the name=Jonn and find the age. Using Find, however, can be done quickly and easily.

    JohnAge = users.find(user= > user.name === 'John').age
    Copy the code
  2. Returns the index whose name is Amy

    In ES6, Array provided methods to find elements in an Array: indexOf, lastIndexOf, but neither of these methods can find objects.

    // Return -1, indicating that Amy is not found
    users.indexOf({ name: 'Amy'.age: 20 })
    // return hi = 1
    users.findIndex(user= > user.name === 'Amy')
    Copy the code

    IndexOf can also be used to find out if an element is present in an array, but it is not semantically good and needs to be compared with -1 every time, so ES6 added a new includes method.

Analog implementation

Find /findIndex returns the first element that satisfies the callback function. Some is similar, so their native code is similar.

Array.prototype.myFind = function(callbackFn) {
    var _arr = this, thisArg = arguments[1] | |window;
    // Returns true on a callback, directly returning the array element
    // If the loop completes, it means that all callbacks return false and the final result is undefined
    for (var i = 0; i<_arr.length; i++) {
        // If the callback is false, the function is interrupted
        if (callbackFn.call(thisArg, _arr[i], i, _arr)) {
            return_arr[i]; }}return undefined;
}
Copy the code

reduce

usage

Reduce is slightly different from the previous approach:

arr.reduce(callback(accumulator, currentValue[, index[, array]]){
}[, initialValue])
Copy the code
  • callback: a callback function that operates on array elements
    • accumulator: The return value of the accumulator’s cumulative callback; It is the cumulative value returned when the callback was last called, orinitialValue
    • currentValue: The current element being processed
    • The index of the current element
    • Call an array of higher-order functions
  • initialValue: as the initial value for the first call to the function. If no initial value is provided, the first element in the array is used.

An error is reported when calling reduce with an empty array with no initial value

But the cumulative effect adds a lot of excitement to reduce and leads to a lot of useful uses.

  1. Array summation
arr = [0.1.2.3.4]
arr.reduce((accu, current) = > accu + current, 0)
Copy the code
  1. Summing up an array of objects

If you just use Reduce as above, the end result will be problematic

objArr = [{x: 1}, {x:2}, {x:3}]
objArr.reduce((accu, current) = > accu.x + current.x, 0)
Copy the code

The above code returns a NaN. Why is that?

Accumulator is an accumulator or an initial value since the last call. Therefore, after the first call, 3 is assigned to Accumulator. It no longer has an X attribute, so NaN is returned

// Method 1: Use map to extract values
objArr.map(obj= > obj.x).((accu, current) = > accu + current, 0)
// Method 2: give the initial value, each time run accu + current
objArr.reduce((accu, current) = > accu + current.x, 0)
Copy the code
  1. Count the number of occurrences of each element in the array
var names = ['Alice'.'Bob'.'Tiff'.'Bruce'.'Alice'];

var countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
Copy the code

Analog implementation

Array.prototype.myReduce = function(callbackFn) {
    var _arr = this, accumulator = arguments[1];
    var i = 0;
    // Determine whether an initial value is passed in
    if (accumulator === undefined) {
        // An error is reported when calling reduce with an empty array with no initial value
        if (_arr.length === 0) {
            throw new Error('initVal and Array.length>0 need one')}// Assign the initial value to the first element of the array
        accumulator = _arr[i];
        i++;
    }
    for (; i<_arr.length; i++) {
        // The computed result is assigned to the initial value
        accumulator = callbackFn(accumulator,  _arr[i], i, _arr)
    }
    return accumulator;
}
Copy the code

Past wonderful articles

  • Cow guest latest front-end JS written test 100 questions
  • A thorough understanding of prototypes and prototype chains in JavaScript
  • JavaScript precompilation learning
  • Complete understanding of EventLoop in JavaScript
  • “2W word big chapter 38 interview questions” completely clear JS this pointing to the problem