Skip to content
本页目录

数组相关

一、Array.prototype.flat() (ES10)

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

示例

1、扁平化嵌套数组

js
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]

相关题目

题目描述

已知如下数组:

js
let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];

编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

答案解析
js
let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
let newArr = [...new Set(arr.flat(Infinity))].sort((a, b) => {
  return a - b;
});
console.log(newArr);
//[1, 2, 3,  4,  5,  6, 7, 8, 9, 10, 11, 12, 13, 14]

替代方案

reduce + concat + isArray + 递归

js
let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];

function flatDeep(arr, d = 1) {
  return d > 0
    ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [])
    : arr.slice();
}

console.log(flatDeep(arr, Infinity));

2、扁平化与数组空项

flat() 方法会移除数组中的空项:

js
let arr4 = [1, 2, , 4, 5];
arr4.flat();
// [1, 2, 4, 5]

二、Array.prototype.includes() (ES7)

在 ES6 中我们有 String.prototype.includes() 可以查询给定字符串是否包含一个字符,而在 ES7 中,我们在数组中也可以用 Array.prototype.includes 方法来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false

js
const arr = [1, 3, 5, 2, '8', NaN, -0];
arr.includes(1); // true
arr.includes(1, 2); // false  该方法的第二个参数表示搜索的起始位置,默认为 0
arr.includes('1'); // false
arr.includes(NaN); // true
arr.includes(+0); // true

三、Array.prototype.reduce()

定义

reduce() 方法对数组中的每个元素执行一个提供的 callback 函数(升序执行),将其结果汇总为

callback 函数接收 4 个参数:

  • Accumulator (acc) (累计器)
  • Current Value (cur) (当前值)
  • Current Index (idx) (当前索引)
  • Source Array (src) (源数组)

语法

js
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])   // []内为可选参数

参数解析

callback —— 执行数组中每个值 (如果没有提供 initialValue 则第一个值除外)的函数,包含四个参数:

  • 1、accumulator —— 累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或 initialValue(见于下方)。
  • 2、currentValue —— 数组中正在处理的元素。
  • 3、index —— 可选,数组中正在处理的当前元素的索引。 如果提供了 initialValue,则起始索引号为 0,否则从索引 1 起始。
  • 4、array —— 可选,调用 reduce() 的数组。

initialValue —— 可选,作为第一次调用 callback 函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用 reduce 将报错。

简单示例:

js
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer)); // 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5)); // 15

注意点

DANGER

五、for 和 forEach 遍历数组的区别

  • for 循环遍历数组的时候会执行 length - 1 次块语句的代码。
  • forEach 循环遍历数组的时候会先拿到所有数组元素的 ‘key’ 值,然后对每个有 ‘key’ 值的数组元素进行操作。
  • 这和数组在 js 中怎么存储有关,数组在 js 中是对象。数组有下标,其实作为对象来说,数组的下标也是一个 ‘key’。
  • V8 中会对数组生成 数字索引和字符串索引两种索引,然后对不同的操作使用不同的索引。

示例如下:

for

js
var arr = [1];
arr[10000] = 1;
function a() {
  console.time();
  for (var i = 0; i < arr.length; i++) {
    console.log(1);
  }
  console.timeEnd();
}
a();
//执行结果为打印 10000 个 1。耗时 3562.258ms

forEach

js
var arr = [1];
arr[10000] = 1;
function b() {
  console.time();
  arr.forEach((item) => {
    console.log(2);
  });
  console.timeEnd();
}
b();
//执行结果为打印 2 个 2。耗时 2.726ms

六、判断一个变量是否为数组的方法

1、instanceof

  • instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。 使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。
js
[] instanceof Array; // true

但是 instanceof 只能判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。

js
[] instanceof Object; // true

2、Array.isArray()

  • 接受一个参数如果是 Array ,则返回 true,否则为 false。
js
// 下面的函数调用都返回 true
Array.isArray([]);
Array.isArray([1]);
Array.isArray(new Array());
Array.isArray(new Array('a', 'b', 'c', 'd'));
Array.isArray(Array.prototype);

// 下面的函数调用都返回 false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray('Array');
Array.isArray(true);
Array.isArray(false);
Array.isArray(new Uint8Array(32));
Array.isArray({ __proto__: Array.prototype });
  • Array.isArray()是 ES5 新增的方法,有些浏览器不支持。假如不存在 Array.isArray(),则在其他代码之前运行下面的代码将创建该方法。
js
if (!Array.isArray) {
  Array.isArray = function (arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

3、Object.prototype.toString.call()

每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型。但当除了 Object 类型的对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。

js
let jing = { a: 122 };
jing.toString(); //"[object Object]"
const an = ['Hello', 'An'];
an.toString(); // "Hello,An"
Object.prototype.toString.call(an); // "[object Array]"

这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。

js
Object.prototype.toString.call('An'); // "[object String]"
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call(Symbol(1)); // "[object Symbol]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(function () {}); // "[object Function]"
Object.prototype.toString.call({ name: 'An' }); // "[object Object]"

instanceof 和 isArray

当检测 Array 实例时, Array.isArray 优于 instanceof,因为 Array.isArray 能检测 iframes.

  • instanceof 操作符的问题在于,它假定只有一个全局环境。如果网页中包含多个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的 Array 构造函数。
  • 如果你从一个框架向另一个框架传入一个数组,那么传入的数组与在第二个框架中原生创建的数组分别具有各自不同的构造函数。

七、类数组转换为数组方法

1、Array.from()

js
let arr = document.getElementsByTagName('*'); //类数组
Array.from(arr);

2、ES6 ...展开运算符

... 扩展运算符,不过它只能作用于 iterable 对象,即拥有 Symbol(Symbol.iterator) 属性值

js
let arr = document.getElementsByTagName('*')  //类数组
[...arr]

3、ES5 中的方法

ES5 中可以借用 Array API 通过 call/apply 改变 this 或者 arguments 来完成转化。一切以数组为输入,并以数组为输出的 API 都可以来做数组转换。

  • Array (借用 arguments)
  • Array.prototype.concat (借用 arguments)
  • Array.prototype.slice (借用 this)
  • Array.prototype.map (借用 this)
  • Array.prototype.filter (借用 this)
js
let arr = document.getElementsByTagName('*'); //类数组
Array.apply(null, arr);
Array.prototype.concat.apply([], arr);
Array.prototype.slice.call(arr);
Array.prototype.map.call(arr, (x) => x);
Array.prototype.filter.call(arr, (x) => 1);

八、forEach 和 map 的区别

  • forEach 方法不会返回执行结果,而是 undefined

  • 也就是说,forEach 会修改原来的数组,而 map 方法会得到一个新的数组并返回。

看两行代码你就懂了:

js
[1, 2, 3].map((d) => d + 1); // [2, 3, 4];
[1, 2, 3].forEach((d) => d + 1); // undefined;

九、数组去重

方法一

先将原数组排序,在与相邻的进行比较,如果不同则存入新数组。

js
function unique(arr) {
  var fomArr = arr.sort();
  var newArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (fomArr[i] !== fomArr[i - 1]) {
      newArr.push(fomArr[i]);
    }
  }
  return newArr;
}

console.log(unique([1, 3, 3, 5, 5, 9, 8]));

方法二

利用数组的 indexOf 下标属性来查询

js
/**
 * @param {Array} arr
 */
function unique(arr) {
  let newArr = [];
  arr.forEach((item) => {
    if (newArr.indexOf(item) === -1) {
      //indexOf求数组中元素的索引
      newArr.push(item);
    }
  });
  return newArr;
}

console.log(unique([1, 2, 2, 3, 3, 4, 5, 5, 9, 9, 8]));

方法三

利用对象属性存在的特性,如果没有该属性则存入新数组。

js
/**
 * @param {Array} arr
 */
function unique(arr) {
  var newArr = [];
  var obj = {}; //对象字面量
  arr.forEach((item) => {
    if (!obj[item]) {
      obj[item] = 1;
      newArr.push(item);
    }
  });
  return newArr;
}

console.log(unique([1, 3, 5, 5, 6, 8, 8, 9]));

方法四

利用数组原型对象上的 includes 方法。

js
/**
 * @param {array} arr
 */
function unique(arr) {
  var newArr = [];
  arr.forEach((item) => {
    if (!newArr.includes(item)) {
      newArr.push(item);
    }
  });
  return newArr;
}
console.log(unique([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]));

方法五

利用数组原型对象上的 filter 方法。

js
// item 当前数组元素
// index 当前数组元素下表
// array 调用数组本身
function unique(array) {
  let res = array.filter((item, index, array) => {
    return array.indexOf(item) === index;
  });
  return res;
}

console.log(unique([1, 5, 6, 8, 4, 3, 5, 9, 8]));

方法六

利用 ES6 Set 数据结构。

js
// set 实现
// 怎么把一个类数组转换成一个数组 Array.from()  [...]
function unique(array) {
  // return Array.from(new Set(array));
  return [...new Set(array)];
}

console.log(unique([1, 5, 6, 8, 4, 3, 5, 9, 8]));

MIT Licensed