快来学习javascript设计模式

12/10/2022 扁平化

# highlight: a11y-dark theme: channing-cyan

本文正在参加「金石计划 . 瓜分6万现金大奖」 (opens new window)

什么是扁平化?对于数组扁平化来说,就是降维过程,即将多维数组降为一维数组的过程。而对于对象扁平化来说,就是将深度大于1的对象,降到深度为1的过程。这么一说,肯定不好理解,接下来,让代码说话吧~。

# 一、数组的扁平化

比如现在需要将一个这样的数组进行扁平化,让他变为以为数组:

const myArray=[1,2,[3,4,5,[6,[7]]],8,[9]]
1

对于数组的扁平化,你可以使用哪些API来实现呢?首先肯定是先想到ES6里面的flat方法:

# (1)Array.prototype.flat()

先介绍介绍flat,Array.prototype.flat()用于将嵌套的数组"拉平",变成一维数组,并且该方法会返回一个新的数组,对原数组没有任何影响。同时,它默认只会拉平一层,如果想拉平多层的嵌套数组,需要给flat()方法传入一个正整数的参数,表示想要拉平的层数,默认值是1。如下扁平化结果:

// 扁平化一层
const flatArray1 = myArray.flat()
console.log(flatArray1); //[ 1, 2, 3, 4, 5, [ 6, [ 7 ] ], 8, 9 ]
​
// 扁平化两层
const flatArray2 = myArray.flat(2)
console.log(flatArray2); //[1,2, 3,4,5, 6,[ 7 ], 8, 9]
​
// 扁平化为一维数组
const flatArray3 = myArray.flat(3)
console.log(flatArray3); //[1, 2, 3, 4, 5,6, 7, 8, 9]
1
2
3
4
5
6
7
8
9
10
11

可烦了,扁平化我还得数一下有几层,但是,有一个关键字,不管有多少层嵌套,都可以将多维数组转成一维数组,即用Infinity关键字作为参数:

const flatArray4 = myArray.flat(Infinity)
console.log(flatArray4);
////[1, 2, 3, 4, 5,6, 7, 8, 9]
1
2
3

# (2)Array.prototype.toString()

toString()方法可以将一个数组转换成字符串,然后需要将字符串通过split()方法转换成数组,然而这个时候数组的每一项还是一个字符串,需要强制类型转换,这样的话,就只适用于数组的每一个元素都是数字的场景。

const toStringArray = myArray.toString().split(',').map(item => Number(item))
console.log(toStringArray);
​
//toString()得到
1,2,3,4,5,6,7,8,9
//split()得到
[
  '1', '2', '3',
  '4', '5', '6',
  '7', '8', '9'
]
//map()
[
  1, 2, 3, 4, 5,
  6, 7, 8, 9
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# (3)Array.prototype.concat()

该方法会将数组转化为纯字符串的形式,所以还需要将字符串转换成一维数组,字符串转换成一维数组的方法也有很多,在这里使用的是(...)扩展运算符。

function concatFlatten(array) {
    while (array.some(item => Array.isArray(item))) {
        array = [].concat(...array);
    }
    return array;
​
}
​
const concatArray = concatFlatten(myArray)
console.log(concatArray);
//[ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
1
2
3
4
5
6
7
8
9
10
11

# (4)使用Generator函数

使用Generator函数,也是可以实现数组扁平化的,但是这需要一些基本的知识基础。巧妙地使用了yield关键字和yield*表达式,如下所示:

var flat = function*(array) {
    for (var i = 0; i < array.length; i++) {
        if (Array.isArray(array[i])) {
            yield* flat(array[i]);
        } else {
            yield array[i];
        }
    }
};
const flatArray = [...flat(myArray)]
console.log(flatArray);
1
2
3
4
5
6
7
8
9
10
11

yieldyield*这两个表达式不一样,yield是关键字,紧接在其后面的是Generator函数返回的遍历器调用next方法得到的值;而yield*是表达式,有返回值,紧接在其后面的是可遍历对象,如数组,类数组,或者另一个Generator函数的执行表达式等,尽管接可遍历对象,但是值是一个一个赋值的,举个例子:

function* gen() {
    yield* [1, 2];
    yield* '34';
    yield* Array.from(arguments);
}
​
var iterator = gen(5, 6);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
​
//{ value: 1, done: false }
//{ value: 2, done: false }
//{ value: '3', done: false }
//{ value: '4', done: false }
//{ value: 5, done: false }
//{ value: 6, done: false }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

我们需要知道,Generator函数返回的对象是一个可遍历的对象,并且通过扩展运算符结构一个Generator函数,解构的是其value的值。

# (5)Array.reduce()

Array.reduce(total, value, index, array),该方法为数组中的每个元素按照顺序执行一个reducer函数,每次执行会将先前的计算结果作为参数传入,然后将结果汇总为单个返回值。

该方法的回调函数接收四个参数:

preval:上一次调用回调函数的返回值,在第一次调用时可指定初始值,那么preval就会等于这个初始值;否则初始值默认是数组的索引为0的元素。

curval:当前处理的元素,在第一次调用时如果指定了初始值,则curval就等于索引为0的元素;否则,为索引为1的元素。

curindex:当前处理元素的索引,如果指定了初始值,则索引就等于0;否则索引从1开始。

initval:初始值,可选参数。作为第一次调用回调函数时的preval的值。

现在就来试试水吧:

const flatArray = myArray.reduce((preval, curval, curindex) => {
    console.log('preval=', preval);
    console.log('curval=', curval);
    return preval.concat(curval)
}, []);
console.log(flatArray); 
//[ 1, 2, 3, 4, 5, [ 6, [ 7 ] ], 8, 9 ]
1
2
3
4
5
6
7

经过测试,扁平化后的结果并不是我们想要的。通过代码发现,单纯的使用该方法只可以扁平化一层数组,对于多层嵌套的数组就束手无策,而对于多层嵌套层的数组,需要进行递归,遍历深度,所以优化后:

function flatten (array, deep = 1) => {
    if (deep <= 0) return array;
    return array.reduce((preval, curval) => {
        return preval.concat(Array.isArray(curval) ? flatten(curval, deep - 1) : curval)
    }, [])
}
​
const flatArray = flatten(myArray, Infinity)
console.log(flatArray);
1
2
3
4
5
6
7
8
9

数组的扁平化,其实有很多种实现方法,不使用任何API也比较好实现,利用递归的实现,层层遍历既可:

const flatArray = []
const flatten = (array) => {
    for (let i in array) {
        if (Array.isArray(array[i])) { //每一项是否为数组
            flatten(array[i]) //继续遍历
        } else {
            flatArray.push(array[i])
        }
    }
    return flatArray
}
const res = flatten(myArray)
console.log(res);
1
2
3
4
5
6
7
8
9
10
11
12
13

其实,数组的扁平化实现方法颇多,如使用正则表达式方法,接下来,看看对象的扁平化。

# 二、对象的扁平化

什么是对象的扁平化?比如现在有这样一个对象:

const myObj = {
    a: 1,
    b: [1, 2, {c: true},[3]],
    d: {e: 2,f: 3},
    g: null,
    like: [6, 8, 9]
}
1
2
3
4
5
6
7

你可以理解数组的元素会是数组,对象的值也可以是对象或者数组,但是这个对象扁平化会是怎么样呢?对象的扁平化同样是需要递归遍历的,对象的扁平化是将对象的值中是对象的的键值对进行扁平化处理,即如果值为对象,就进行递归遍历。如上对象扁平化后的结果为:

const flatObj={
    a:1,
    b[0]:1,
    b[1]:2,
    b[3].c:true,
    b[4][0]:3,
    d.e:2,
    d.f:3,
    g:null,
    like[0]:6,
    like[1]:8,
    like[2]:9
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# (1)手写对象扁平化

核心思想:一旦遍历到值为对象的键值对,则立即遍历这个对象(递归遍历),并且保存键名,以便接下来维护一个新的键名。这里借助一个外部变量flag来定义最初始时的键名,以便于将维护好的键值一一对应。

function flatten(myObj) {
    const flatObj = {}
    let flag = null
​
    function formatKey(obj, keyName) {
        for (let key in obj) {
            if (typeof obj[key] === 'object' && obj[key] !== null) { //值为对象
                if (!keyName) {
                    formatKey(obj[key], key)
                } else {
                    if (Array.isArray(obj)) {
                        formatKey(obj[key], `${keyName}[${key}]`)
                    } else {
                        formatKey(obj[key], `${keyName}.${key}`)
                    }
                }
            } else { //值不为对象或者值为null
                if (!keyName) {
                    flatObj[key] = obj[key]
                } else {
                    if (Array.isArray(obj)) {
                        flatObj[`${keyName}[${key}]`] = obj[key]
                    } else {
                        flatObj[`${keyName}.${key}`] = obj[key]
                    }
                }
            }
        }
    }
​
    formatKey(myObj, flag)
    return flatObj
}
flatten(myObj)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

还有更多实现数组的扁平化和对象的扁平化,敬请补充~。

Last Updated: 12/18/2022, 7:24:59 PM
Faster Than Light
Andreas Waldetoft / Mia Stegmar