Skip to content

Commit addb46f

Browse files
committed
添加JavaScript专题之数组扁平化
1 parent 09f88f3 commit addb46f

File tree

1 file changed

+257
-0
lines changed

1 file changed

+257
-0
lines changed
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
# JavaScript专题之数组扁平化
2+
3+
## 扁平化
4+
5+
数组的扁平化,就是将一个嵌套多层的数组 array (嵌套可以是任何层数)转换为只有一层的数组。
6+
7+
举个例子,假设有个名为 flatten 的函数可以做到数组扁平化,效果就会如下:
8+
9+
```js
10+
var arr = [1, [2, [3, 4]]];
11+
console.log(flatten(arr)) // [1, 2, 3, 4]
12+
```
13+
14+
知道了效果是什么样的了,我们可以去尝试着写这个 flatten 函数了
15+
16+
## 递归
17+
18+
我们最一开始能想到的莫过于循环数组元素,如果还是一个数组,就递归调用该方法:
19+
20+
```js
21+
// 方法 1
22+
var arr = [1, [2, [3, 4]]];
23+
24+
function flatten(arr) {
25+
var result = [];
26+
for (var i = 0, len = arr.length; i < len; i++) {
27+
if (Array.isArray(arr[i])) {
28+
result = result.concat(flatten(arr[i]))
29+
}
30+
else {
31+
result.push(arr[i])
32+
}
33+
}
34+
return result;
35+
}
36+
37+
38+
console.log(flatten(arr))
39+
```
40+
41+
## toString
42+
43+
如果数组的元素都是数字,那么我们可以考虑使用 toString 方法,因为:
44+
45+
```js
46+
[1, [2, [3, 4]]].toString() // "1,2,3,4"
47+
```
48+
49+
调用 toString 方法,返回了一个逗号分隔的扁平的字符串,这时候我们再 split,然后转成数字不就可以实现扁平化了吗?
50+
51+
```js
52+
// 方法2
53+
var arr = [1, [2, [3, 4]]];
54+
55+
function flatten(arr) {
56+
return arr.toString().split(',').map(function(item){
57+
return +item
58+
})
59+
}
60+
61+
console.log(flatten(arr))
62+
```
63+
64+
然而这种方法使用的场景却非常有限,如果数组是 [1, '1', 2, '2'] 的话,这种方法就会产生错误的结果。
65+
66+
## reduce
67+
68+
既然是对数组进行处理,最终返回一个值,我们就可以考虑使用 reduce 来简化代码:
69+
70+
```js
71+
// 方法3
72+
var arr = [1, [2, [3, 4]]];
73+
74+
function flatten(arr) {
75+
return arr.reduce(function(prev, next){
76+
return prev.concat(Array.isArray(next) ? flatten(next) : next)
77+
}, [])
78+
}
79+
80+
console.log(flatten(arr))
81+
```
82+
83+
## ...
84+
85+
ES6 增加了扩展运算符,用于取出参数对象的所有可遍历属性,拷贝到当前对象之中:
86+
87+
```js
88+
var arr = [1, [2, [3, 4]]];
89+
console.log([].concat(...arr)); // [1, 2, [3, 4]]
90+
```
91+
92+
我们用这种方法只可以扁平一层,但是顺着这个方法一直思考,我们可以写出这样的方法:
93+
94+
```js
95+
// 方法4
96+
var arr = [1, [2, [3, 4]]];
97+
98+
function flatten(arr) {
99+
100+
while (arr.some(item => Array.isArray(item))) {
101+
arr = [].concat(...arr);
102+
}
103+
104+
return arr;
105+
}
106+
107+
console.log(flatten(arr))
108+
```
109+
110+
## undercore
111+
112+
那么如何写一个抽象的扁平函数,来方便我们的开发呢,所有又到了我们抄袭 underscore 的时候了~
113+
114+
在这里直接给出源码和注释,但是要注意,这里的 flatten 函数并不是最终的 _.flatten,为了方便多个 API 进行调用,这里对扁平进行了更多的配置。
115+
116+
```js
117+
/**
118+
* 数组扁平化
119+
* @param {Array} input 要处理的数组
120+
* @param {boolean} shallow 是否只扁平一层
121+
* @param {boolean} strict 是否严格处理元素,下面有解释
122+
* @param {Array} output 这是为了方便递归而传递的参数
123+
* 源码地址:https://github.com/jashkenas/underscore/blob/master/underscore.js#L528
124+
*/
125+
function flatten(input, shallow, strict, output) {
126+
127+
// 递归使用的时候会用到output
128+
output = output || [];
129+
var idx = output.length;
130+
131+
for (var i = 0, len = input.length; i < len; i++) {
132+
133+
var value = input[i];
134+
// 如果是数组,就进行处理
135+
if (Array.isArray(value)) {
136+
// 如果是只扁平一层,遍历该数组,依此填入 output
137+
if (shallow) {
138+
var j = 0, len = value.length;
139+
while (j < len) output[idx++] = value[j++];
140+
}
141+
// 如果是全部扁平就递归,传入已经处理的 output,递归中接着处理 output
142+
else {
143+
flatten(value, shallow, strict, output);
144+
idx = output.length;
145+
}
146+
}
147+
// 不是数组,根据 strict 的值判断是跳过不处理还是放入 output
148+
else if (!strict){
149+
output[idx++] = value;
150+
}
151+
}
152+
153+
return output;
154+
155+
}
156+
```
157+
158+
解释下 strict,在代码里我们可以看出,当遍历数组元素时,如果元素不是数组,就会对 strict 取反的结果进行判断,如果设置 strict 为 true,就会跳过不进行任何处理,这意味着可以过滤非数组的元素,举个例子:
159+
160+
```js
161+
var arr = [1, 2, [3, 4]];
162+
console.log(flatten(arr, true, true)); // [3, 4]
163+
```
164+
165+
那么设置 strict 到底有什么用呢?不急,我们先看下 shallow 和 strct 各种值对应的结果:
166+
167+
* shallow true + strict false :正常扁平一层
168+
* shallow false + strict false :正常扁平所有层
169+
* shallow true + strict true :去掉非数组元素
170+
* shallow false + strict true : 返回一个[]
171+
172+
我们看看 underscore 中哪些方法调用了 flatten 这个基本函数:
173+
174+
## _.flatten
175+
176+
首先就是 _.flatten:
177+
178+
```js
179+
_.flatten = function(array, shallow) {
180+
return flatten(array, shallow, false);
181+
};
182+
```
183+
184+
在正常的扁平中,我们并不需要去掉非数组元素。
185+
186+
## _.union
187+
188+
接下来是 _.union:
189+
190+
该函数传入多个数组,然后返回传入的数组的并集,
191+
192+
举个例子:
193+
194+
```js
195+
_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
196+
=> [1, 2, 3, 101, 10]
197+
```
198+
199+
如果传入的参数并不是数组,就会将该参数跳过:
200+
201+
```js
202+
_.union([1, 2, 3], [101, 2, 1, 10], 4, 5);
203+
=> [1, 2, 3, 101, 10]
204+
```
205+
206+
为了实现这个效果,我们可以将传入的所有数组扁平化,然后去重,因为只能传入数组,这时候我们直接设置 strict 为 true,就可以跳过传入的非数组的元素。
207+
208+
```js
209+
// 关于 unique 可以查看《JavaScript专题之数组去重》[](https://github.com/mqyqingfeng/Blog/issues/27)
210+
function unique(array) {
211+
return Array.from(new Set(array));
212+
}
213+
214+
_.union = function() {
215+
return unique(flatten(arguments, true, true));
216+
}
217+
```
218+
219+
## _.difference
220+
221+
是不是感觉折腾 strict 有点用处了,我们再看一个 _.difference:
222+
223+
语法为:
224+
225+
> _.difference(array, *others)
226+
227+
效果是取出来自 array 数组,并且不存在于多个 other 数组的元素。跟 _.union 一样,都会排除掉不是数组的元素。
228+
229+
举个例子:
230+
231+
```js
232+
_.difference([1, 2, 3, 4, 5], [5, 2, 10], [4], 3);
233+
=> [1, 3]
234+
```
235+
236+
实现方法也很简单,扁平 others 的数组,筛选出 array 中不在扁平化数组中的值:
237+
238+
```js
239+
function difference(array, ...rest) {
240+
241+
rest = flatten(rest, true, true);
242+
243+
return array.filter(function(item){
244+
return rest.indexOf(item) === -1;
245+
})
246+
}
247+
```
248+
249+
注意,以上实现的细节并不是完全按照 underscore,具体细节的实现感兴趣可以[查看源码](https://github.com/jashkenas/underscore/blob/master/underscore.js#L528)
250+
251+
## 专题系列
252+
253+
JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)
254+
255+
JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
256+
257+
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

0 commit comments

Comments
 (0)