[suggested collection] native JavaScript handwriting array API

This article will first understand the usage of array APIs, and then simulate the implementation of these APIs. If you think there is anything wrong, please point out the following!

1. forEach method

This method will run the passed in function for each item of the array element without returning a value. This is equivalent to using a for loop to traverse the array. For example:

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach((item, index, array) => {
    // Perform some actions 
    item += 2
})
console.log(numbers);

We found that the array elements were not changed

You can use the forEach method instead of the for loop to traverse the array

Let's take a look at the following code to summarize

let arr1 = [{
    name: 'ljc',
    age: 19
}, {
    name: 'xy',
    age: 18
}]
arr1.forEach(item => {
    item.age += 1
})
console.log(arr1);

From the above two codes, we can see that the age attribute value of both members is increased by 1

So we can simply draw a conclusion: when the elements in the array are value types, forEach will never change the array. When the elements in the array are reference types, the array can be changed

Note: because the forEach method does not return a value, forEach does not support chained operations

1-1 handwritten forEach method

The native forEach method receives two parameters callback and thisArg, and the callback function passes in three parameters: the value of the current item of the array, the index, and the array itself

Array.prototype.myForEach = function (callback, thisArg) {
    // Determine whether the element calling the API is null
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    // Judge whether it is function
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    // Get the caller arr through this
    const arr = this
    // Determine loop variables
    let index = 0
    // Loop traversal calls callback for each array element
    while (index < arr.length) {
        // Determine whether this item exists
        if (index in arr) {
            // Point this to thisArg through call, and pass in three parameters
            callback.call(thisArg, arr[index], index, arr)
        }
        index++
    }
}

2. map method

Compared with the forEach method, the map method has a return value, while the forEach method has no return value.

map is also called mapping, that is, mapping the original array to a new array

  1. Each element in the array will call a provided function and return the result.
  2. A new array will be created, and a bearing object is required, that is, a new object will be returned
  3. The original array will not change unless it is carried by the original array

usage method

let arr = [1, 2, 3, 4, 5]
let newArr = arr.map(item => item * 2)
console.log(newArr); //  [2, 4, 6, 8, 10]

map needs to have a return value, which can be abbreviated by arrow function

Error prone point

Every element in the map must execute a callback function, so there must be a return. Therefore, the array cannot be filtered by map

You can see the gray undefined. Bye

2-2 handwritten map method

  1. First, exclude empty arrays and no callback functions
  2. According to the requirements of map, you need to create a new array, execute the function and return the array
Array.prototype.myMap = function (callback, thisArg) {
    // Like forEach, two exclusions are required
    if (this == undefined) {
        throw new TypeError('this is null or not defined');
    }
    if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
    }
    // Unlike forEach, map returns a new array
    const ret = []
    // Get function caller
    const arr = this
    // Array length
    let len = arr.length
    // Execute a callback function on each element
    for (let i = 0; i < len; i++) {
        // Check if i is in arr
        if(i in arr) {
            ret[i] = callback.call(thisArg, arr[i], i, arr)
        }
    }
    // Returns a processed array
    return ret
}

3. filter

From the name of filter, you can know that it is used for filtering. Like map, it returns a new array of objects without changing the original array

usage method

Thus, we can filter out the elements whose array elements are less than 3

3-3 handwriting filter method

Compared with the map method, the filter needs to form a new array of elements that meet the conditions and return them

Array.prototype.myFilter = function(callback,thisArg) {
    if (this == undefined) {
        throw new TypeError('this is null or not defined');
    }
    if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
    }
    // New array
    const res = []
    // Save this
    const arr = this
    // Calculate array length in advance
    const len = arr.length
    for(let i = 0;i<len;i++) {
        if(i in arr) {
            // Judge whether the element has a return value after function call
            // To determine whether the screening rules are met,
            if(callback.call(thisArg,arr[i],i,arr)) {
                res.push(arr[i])
            }
        }
    }
    // Finally, remember to return a new array
    return res
}

4. some method

The some method is used to check whether there are qualified values in the array. The return value is a Boolean value

usage method

some method is more performance friendly, because it does not need to traverse all, as long as it finds a qualified one, it will 9 return true

According to this principle, we can write a some method

4-4 handwriting some method

Array.prototype.mySome = function (callback, thisArg) {
    if (this == undefined) {
        throw new TypeError('this is null or not defined');
    }
    if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
    }
    let arr = this
    let len = arr.length
    for (let i = 0; i < len; i++) {
        if (i in arr) {
            if (callback.call(thisArg, arr[i], i, arr)) {
                return true
            }
        }
    }
    return false
}

5. every method

Compared with some, each member returns true only if it meets the conditions, and false if it does not meet the conditions

true will be returned only if all conditions are met

5-5 handwriting every method

Array.prototype.myEvery = function (callback, thisArg) {
    if (this == undefined) {
        throw new TypeError('this is null or not defined');
    }
    if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
    }
    const arr = this
    const len = arr.length
    for (let i = 0; i < len; i++) {
        if (i in arr) {
            if (!callback.call(thisArg, arr[i], i, arr)) {
                return false
            }
        }
    }
    return true
}

6. find and findIndex methods

If a qualified element is found, the current element will be returned if it is found, and undefined if it is not found

The same shape as the find method is also the findIndex method, which returns the index value of the first element that meets the condition

find usage

Return satisfied elements

findIndex usage

Returns the index that meets the

6-6 handwritten find method

Loop through the array and call the passed in function. If the conditions are met, the array element corresponding to the current index will be returned, and only the first one will be returned

Array.prototype.myFind = function (callback, thisArg) {
    if (this == undefined) {
        throw new TypeError('this is null or not defined');
    }
    if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
    }
    // Save this, that is, the caller
    const arr = this
    const len = arr.length
    for (let i = 0; i < len; i++) {
        if (i in arr) {
            if (callback.call(thisArg, arr[i], i, arr)) {
                return arr[i]
            }
        }
    }
    return undefined
}

findIndex method

The difference from find is the return value. You only need to change return arr[i] to return i

Small scene

For the above 6 or 7 array methods, you will find the difference in implementation, that is, the lines of code are not easy to remember. Their use scenarios are unknown. Use a small scenario to show the use scenarios of these API s

Previously on: in a company, the boss is considering giving employees a promotion and a raise

Company employee data

let staff = [
    {name: 'a', salary: 20000, age: 36},
    {name: 'b', salary: 19000, age: 34},
    {name: 'c', salary: 18000, age: 20},
    {name: 'd', salary: 17000, age: 18}
]

🤵 Boss: "this year's performance is good, and the wages of all employees have increased by 1000"

👨‍🦲 Programmer brother: "it's simple. Just use forEach. The code and results are like this"

staff.forEach(item => item.salary += 1000)

🤵 Boss: "make me a salary form"

👨‍🦲 Programmer brother: "no problem. Map has a return value. You can use map"

w = staff.map(item => item.salary += 1000)
console.log(w) // [21000, 20000, 19000, 18000]

🤵 Boss: "the company has been established for so many years. Please give me a list of employees over 33 years old."

👨‍🦲 Programmer brother: "OK, no problem, use filter"

w = staff.filter(item => item.age > 33)

👨‍🦲 Programmer brother: "employees a and B are over 33 years old"

🤵 Boss: "then help me see if there are employees under the age of 18"

👨‍🦲 Programmer: "OK, let's use some method. We don't have employees younger than 18 years old."

w = staff.some(item => item.age < 18) // false

🤵 Boss: "the company is now listed. Do you see if the wages of our employees are more than 1.6w?"

👨‍🦲 Programmer brother: "it's really good. It's all over 1.6w. What else do you need?"

w = staff.every(item => item.salary > 16000) // true

🤵 Boss: "then help me find someone over 35 years old. Just the first one."

👨‍🦲 Programmer's younger brother: "simply check the first one above 35, called a"

w = staff.find(item => item.age > 35) // {name: "a", salary: 20000, age: 36}

🤵 Boss: "how many places does it rank in the company's employee data?"

👨‍🦲 Programmer brother: "you're boring. It all depends."

w = staff.findIndex(item => item.age > 35) // 0

👨‍🦲 Programmer brother: "0, first, veteran level"

🤵 Boss: "very good. Your skills are good. Then calculate the total salary and ask the Secretary to give money to the finance department."

👨‍🦲 Programmer brother: "... Wait a minute, I'll learn reduce again"

7. reduce method

Different from the iterative method, reduce is a merging method. Merging does not execute the objective function for each item. It can be summarized into the following steps:

  1. Continuously take out the first two items of the array, execute the objective function on it, and calculate the return value
  2. Insert the return value into the header of the array, that is, as ayyay[0]
  3. This process continues until each item in the array is accessed once
  4. Return final result

Examples

const arr = [1, 2, 3]
const res = arr.reduce((prev, cur) => prev + cur)
console.log(res); // 6

In the above code, reduce does the following merging operations

[1, 2, 3] // Take out 1 and 2, perform 1 + 2 and fill in 3
[3, 3] // Take out 3 and fill in 6
[6] // Final return 6

7-7 handwritten reduce method

Write according to the 4-step rule above

Array.prototype.myReduce = function (callback, initialValue) {
    // Determine whether the element calling the API is null
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    // Judge whether it is function
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    const arr = this
    const len = arr.length
    // Second parameter
    let accumulator = initialValue
    let index = 0
    // If the second parameter is undefined, the first valid value of the array
    // As the initial value of the accumulator
    if (accumulator === undefined) {
        // The first valid value found in the array is not necessarily arr[0]
        while (index < len && !(index in arr)) {
            index++
        }
        if (index >= len) {
            throw new TypeError('Reduce of empty array with no initial value')
        }
        // Output the first valid array element as the first element of the accumulator
        accumulator = arr[index++]
    }
    while (index < len) {
        if (index in arr) {
            // arr[index] is the next element of the accumulator
            accumulator = callback.call(undefined, accumulator, arr[index], index, arr)
        }
        // Continuous backward shift
        index++
    }
    // Return results
    return accumulator
}

7-x implementing map with reduce

I have seen this topic in many places

Implementation idea: take the elements traversed each time as the parameters of the incoming function, and store the function execution results into a new array for return

Core: the map function receives a function as a parameter, and the function as a parameter receives three parameter values, respectively traversing each element of the array, the element index and the array itself. These three parameters just correspond to the second, third and fourth parameters of the first function parameter received by the reduce function

Array.prototype.mapReduce = function (callback, context = null) {
    if (this == null) {
        throw new TypeError('this is null or not defined')
    }
    // Judge whether it is function
    if (typeof callback !== "function") {
        throw new TypeError(callback + ' is not a function')
    }
    let arr = this
    return arr.reduce((pre, cur, index, array) => {
        let res = callback.call(context, cur, index, array)
        return [...pre, res]
    })
}

That's all for today's hand tearing code. If you have any questions, please add corrections in the comment area!

Keywords: Javascript Front-end ECMAScript

Added by alluoshi on Mon, 03 Jan 2022 10:09:07 +0200