Array全概括(附数组面试大全)(更新中。。。)

Array基础属性和方法

Array是js中一个非常重要的对象,以至于面试中,总是频频出现,什么数组去重啊、排序啊、减少嵌套深度啊balabal~~
所以我要在这里一次性把所有关于数组的问题和方法总结,方便后面查看。

注意
* XX意味着这个题是重点,面试必问的哪种

属性

length

  • length是Array的实例属性。值是 0 到 232-1的整数
  • 可以设置length属性值来截断或扩展数组,若是扩展数组,那么扩展位的元素的值为undefined
  • length 并不能代表数组中定义值的个数。例如:
var a = [0,1,2];
a.length = 10;
console.log(a); // [0,1,2]
console.log(a.length); // 10

Prototype

Array.prototype 是 Array 构造函数的原型对象,可以通过向 Array.prototype 添加属性和方法,从而对所有的Array对象添加新的属性和方法。
所有我们熟悉的数组的方法,例如:pushconcatshiftfillslice…等等方法,均是在Array.prototype 上的方法。
除了可以更改 Array 实例的方法,还有一个点需要了解:Array.prototype 本身是一个数组

不改变原数组的方法

1. Array.isArray()

确定传递的值是否是一个Array。

Array.isArray([1,2,3]);  // true
Array.isArray(new Array());  //  true
Array.isArray('arr')  // false

这里就要说一个面试必问的考点

面试题
* 判断数组
  • typeof ❌
    typeof是检测基本数据类型的最佳工具,凡事typeof数组只能返回object,不能判断出数组。
  • instanceof ⭕️
    instanceof是专门来检测引用类型的值的类型的工具,它的原理是根据给定变量的原型链向上匹配。所以instanceof可以用来判断数组:
	[1,2,3] instanceof Array // true
  • Array.isArray() ⭕️
    由于instanceof是假定只有一个全局执行环境,所以当存在两种或以上全局执行环境的时候,就会出现问题。所以,出现了Array.isArray()方法。
  • Object.prototype.toString() ⭕️
    Array.isArray()因为是在ES5中才添加的方法,在IE8以下的浏览器都不支持,所以还有一种稳妥的方法就是Object.prototype.toString()方法。在任何值上调用Object原生的toString方法都会返回[object NativeConstructorName]格式的字符串。每个类在内部都有一个[[Class]]属性,这个属性中就指定了上述字符串中的构造函数名。
	Object.prototype.toString.call([1,2,3]) === '[object Array]'  // true

2. Array.of() (ES6)

Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。

Array.of(7);       // [7] 
Array.of(1, 2, 3); // [1, 2, 3]

Array(7);          // [ , , , , , , ]
Array(1, 2, 3);    // [1, 2, 3]

3. concat()

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。这个新数组中的元素是原有数组的元素的浅拷贝

const a = [0, 1, 2];
const b = ['a', 'b', 'c'];
const newArr1 = a.concat(b); // [0, 1, 2, 'a', 'b', 'c']

const c = [true];
const newArr2 = a.concat(b, c) // [0, 1, 2,  'a', 'b', 'c', true]

const d = [3, [10]];
const newArr3 = a.concat(d); // [0, 1, 2, 3, [10]]
// newArr3 与 d 数组中的[10]项,均为引用对象,且引用的是相同的对象
newArr3[4][0] = 9;	// [0, 1, 2, 3, [9]]
console.log(newArr3) // [0, 1, 2, 3, [9]]

4. every()

every()方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。所有元素都符合指定函数的测试,则返回true。如果发现了一个不符合测试的元素,则立即返回false,不再往下遍历。

注意:若收到一个空数组,此方法在一切情况下都会返回 true。

const isBelowThreshold = value => {
	console.log(value);
	return value < 40;
}
const array1 = [1,2,54,44,33,22];
console.log(array1.every(isBelowThreshold));
// 1
// 2
// 54  ---在找到大于40的值以后,直接返回false,不再进行下面值的比较
// false

const array2 = [22,23,34];
console.log(array2.every(isBelowThreshold));
// 22
// 23
// 34
// true ---全部符合,返回true

5. find()

find()方法返回数组中满足测试函数的第一项的值。否则返回undefined。

arr.find(callback[, thisArg)

  • callback 在数组每一项上执行的函数,接受三个参数
    • element 当前遍历到的元素
    • index(可选) 当前遍历到的索引
    • array (可选) 数组本身
  • thisArg 执行回调时用作this的对象
const arr = [
	{name: '路飞', proportion: 88},
	{name: '索隆', proportion: 48},
	{name: '乌索普', proportion: 38}
];
const newArr1 = arr.find(item => item.proportion < 50);

console.log(newArr1); // {name: "索隆", proportion: 48}

find()不修改原数组。

在调用回调函数(用来筛选的函数)时,就会确定数组的索引范围,因此,在find方法开始执行之后,添加到数组的新元素不会被访问到;未被访问的元素被修改,当被访问的时候,访问到的值是当前值;未被访问的元素被删除,仍然会被访问,只不过其值是undefined。

// 仍然使用上面的arr
const newArr2 = arr.find((item, index) => {
	if (index === 0) {
		arr[3] = {name: '娜美', proportion: 999};
	}
	return item.proportion > 99;
});
console.log(arr); // [{...}, {...}, {...}, {name: "娜美", proportion: 999}]
console.log(newArr2); // undefined   (新添加的元素不会被访问)

const newArr3 = arr.find((item, index) => {
	if (index === 0) {
		delete arr[1];
	}
	return item && item.proportion < 50;
});
console.log(arr); // [{…}, empty, {…}, {…}]
console.log(newArr3); // {name: "乌索普", proportion: 38}

6. findIndex()

findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。其余规则同上。

const arr = [1, 3, 4, 11, 5];
const newIndex = arr.findIndex(item => item > 10);

console.log(newIndex); // 3

7. flat()

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

var newArray = arr.flat([depth])

  • depth(可选)指定要提取嵌套数组的结构深度。默认值为1。
var arr1 = [1, 2, , 4, [5, [6, 7]]];
console.log(arr1.flat()); // [1, 2, 4, 5, [6, 7]]

修改原数组的方法

1. copyWithin()(ES6)

copyWithin() 方法浅复制数组的一部分到同一数组中的另一个位置,并返回它,不会改变原数组的长度。

arr.copyWithin(target[, start[, end]])
  • target: 目标位置下标
  • start、end:要复制的开始和结束下标 (包括开始位置,不包括结束位置)
const num = [0, 1, 2, 3, 4, 5, 6, 7, {name: '张三'}, 9];

// 1. 复制下标为6到9的数组元素(不包括下标为9的元素)到下标为2开始的位置
num.copyWithin(2, 6, 9);
// [0, 1, 6, 7, {name: '张三'}, 5, 6, 7, {name: '张三'}, 9]

// 2. 如果数组中包含的对象在复制时,为浅复制
num[8].age = 18;
// [0, 1, 6, 7, {name: '张三', age: 18}, 5, 6, 7, {name: '张三', age: 18}, 9]

2. fill()

fill()方法将传入值填充数组中从起始索引到终止索引内的全部元素。(不包括终止索引)。会修改原数组,但不会改变原数组的长度。

array.fill(value[, start[, end]])
  • value::用来填充数组元素的值
  • start(可选):起始索引,默认值为0
  • 终止索引(可选):默认值为this.length(数组的长度)
const array1 = [1, 2, 3, 4, 5];

console.log(array1.fill(0, 2, 4));
// 输出:[1, 2, 0, 0, 5]

console.log(array1.fill(9, 1)); // end被省略时,默认值是数组的长度,在此处为5
// 输出:[1, 9, 9, 9, 9]

console.log(array1.fill(8)); // start被省略时,默认值为0
// 输出:[8, 8, 8, 8, 8]

console.log(array1.fill(7, -4, -1)); // (1)
// 输出:[8, 7, 7, 7, 8]

console.log(array1.fill(6, 1, 8)); // (2)
// 输出:[8, 6, 6, 6, 6]

console.log(array1.fill({})); // (3)
// 输出:[{}, {}, {}, {}, {}]
console.log(array1[0] === array1[1]);
// 输出:true
array1[0].name = '张三';
console.log(array1);
// 输出:[{name: '张三'}, {name: '张三'}, {name: '张三'}, {name: '张三'}, {name: '张三'}]

[].fill.call({ length: 3 }, 4); // (4)
// {0: 4, 1: 4, 2: 4, length: 3}

需要注意的点

  • (1)如果start是负数,则开始索引会被自动计算成length+start;如果end是负数,则结束索引会被自动计算成length+end
  • (2)fill()会改变原数组,但不会改变原数组的长度
  • (3)当一个对象被传递给 fill方法的时候, 填充数组的是这个对象的引用
  • (4)fill()不要求this是数组,可以指定其他的对象作为fill的this对象。
面试题
mock快速生成列表数据

再使用mock模拟接口数据的时候,尤其是在模拟table列表数据的时候,经常需要模拟很多条,一条一条的写太麻烦了,使用fill可以快速生成我们想要的不同类型的数据。

const classNums = ['一班', '二班', '三班', '四班'];
Array(4).fill(null).map((item, index) => {
	id: 'ID' + index,
	age: parseInt(Math.random() * 30, 10),
	classNum: classNums[parseInt(Math.random() * 4, 10)]
});

遍历方法

以下的方法是可以对数组的元素进行挨个遍历访问的方法。

1. Array.from() (ES6)

Array.from() 方法从一个类似数组或可迭代对象(部署了Iterator接口)中创建一个新的数组实例。语法如下:

Array.from(arrayLike[, mapFn[, thisArg]])
  • arrayLike (必选)想要转换成数组的类数组
  • mapFn (可选)在生成的新数组上执行一次map方法后返回
  • thisArg (可选)执行回调函数 mapFn 时的 this 对象

例子:

Array.from({0: '张三', 1: '李四', length: 2});  // ['张三', '李四']

Array.from(new Set(['foo', 'boo']));  // ['foo', 'boo']

Array.from(new Map([[1, 2], [2, 4], [4, 8]]));  // [[1, 2], [2, 4], [4, 8]]

Array.from([1, 2, 3], x => x + x);   // [2, 4, 6]

注意的点

  • Array.from()是返回一个新的数组,并没有改变原有的类数组或者对象
  • 在创建新的数组实例时,对类数组或可迭代对象中的每一项都是浅复制
  • 类数组的项数与length不对应时,生成的新数组会根据类数组的key进行undefined填充,以及多余项删除。
let obj = {age: 18};

let a = {1: '李四', 2: obj, 3: '王五', length: 3};
let a1 = Array.from(a);	// [undefined, '李四', {age: 18}]

obj.age = 30;

console.log(a); // {1: '李四', 2: {age: 30}, 3: '王五', length: 3}
console.log(a1); // [undefined, '李四', {age: 30}]

面试题
(1. 数组的去重
function combine(arr){ 
    return Array.from(new Set(arr));
} 

var m = [1, 2, 2]; 
console.log(combine(m));  // [1, 2]

考点: 一个是Set结构的实例是不重复值的集合,然后再用Array.from()将set实例转为数组。

(2. 生成指定长度的序列数组
function range(start, length, range) {
	return Array.from({length: length}, (_, i) => (range * i) + start);
}
range(4, 6, 3); // [4, 7, 10, 13, 16, 19]

注意:用Array.from()来生成指定长度的序列数组的方法性能并不是很好,for循环和Array().join.split都比它更好。

(3. 转换字符串
Array.from('\u4e0d\u4f1a\u9a91\u626b\u628a\u7684\u5c0f\u5973\u5deb');
// ["不", "会", "骑", "扫", "把", "的", "小", "女", "巫"]

Array.from('hello');
// ["h", "e", "l", "l", "o"]

2. entries() (ES6)

entries() 方法返回一个新的Array Iterator对象,该对象包含数组中每个索引的键/值对。就相当于把这个数组变成一个迭代器。

迭代器是一个对象,它定义了一个序列,并在终止时可能返回一个返回值。通过使用next() 方法返回具有两个属性的对象: value,这是序列中的 next 值;和 done ,如果已经迭代到序列中的最后一个值,则它为 true
一旦创建,迭代器对象可以通过重复调用next()显式地迭代。 迭代一个迭代器被称为消耗了这个迭代器,因为它通常只能执行一次。 在产生终止值之后,对next()的额外调用应该继续返回{done:true}

const array1 = ['a', 'b', 'c'];
const iterator1 = array1.entries();

console.log(Object.prototype.toString.call(iterator1));
// [object Array Iterator]
console.log(iterator1.next().value);
// { value: [0, "a"], done: false }
console.log(iterator1.next().value);
// { value: [1, "b"], done: false }
console.log(iterator1.next().value);
// { value: [2, "c"], done: false }
// 以上就是产生了终止值,所以再进行 next 方法调用的话,就不会再返回value了,只能返回{done:true}
console.log(iterator1.next().value);
//{ value: undefined, done: true }

除了使用next()方法,还可以是...运算符展开迭代器,但也算是消耗了这个迭代器。再使用next调用的话,就只能返回{done:true}

console.log([...iterator1]);
// [[0, "a"], [1, "b"], [2, "c"]]

console.log(iterator1.next().value);
// { value: undefined, done: true }

console.log([...iterator1]);
// []

除了扩展运算符,还可以使用for…of循环。与上同理,循环完毕,也是消耗完了这个迭代器。

for (let e of iterator1) {
    console.log(e);
}
// [0, "a"] 
// [1, "b"] 
// [2, "c"]

console.log(iterator1.next().value);
// { value: undefined, done: true }

3. filter()

filter() 通过指定函数筛选数组的每一项,并将符合条件的元素集合成新的数组返回。

var newArr = arr.filter(callback(element[, index[, array]])[, thisArg]);

  • callback: 筛选函数,返回true表示该元素符合条件,保留。反之则不保留。接受三个参数。
    • element: 数组中当前正在处理的元素
    • index(可选): 正在处理的元素在数组中的索引
    • array(可选): 调用了filter的数组本身
  • thisArg(可选): 执行callback时,用于this的值
const arr = [12, 3, 4, 56, 7];
console.log(arr.filter(item => item > 10));
// [12, 56]

4. map()

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

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
// Return element for new_array
}[, thisArg])

  • callback: 生成新数组元素的函数,使用三个参数:
    • currentValue: callback 数组中正在处理的当前元素。
    • index(可选): callback 数组中正在处理的当前元素的索引。
    • array(可选): map 方法调用的数组。
  • thisArg(可选): 执行 callback 函数时值被用作this。
var numbers = [1, 4, , 9];
var doubles = numbers.map(function(num) {
  return num * 2;
});

// doubles数组的值为: [2, 8,, 18]
// numbers数组未被修改: [1, 4,, 9]
重点
  • callback函数只会在有值的索引上被调用,那些从来没被赋过值或者使用delete删除的索引则不会被调用。也就是说如果被map调用的数组是离散的,那新数组也将是离散的,保持相同的索引为空。
  • 因为map会生成一个新数组,也不修改原数组(虽然可以在callback执行时改变原数组,但是强烈建议不要修改原数组!!!)。
  • 当你不准备使用新数组,或者也没有从回调函数中返回值的时候,使用map是违背设计初衷的。请用forEach或者for-of替代。
  • 如果有熊孩子非得在回调函数中,不返回任何值,那么生成的新的数组,则为与原数组相同长度,每一项为undefined。如下:
const arr = [1, 4, 9].map(item => {});
// arr输出为 [undefined, undefined, undefined]
面试题
* 经典面试题

以下代码会输出什么?

["1", "2", "3"].map(parseInt);

我们先不说答案,先来分析一下。
parseInt是可以接收接收两个参数,第一个是要被解析的值,第二个是表示被解析值的基数(2-36之间)。(在基数为0,或者undefined的时候,在字符串不以‘0x’开头,则基础默认为10(十进制))。
而map传递是3个值,所以上面的代码其实是下面👇代码的简写:

["1", "2", "3"].map((value, index) => parseInt(value, index));
// 实际执行过程就是以下:
parseInt('1', 0) // 1 (基数为0的时候,默认基数为10)
parseInt('2', 1) // NaN (基数是2-36之间的整数)
parseInt('3', 2) // NaN (二进制的有效数字只有 0、1)

那么想要实现输出[1, 2, 3],有以下解决办法。

["1", "2", "3"].map(value => parseInt(value));
["1", "2", "3"].map(Number);

5. flatMap()

flatMap()方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与 map 连着深度值为1的 flat几乎相同,但 flatMap 通常在合并成一种方法的效率稍微高一些。一些需要注意的点和

var new_array = arr.flatMap(function callback(currentValue[, index[, array]]) {
// return element for new_array
}[, thisArg])

  • callback: 生成新数组元素的函数,使用三个参数:
    • currentValue: callback 数组中正在处理的当前元素。
    • index(可选): callback 数组中正在处理的当前元素的索引。
    • array(可选): map 方法调用的数组。
  • thisArg(可选): 执行 callback 函数时值被用作this。
var arr1 = [1, 2, 3, 4];

arr1.map(x => [x * 2]); 
// [[2], [4], [6], [8]]

arr1.flatMap(x => [x * 2]);
// [2, 4, 6, 8]
// 相当于map连着深度为1的flat
arr1.map(x => [x * 2]).flat(1);
// [2, 4, 6, 8] 

参考文档

Javascript 高级程序设计(第3版)
MDN web docs
阮一峰的ECMAScript6 入门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值