实现js常见的map、filter和reduce等高阶函数以及数组扁平化

376 阅读5分钟

首先,我们来了解一下什么是高阶函数,JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。那么js里面常见的高阶函数又有哪些呢?常见的比如Array.prototype.map() - JavaScript | MDN (mozilla.org)Array.prototype.filter() - JavaScript | MDN (mozilla.org)Array.prototype.reduce() - JavaScript | MDN (mozilla.org)等,建议点击链接认真查看有关内容。

map

map()  方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值

    let arr = [1,2,3,4];

    let res = arr.map((item) => {
        return item * item;
    })

    console.log(res);

输出结果如下:

image.png

接下来,我们来实现map函数

    function selfMap(fn, context) {
        let arr = Array.prototype.slice.call(this); //arr为测试数组

        let newArr = new Array();

        for (let i = 0; i < arr.length; i++) {
            if (!arr.hasOwnProperty(i)) {
                continue;
            }   //判断是否为稀疏数组
            newArr[i] = fn.call(context, arr[i], i, this);
        }

        return newArr;
    }

    Array.prototype.selfMap = selfMap;  // 挂载到Array的原型链上
    
    let arr = [1, 2, 3, 4];
    
    let res2 = arr.selfMap((item) => {
        return item * item;
    })

    console.log(res2);

测试结果和map的结果一致

image.png

filter

filter()  方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

    let arr = [1, 2, 3, 4];

    let res = arr.filter((item) => {
        return item > 2;
    })

    console.log(res);

输出结果如下:

image.png

接下来,我们来实现filter函数

    let selfFilter = function (fn, context) {

        let arr = Array.prototype.slice.call(this);
        let finalArr = [];
        
        for (let i = 0; i < arr.length; i++) {
            if (!arr.hasOwnProperty(i)) {
                continue;
            }  //判断是否为稀疏数组

            if (fn.call(context, arr[i], i, this)) {   // 判断是否符合条件
                finalArr.push(arr[i]);
            }
        }

        return finalArr;
    }

    Array.prototype.selfFilter = selfFilter;
    
    let arr = [1, 2, 3, 4];

    let res2 = arr.selfFilter((item) => {
        return item > 2;
    })

    console.log(res2);

测试结果和filter的结果一致

image.png

reduce

reduce()  方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

第一次执行回调函数时,不存在“上一次的计算结果”。如果需要回调函数从数组索引为 0 的元素开始执行,则需要传递初始值。否则,数组索引为 0 的元素将被作为初始值 initialValue,迭代器将从第二个元素开始执行(索引为 1 而不是 0)。

    let arr = [1, 2, 3, 4];

    let res = arr.reduce((pre,cur) => {
        return pre + cur;
    },0)

    console.log(res);
    
    // 在上面的例子中,reduce实现的功能是求和,pre参数代表前一次计算的结果,cur代表当前值,0代表初始值,即在没有计算之前,pre的值

image.png

     function selfReduce(fn, initValue) {
        let arr = Array.prototype.slice.call(this);

        let res;
        let startIndex;

        if (initValue == undefined) {  //找到第一个非空单元的元素和下表
            for (let i = 0; i < arr.length; i++) {
                if (!arr.hasOwnProperty(i)) {
                    continue;
                }
                startIndex = i;
                res = arr[i];
                break;
            }
        }
        else {
            res = initValue;
        }

        //console.log(startIndex, res);  

        for (let i = ++startIndex || 0; i < arr.length; i++) {
            if (!arr.hasOwnProperty(i)) {
                continue;
            }

            res = fn.call(null, res, arr[i], i, this);
        }

        return res;
    }

    Array.prototype.selfReduce = selfReduce;
    
    let arr = [1, 2, 3, 4];

    let res2 = arr.selfReduce((pre,cur) => {
        return pre + cur;
    },0)

    console.log(res2);

测试结果和reduce一致

image.png

利用reduce实现map和filter

如果已经理解前面三个高阶函数的用法,就可以自主尝试一下利用reduce实现map和filter,这里也简单实现一下

利用reduce实现map

    let arr = [1, 2, 3, 4];

    let res = arr.map((item) => {
        return item * item;
    })

    console.log(res);

    function selfMap2(fn, context) {

        let arr = Array.prototype.slice.call(this);

        return arr.reduce((pre, cur, index) => {
            return [...pre, fn.call(context, cur, index, this)]
        }, [])
    }

    Array.prototype.selfMap2 = selfMap2;

    let res2 = arr.selfMap2((item) => {
        return item * item;
    })

    console.log(res2);

image.png

利用reduce实现filter

    let arr = [1, 2, 3, 4];

    let res = arr.filter((item) => {
        return item > 2;
    })

    console.log(res);

    function selfFilter2(fn, context) {
        let arr = Array.prototype.slice.call(this);
        
        return arr.reduce((pre, cur, index) => {
            return fn.call(context, arr[index], index, this) ? [...pre, cur] : [...pre];
        }, [])
    }

    Array.prototype.selfFilter2 = selfFilter2;


    let res2 = arr.selfFilter2((item) => {
        return item > 2;
    })

    console.log(res2);

image.png

flat

flat()  方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。示例参考自Array.prototype.flat() - JavaScript | MDN (mozilla.org)

let arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

let arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

let arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

//使用 Infinity,可展开任意深度的嵌套数组
let arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

//flat方法会移除数组中的空项
let arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]

方法1

    function selfFlat2(depth = 1) {   //提取嵌套数组的结构深度默认值为1
        let arr = Array.prototype.slice.call(this);

        let newArr = [];

        if (depth == 0) {
            return arr;
        }

        for (let i = 0; i < arr.length; i++) {
            if (Array.isArray(arr[i])) {
                newArr.push(...selfFlat2.call(arr[i], depth - 1));
            }
            else {
                if (arr[i]){    // 如果数组某个值为空,不填入新数组
                    newArr.push(arr[i]);
                }
            }
        }

        return newArr;


    }
    
    // 这里只展示测试的部分示例,感兴趣的可自行测试其他例子

    Array.prototype.selfFlat2 = selfFlat2;
    console.log([1, 2, [3, 4], ,[5, [6, 7, [8, ,9]]]].selfFlat2(2));

image.png

方法2

   function selfFlat(depth = 1) {
        let arr = Array.prototype.slice.call(this);

        if (depth == 0) {
            return arr;
        }

        return arr.reduce((pre, cur, index) => {
            if (Array.isArray(cur)) {
                return [...pre, ...selfFlat.call(cur, depth - 1)]
            }
            else {
                if(cur){
                   return [...pre, cur];
                }
                
            }
        }, [])
    }
    
    // 这里只展示测试的部分示例,感兴趣的可自行测试其他例子

    Array.prototype.selfFlat = selfFlat;
    console.log([1, 2, [3, 4],, [5, [6, 7, [8,, 9]]]].selfFlat());
    

image.png

方法3

   function flattern(arr) {

        return arr.join().split(',').map((item) => {
            if (item != undefined){
                return parseInt(item);
            }
        }).filter((item) => {
            if (!isNaN(item)){
                return item;
            }
        });
        
        
        
        //如果不加上filter函数,返回的结果是[1, 2, 3, NaN, 4, 6, NaN, 7, 5],其中会包含NaN

    }

    console.log(flattern([1, 2, 3,, [4,[6,,7], 5]]));

join() 方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。如果省略该参数,则使用逗号作为分隔符。(这里还有一个小插曲,当时我贝壳二面的时候,面试问我数组扁平化的方法,当时我说到了这个方法,写出来之后,面试官问我有没有问题,我很肯定,一点问题都没有,我之前有测试过。但是面试官一直坚持说split不传入参数跑不出来,我当时面试的时候,就紧张起来了,想质疑他来着,但还是缺乏了一点勇气,就说自己下去之后再试试。查阅了资料之后才发现默认参数就是",",有点后悔没有继续“刚”下去,即时最后还是给了我offer)

split() 方法用于把一个字符串分割成字符串数组。

image.png

方法4


    //[].concat(...[1, 2, 3, [4, 5]]);  // [1, 2, 3, 4, 5]
    // es6的扩展运算符可以将二维数组变成一位数组
    
    let arr = [1, 2, 3, , [4, [6, , 7, [8,9,,[10]]], 5]];

    function flattern(arr) {

        while (arr.some((item) => Array.isArray(item))) {
            arr = [].concat(...arr);
        }

        return arr.filter((item) => {
            return item != undefined;
        })
    }

    console.log(flattern(arr));

image.png