js 数组API复习

527 阅读15分钟

创建一个数组

我们在创建数组的过程可能会遇到很奇葩的问题。例如:

使用Array() 构造器

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

上面代码中,Array()方法没有参数、一个参数、三个参数时,返回的结果都不一样。这是因为使用Array() 构造器创建数组的时候 Array 构造器会根据给定的元素创建一个 JavaScript 数组,但是当仅有一个参数且为数字时除外

再来看看 Array.of()

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

Array.of()方法用于将一组值,转换为数组。这个方法的可以弥补数组构造函数Array()的不足。因为参数个数的不同,会导致Array()的行为有差异。

数组的静态方法

  • Array.from() 从类数组对象或者可迭代对象中创建一个新的数组实例
Array.from('foo');
// [ "f", "o", "o" ]
  • Array.isArray() 用来判断某个变量是否是一个数组对象
Array.isArray([1, 2, 3]);
// true
Array.isArray({foo: 123});
// false
Array.isArray("foobar");
// false
Array.isArray(undefined);
// false

  • Array.of() 根据一组参数来创建新的数组实例,支持任意的参数数量和类型
Array.of(7);       // [7]
Array.of(1, 2, 3); // [1, 2, 3]

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

数组循环执行效率

js常见的几种循环执行效率,由于实际测试有些许偏差所以我取10次测试结果的平均值来看各种循环的执行效率对比 (测试环境使用Chrome 81)。测试结果对比:for > forEach > while > map > for of > for in

以下是测试用例:

//测试用例
    const data = new Array(10000000).fill(0);
    console.log('-----普通for循环-------');
    console.time('for');
    const result_1 = [];
    for (let i = 0; i < data.length; i++) {
        result_1.push(data[i]);
    }
    console.timeEnd('for');
    console.log('-----执行完毕-------');


    console.log('-----for of循环-------');
    console.time('for_of');
    const result_2 = [];
    for (let item of data) {
        result_2.push(item);
    }
    console.timeEnd('for_of');
    console.log('-----执行完毕-------');


    console.log('-----for in-------');
    console.time('for_in');
    const result_3 = [];
    for (let key in data) {
        result_3.push(data[key]);
    }
    console.timeEnd('for_in');
    console.log('-----执行完毕-------');


    console.log('-----forEach-------');
    console.time('forEach');
    const result_4 = [];
    data.forEach((item) => {
        result_4.push(item);
    });
    console.timeEnd('forEach');
    console.log('-----执行完毕-------');


    console.log('-----map-------');
    console.time('map');
    const result_5 = [];
    data.map((item) => {
        result_5.push(item);
    });
    console.timeEnd('map');
    console.log('-----执行完毕-------');


    console.log('-----while-------');
    console.time('while');
    const result_6 = [];
    let count = 0
    while (data.length > count) {
        result_6.push(data[count]);
        count++
    }
    console.timeEnd('while');
    console.log('-----执行完毕-------');

数组的遍历方法

every()

用来测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。语法:

arr.every(callback(element[, index[, array]])[, thisArg])
参数说明
callbackcallback是用来测试每个元素的函数,它可以接收三个参数:element代表用于测试的当前值。index可选用于测试的当前值的索引。array可选。调用 every 的当前数组。
thisArg执行 callback 时使用的 this 值。

some()

some() 方法数组用于确定数组中有至少一个元素通过回调函数的测试就会返回true;所有元素都没有通过回调函数的测试返回值才会为false。如果用一个空数组进行测试,在任何情况下它返回的都是false。语法:

arr.some(callback(element[, index[, array]])[, thisArg])
参数说明
callbackcallback是在数组每一项上执行的函数,接收 3 个参数:element,数组中当前正在处理的元素。index可选,正在处理的元素在数组中的索引。array可选,some()被调用的数组。
thisArg执行 callback 时使用的 this 值。

filter()

filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。如果没有任何数组元素通过测试,则返回空数组。语法:

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])
参数说明
callbackcallback是用来测试数组的每个元素的函数。返回 true 表示该元素通过测试,保留该元素,返回false 则不保留。它接受以下三个参数: element,数组中当前正在处理的元素。index可选,正在处理的元素在数组中的索引。array可选,调用了 filter 的数组本身。
thisArg执行 callback 时使用的 this 值。

forEach()

forEach() 方法按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。返回值 undefined。语法:

arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
参数说明
callbackcallback是在数组每一项上执行的函数,接收 3 个参数:currentValue,数组中当前正在处理的元素。index可选,正在处理的元素在数组中的索引。array可选,数组本身。
thisArg执行 callback 时使用的 this 值。

map()

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

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
 // Return element for new_array 
}[, thisArg])
参数说明
callbackcallback是生成新数组元素的函数,使用三个参数:currentValue,数组中当前正在处理的元素。index可选,callback 数组中正在处理的当前元素的索引。array可选,map 方法调用的数组。
thisArg执行 callback 时使用的 this 值。

reduce()

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。语法:

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

参数说明:

  • callback: callback是执行数组中每个值 (如果没有提供 initialValue则第一个值除外)的函数,包含四个参数:accumulator,累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue。currentValue,数组中正在处理的元素。index 可选,数组中正在处理的当前元素的索引。如果提供了initialValue,则起始索引号为0,否则从索引1起始。array可选,调用reduce()的数组。
  • initialValue可选: 作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。

reduceRight()

reduceRight() 方法接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值。语法:

arr.reduceRight(callback(accumulator, currentValue[, index[, array]])[, initialValue])

参数说明:

  • callback: callback是执行数组中每个值 (如果没有提供 initialValue则第一个值除外)的函数,包含四个参数:accumulator,累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue。currentValue,数组中正在处理的元素。index 可选,数组中正在处理的当前元素的索引。如果提供了initialValue,则起始索引号为0,否则从索引1起始。array可选,调用reduceRight()的数组。
  • initialValue可选: 首次调用 callback 函数时,累加器 accumulator 的值。如果未提供该初始值,则将使用数组中的最后一个元素,并跳过该元素。如果不给出初始值,则需保证数组不为空。 否则,在空数组上调用 reduce 或 reduceRight 且未提供初始值(例如 [].reduce( (acc, cur, idx, arr) => {} ) )的话,会导致类型错误 TypeError: reduce of empty array with no initial value。

数组的查找

1.find()

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。语法:

arr.find(callback[, thisArg])
参数说明
callbackcallback是在数组每一项上执行的函数,接收 3 个参数:element,数组中当前正在处理的元素。index可选,正在处理的元素在数组中的索引。array可选,数组本身。
thisArg执行 callback 时使用的 this 值。

2.findIndex()

findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。若没有找到对应元素则返回-1。语法:

arr.findIndex(callback[, thisArg])
参数说明
callbackcallback是针对数组中的每个元素, 都会执行该回调函数, 执行时会自动传入下面三个参数: element,当前元素。index,当前元素的索引。array可选,调用findIndex的数组。
thisArg可选。执行callback时作为this对象的值.

可以修改原始数组的操作

1.fill()

fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。并且返回修改后的数组。语法:

arr.fill(value[, start[, end]])

参数说明:fill 方法接受三个参数 value, start 以及 end. start 和 end 参数是可选的, 其默认值分别为 0 和 this 对象的 length 属性值。

  • value:用来填充数组元素的值。
  • start:可选。起始索引,默认值为0。如果 start 是个负数, 则开始索引会被自动计算成为 length + start,其中 length 是 this 对象的 length 属性值。如果 end 是个负数, 则结束索引会被自动计算成为 length+end。
  • end :可选。终止索引,默认值为 this.length。

示例:

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

// Objects by reference.
var arr = Array(3).fill({}) // [{}, {}, {}];
// 需要注意如果fill的参数为引用类型,会导致都执行都一个引用类型
// 如 arr[0] === arr[1] 为true
arr[0].hi = "hi"; // [{ hi: "hi" }, { hi: "hi" }, { hi: "hi" }]

2.push()

push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。语法:

arr.push(element1, ..., elementN)

参数说明:

  • elementN:被添加到数组末尾的元素。

3.pop()

pop()方法从数组中删除最后一个元素,并返回该元素的值。此方法更改数组的长度(当数组为空时返回undefined)。语法:

arr.pop()

4.reverse()

reverse() 方法将数组中元素的位置颠倒,并返回该数组。数组的第一个元素会变成最后一个,数组的最后一个元素变成第一个。该方法会改变原数组。语法:

arr.reverse()

5.shift()

shift() 方法从数组中删除第一个元素,并返回该元素的值(如果数组为空则返回undefined )。此方法更改数组的长度。语法:

arr.shift()

6.unshift()

unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)。语法:

arr.unshift()

7.splice()

splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容(由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。)。此方法会改变原数组。语法:

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

参数

  • start 指定修改的开始位置(从0计数)。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位(从-1计数,这意味着-n是倒数第n个元素并且等价于array.length-n);如果负数的绝对值大于数组的长度,则表示开始位置为第0位。
  • deleteCount 可选, 整数,表示要移除的数组元素的个数。如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。如果 deleteCount 被省略了,或者它的值大于等于array.length - start(也就是说,如果它大于或者等于start之后的所有元素的数量),那么start之后数组的所有元素都会被删除。如果 deleteCount 是 0 或者负数,则不移除元素。这种情况下,至少应添加一个新元素。
  • item1, item2, ... 可选,要添加进数组的元素,从start 位置开始。如果不指定,则 splice() 将只删除数组元素。

8.sort()

sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的UTF-16代码单元值序列时构建的语法:

arr.sort([compareFunction])

参数

  • compareFunction 可选。用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。

不会修改原始数组的操作

1.concat()

concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。语法:

var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])

参数

  • valueN可选,数组和/或值,将被合并到一个新的数组中。如果省略了所有 valueN 参数,则 concat 会返回调用此方法的现存数组的一个浅拷贝。

2.join()

join() 方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。语法:

arr.join([separator])

参数

  • separator 可选,指定一个字符串来分隔数组的每个元素。如果需要,将分隔符转换为字符串。如果缺省该值,数组元素用逗号(,)分隔。如果separator是空字符串(""),则所有元素之间都没有任何字符。
var a = ['Wind', 'Rain', 'Fire'];
var myVar1 = a.join();      // myVar1的值变为"Wind,Rain,Fire"
var myVar2 = a.join(', ');  // myVar2的值变为"Wind, Rain, Fire"
var myVar3 = a.join(' + '); // myVar3的值变为"Wind + Rain + Fire"
var myVar4 = a.join('');    // myVar4的值变为"WindRainFire"

3.slice()

slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。语法:

arr.slice([begin[, end]])

参数

  • begin 可选,提取起始处的索引(从 0 开始),从该索引开始提取原数组元素。如果该参数为负数,则表示从原数组中的倒数第几个元素开始提取,slice(-2) 表示提取原数组中的倒数第二个元素到最后一个元素(包含最后一个元素)。如果省略 begin,则 slice 从索引 0 开始。如果 begin 超出原数组的索引范围,则会返回空数组。
  • end 可选,提取终止处的索引(从 0 开始),在该索引处结束提取原数组元素。slice 会提取原数组中索引从 begin 到 end 的所有元素(包含 begin,但不包含 end)。slice(1,4) 会提取原数组中从第二个元素开始一直到第四个元素的所有元素 (索引为 1, 2, 3的元素)。如果该参数为负数, 则它表示在原数组中的倒数第几个元素结束抽取。 slice(-2,-1) 表示抽取了原数组中的倒数第二个元素到最后一个元素(不包含最后一个元素,也就是只有倒数第二个元素)。如果 end 被省略,则 slice 会一直提取到原数组末尾。如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。

4.toString()

toString() 返回一个表示指定的数组及其元素的字符串。语法:

arr.toString()

数组的扁平化 [1, 2, [3, [4, 5]]] => [1, 2, 3, 4, 5]

  1. flat方法

arr.flat(Infinity)
  1. 遍历、递归方法

// 使用reduce
function flatten(arr) {
    return arr.reduce((pre, current) => {
        return pre.concat(Array.isArray(current) ? flatten(current) : current)
    }, [])
}

// forEach
function flatten(arr) {
    let result = []
    arr.forEach(item => {
        if(Array.isArray(item)) {
            result = result.concat(flatten(item))
        } else {
            result.push(item)
        }
    })
    return result
}

  1. 使用 toString 和 split
function flatten(arr) {
    //toString 方法连接数组并返回一个字符串,其中包含用逗号分隔的每个数组元素。
    //split 返回源字符串以分隔符出现位置分隔而成的一个 Array 
    return arr.toString().split(',').map(item => Number(item))
}
  1. 使用 join 和 split
function flatten(arr) {
    //join() 方法将数组作为字符串返回,默认使用“,”分割
    //split 返回源字符串以分隔符出现位置分隔而成的一个 Array 
    return arr.join(',').split(',').map(item => Number(item))
}

数组元素中对象的属性去重


let arr = [
    {"age": 13, "name": "s"},
    {"age": 14, "name": "sss"},
    {"age": 14, "name": "sddd"},
    {"age": 25, "name": "saaaa"},
]

// 利用对象的属性去重
function getData(arr) {
    let result = [], ob = {}
    arr.forEach(item => {
        if(!ob[item.age]) {
            result.push(item)
            ob[item.age] = true
        }
    })
    return result
}

let res = getData(arr) // [{"age":13,"name":"s"},{"age":14,"name":"sss"},{"age":25,"name":"saaaa"}]

复制数组

  1. 深拷贝实现
// 简单版本
function deepClone(val) {
    if(typeof val === "object" && val !== null){
        let result = Array.isArray(val)? [] : {}
        for(let key in val) {
            result[key] = deepClone(val[key])
        }
        return result
    }
    return val
}
  1. 扩展运算符实现
let arr = [
    {"age": 13, "name": "s"},
    {"age": 14, "name": "sss"},
    {"age": 25, "name": "saaaa"},
];

let arr1 = [...arr]
arr1[0] = true    
console.log(arr1);
// [
//     true,
//     {"age": 14, "name": "sss"},
//     {"age": 25, "name": "saaaa"},
// ]

数组交集

使用 filter 和 includes

let arr1 = [1, 2, 3, 3, 4, 5]
let arr2 = [3, 4, 6, 7]
let arr3 = [...new Set(arr1)].filter(item => arr2.includes(item))   //[3, 4]

数组元素是对象的时候各种操作

const arr1 = [
    { id: '11'},
    { id: '12'}, 
    { id: '13'}
];

const arr2 = [
    { id: '12'},
    { id: '13'}, 
    { id: '14'}
];
  1. 数组交集(对于给定的两个集合,返回一个包含两个集合中共有元素的新集合。)
function intersection(arr1, arr2) {
    return arr1.filter(item => arr2.some(item2 => item2.id === item.id))
}
let result = intersection(arr1, arr2)   //[{ id: '12'},{ id: '13'}]
  1. 数组差集(对于给定的两个集合,返回一个包含所有存在于第一个集合且不存在于第二个集合的元素的新集合。 )
function difference(arr1, arr2) {
    return arr1.filter(item => arr2.every(item2 => item2.id !== item.id))
}
let result = difference(arr1, arr2)   //[{ id: '11'}]
  1. 数组并集(对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。)
function getUnion(arr1, arr2) {
    let newArr = arr1.concat(arr2)
    let result = []
    let obj = {}
    newArr.forEach(item => {
        if(!obj[item.id]) {
            result.push(item)
            obj[item.id] = true
        }
    })
    obj = null
    return result
}

let result = getUnion(arr1, arr2)

for...in 与for...of的相同点和区别

相同点:

无论是for...in还是for...of语句都是迭代一些东西。它们之间的主要区别在于它们的迭代方式。

区别:

for...in 语句以任意顺序迭代对象的可枚举属性

for...of 语句遍历可迭代对象定义要迭代的数据。

以下示例显示了与Array一起使用时,for...of循环和for...in循环之间的区别。


Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';

for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}

for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}