【前端面试:手写js系列】flat---数组扁平化
数组扁平化定义:在前端项目开发过程中,偶尔会出现层叠数据结构的数组,需要把多层数组转换为一级数组(即提取嵌套数组元素最终合并为一个数组),使其内容合并并且展开。
- 需求:多维数组 => 一维数组
let arr = [1, 2, [3, [4, 5, [6,7]]], 8]; // -> [1, 2, 3, 4, 5, 6, 7, 8]
let str = JSON.stringify(arr);
- 方法一:调用ES6中的flat方法
arr = arr.flat(Infinity);
//不传参数时,默认“拉平”一层;传入一个整数参数,整数即“拉平”的层数;
//Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
//传入 <=0 的整数将返回原数组,不“拉平”
//如果原数组有空位,flat()方法会跳过空位
- 手写一个数组扁平化flat方法
思路:
1、遍历数组的每一个元素;
2、判断元素是否为数组;
3、将是数组的元素进行展开。
遍历数组的方案:
for循环
for...of
for...in
forEach()
entries()
keys()
values()
reduce()
map()
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "小马同学" }];
//for循环遍历
for (var i = 0; i < arr.length; i++){
console.log(arr[i]);
}
//for...of遍历
for (let item of arr) {
console.log(item)
}
//for...in遍历
for (let item in arr) {
console.log(arr[item]);
}
//forEach遍历
arr.forEach(value = >{
console.log(value);
});
// entries()遍历
for (let [index, value] of arr.entries()) {
console.log(value);
}
// keys() 遍历
for (let index of arr.keys()) {
console.log(arr[index]);
}
// values() 遍历
for (let value of arr.values()) {
console.log(value);
}
//map() 遍历
arr.map(value = > {
console.log(value);
}
判断元素是否是数组的方案:
instanceof
constructor
object.prototype.toString.call
isArray
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "小马同学" }];
arr instanceof Array; // true
arr.constructor === Array; //true
object.prototype.toString.call(arr) === '[object Array]'; //true
Array.isArray(arr); //true
instanceof 操作符是假定只有一种全局环境,如果网页中包含多个框架,多个全局环境,如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。(所以在这种情况下会不准确)
typeof 操作符对数组取类型将返回 object
因为 constructor 可以被重写,所以不能确保一定是数组。
//constructor重写,判断字符串为数组
const str = 'abc';
str.constructor = Array;
str.constructor === Array
// true
将数组元素进行展开一层的方案:
扩展运算法 + concat(concat() 方法用于合并两个或多个数组,在拼接的过程中加上扩展运算符会展开一层数组)
concat + apply(主要是利用 apply 在绑定作用域时,传入的第二个参数是一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。也就是在调用 apply 函数的过程中,会将传入的数组一个一个的传入到要执行的函数中,也就是相当对数组进行了一层的展开。)
toString + split(不推荐使用 toString + split 方法,因为操作字符串是很危险的事情,如果数组中的元素所有都是数字的话,toString + split 是可行的,并且是一步搞定。)
const arr = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]], 5, "string", { name: "小马同学" }];
//扩展运算法 + concat
[ ].concat(...arr);
// concat + apply
[ ].concat.apply([ ], arr);
//以上两种方法,输出结果:
//[1, 2, 3, 4, 1, 2, 3, [1, 2, 3, [1, 2, 3]], 5, "string", { name: "小马同学" }];
//toString + split
const arr2 = [1, 2, 3, 4, [1, 2, 3, [1, 2, 3, [1, 2, 3]]]];
arr2.toString().split(',').map(v = > parseInt(v));
//[1, 2, 3, 4, 1, 2, 3, 1, 2, 3, 1, 2, 3]
最终手写的flat()方法为:
const arr = [1, 2, 3, 4, 5, [1, 2, [1, 2, 3, [4, 5]]], 6, 'flat方法', {name: '小马同学' }];
function flat(arr){
let arrResult = [];
for(let value of arr){
if(Array.isArray(value)){
arrResult.push(...arguments.callee(value)); //递归
}else{
arrResult.push(value);
}
}
return arrResult;
}
console.log(flat(arr));
//[1, 2, 3, 4, 5, 1, 2, 1, 2, 3, 4, 5, 6, 'flat方法', {name: '小马同学' }]
arguments.callee(value) = flat(value),为了解决函数的执行与函数名flat紧密耦合的情况。
扩展运算符是三个点(...),比较像 rest 参数的逆运算,作用是将一个数组转为用逗号分隔的参数序列。
- 方法二:repalce + split
arr = str.replace(/(\[|\]))/g, '').split(',');
- 方法三:replace + JSON.parse
str = str.replace(/(\[|\]))/g, '');
str = '[' + str +']';
arr = JSON.parse(str);
- 方法四:普通递归
let result = [];
let fn = function(arr){
for(let i = 0; i < arr.length; i++){
let item = arr[i]; //重点
if(Array.isArray(arr[i])){
fn(item); //递归
}else{
result.push(item);
}
}
}
- 方法五:利用reduce函数迭代
const arr = [1, 2, 3, 4, 5, [1, 2, [1, 2, 3, [4, 5]]], 6, 'flat方法', {name: '小马同学' }];
function flatten(arr){
return arr.reduce((pre,cur) =>{
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
console.log(flatten(arr));
//[1, 2, 3, 4, 5, 1, 2, 1, 2, 3, 4, 5, 6, 'flat方法', {name: '小马同学' }]
- 方法六:扩展运算符
let arr = [1, 2, 3, 4, [5, 6, [7, 8]]]; //这个方法数组中的元素只能是数字?
while (arr.some((item) => { return Array.isArray(item) })) {
arr = [].concat(…arr);
}
console.log(arr)
参考博客如下,注明一下出处,感谢大神们,希望自己可以多多练习,多多回顾:
三元博客
JS数组reduce()方法详解及高级技巧
数组flat方法实现