手撕JS之数组方法

366 阅读2分钟

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

数组的方法有很多,在面试的过程中也遇到了非常多。不仅仅是数组方法的使用,如果想真正了解并掌握数组的各种方法,我们也可以通过实现它来加深记忆。

一、类数组转换为数组

什么是类数组?

  • 类数组就是类似数组,它具有数组的length属性,但是不具有数组原型上的方法。

常见的类数组有:arguments、DOM操作返回的结果(比如document.getElementsByTagName()

那么很多时候,我们需要将类数组转换成数组,方便我们使用数组原型上的方法。

如何将类数组转换成数组?

下面是转换的几种方法:

1、Array.from

let newArr1 = Array.from(classArr);

2、Array.prototype.slice.call()

let newArr2 = Array.prototype.slice.call(classArr);

3、扩展运算符

let newArr3 = [...classArr];

4、利用concat拼接数组

let newArr4 = Array.prototype.concat.apply([], classArr)

以上这四种方法是最常见也是最实用的转换方法了。

二、手写filter()

filter()的用法

filter顾名思义,就是过滤,能过够过滤出数组当中符合条件的元素。注意:filter不会改变原数组

1、比如我们可以利用filter过滤出数组当中所有的偶数

    let arr = [56, 15, 48, 3, 7];
    let newArr = arr.filter(function(value, index, array) {
        return value % 2 === 0;
    });
    console.log(newArr);  // [56,48]

2、又或者利用filter来实现数组去重

    function unique(arr) {
        return arr.filter(function(item, index, arr) {
            //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
            return arr.indexOf(item, 0) === index;
        });
    }

实现filter()

首先看filter的语法:

array.filter(function(currentValue,index,arr), thisValue)

参数:

  • currentValue: 必选,当前元素的值
  • index: 可选,当前元素的索引值
  • arr: 可选,当前元素属于的数组对象
  • thisValue: 可选,对象作为该执行回调时使用,传递给函数,作为this的值

下面是具体的实现方法:

Array.prototype.filter = function(callback, thisArg) {
    if (this == undefined) {
        throw new TypeError('this is null or not undefined');
    }
    if (typeof callback !== 'function') {
        throw new TypeError(callback + 'is not a function');
    }
    const res = [];
    // 让O成为回调函数的对象传递(强制转换对象)
    const O = Object(this);
    // >>>0 保证len为number,且为正整数
    const len = O.length >>> 0;
    for (let i = 0; i < len; i++) {
        // 检查i是否在O的属性(会检查原型链)
        if (i in O) {
            // 回调函数调用传参
            if (callback.call(thisArg, O[i], i, O)) {
                res.push(O[i]);
            }
        }
    }
    return res;
}

三、手写forEach()

forEach()的用法

forEach()方法用于调用数组的每个元素,并将元素传递给回调函数。主要功能就是遍历数组,这个语句需要一个回调函数作为参数。注意:forEach返回值为undefined

比如通过forEach()遍历数组并打印元素的值:

    var arr = ['a', 'b', 'c'];
    arr.forEach(function(value, index, arr) {
        console.log(value); // a b c
    })

实现forEach()

首先看forEach()的语法:

array.forEach(function(currentValue, index, arr), thisValue)
  • currentValue: 必选,当前元素的值
  • index: 可选,当前元素的索引值
  • arr: 可选,当前元素属于的数组对象
  • thisValue: 可选,传递给函数的值一般用 "this" 值。如果这个参数为空, "undefined" 会传递给 "this" 值

下面是具体的实现方法:

  Array.prototype.forEach = function(callback, thisArg) {
        if (this == null) {
            throw new TypeError("this is null or not defined");
        }
        if (typeof callback !== 'function') {
            throw new TypeError(callback + "is not a function")
        }
        const O = Object(this);
        const len = O.length >>> 0;
        let k = 0;
        while (k < len) {
            if (k in O) {
                callback.call(thisArg, O[k], k, O);
            }
            k++;
        }
    }

四、手写map()

map()的用法

map()方法也是对数组进行一系列的处理。并且返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。注意: map()不会改变原数组。

1、比如,我们想返回一个数组,数组中元素为原始数组的平方根。可以这么实现:

    var nums = [4, 9, 16, 25];

    function fn(nums) {
        return nums.map(Math.sqrt)
    }
    console.log(fn(nums)); // [2, 3, 4, 5]

2、又或者数组中的每个元素都乘以输入框指定的值,并返回新数组

    var nums = [4, 9, 16, 25];

    function fn2() {
        return nums.map(function(item, index, arr) {
            // return num * document.getElementById("multiplyWith").value;
            return item * 2
        })
    }
    console.log(fn2(nums)); // [8, 18, 32, 50]

实现map()

首先来看一下map()的语法:

array.map(function(currentValue,index,arr), thisValue)
  • currentValue: 必选,当前元素的值
  • index: 可选,当前元素的索引值
  • arr: 可选,当前元素属于的数组对象
  • thisValue: 可选,对象作为该执行回调时使用,传递给函数,作为this的值

下面是具体的实现方法:

    Array.prototype.map = 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 res = [];
        // 让O成为回调函数的对象传递(强制转换对象)
        const O = Object(this);
        // >>>0 保证len为number,且为正整数
        const len = O.length >>> 0;
        for (let i = 0; i < len; i++) {
            // 检查i是否在O的属性(会检查原型链)
            if (i in O) {
                // 调用回调函数并传入新数组
                res[i] = callback.call(thisArg, O[i], i, this)
            }
        }
        return res;
    }

五、手写reduce()

reduce()的用法

reduce()方法用于将数组元素计算为一个值(从左到右)。这个方法接收一个函数作为累加器,最终计算成最终的值。

比如计算数组元素四舍五入的总和:

    var nums = [1, 2, 3]

    function getSum(total, num) {
        return total + Math.round(num)
    }

    function fn(item) {
        return nums.reduce(getSum, 0)
    }
    console.log(fn(nums)); // 6

实现reduce()

先来看看它的语法:

array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
  • currentValue: 必选,当前元素的值
  • index: 可选,当前元素的索引值
  • arr: 可选,当前元素属于的数组对象
  • thisValue: 可选,传递给函数的值一般用 "this" 值。如果这个参数为空, "undefined" 会传递给 "this" 值

下面是具体的实现方法:

    Array.prototype.reduce = functon((callback, initialValue) {
        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 O = Object(this);
        const len = O.length >>> 0;
        let accumulator = initialValue; // 累加器
        let k = 0;
        // 如果第二个参数为undefined的情况下
        // 则数组的第一个有效值作为累加器的初始值
        if (accumulator === undefined) {
            while (k < len && !(k in O)) {
                k++;
            }
            // 如果超出数组界限还没有找到累加器的初始值,则抛出错误
            if (k >= len) {
                throw new TypeError("Reduce of empty array with no initial value")
            }
            accumulator = O[k++]
        }
        while (k < len) {
            if (k in O) {
                accumulator = callback.call(undefined, accumulator, O[k], k, O)
            }
            k++;
        }
        return accumulator;
    })

总结

数组当中常用的方法大概就是这样,不管是在面试当中,还是实际开发当中,数组方法都是常见的,多思考多实践有助于加深记忆。