javascript之Array对象回顾

923 阅读16分钟

  最近在回顾javascript的知识点,花了几天的空余时间重新学习了一下MDN中的Array对象,对Array对象有了新的认识和理解,所以决定将这些温故知新的内容大致的整理一下并记录下来,也在于鞭策自己继续学习。

  首先,我们知道Array是引用数据类型,其本质是保存在堆内存中的对象,在栈内存中保存的是对象在堆内存中的引用地址。MDN中数组的描述如下:数组是一种类列表对象。数组对象有三个属性:lengthconstructorprotoType

  • length:表示数组的长度,即数组所包含的元素个数
  • constructor:表示创建数组对象的构造函数,所有数组实例的constructor都是Array
  • protoType:表示数组实例的原型对象

  数组对象的方法有很多,这里笔者根据自己的理解归纳了9大类,逐一针对各方法的语法及使用做了简要的介绍。

1.数组的创建

  我们在使用js创建数组时,通常会通过数组直接量形式或构造函数Array来创建,而ES6新增了两种创建数组的方法:Array.ofArray.from

  • 数组直接量形式创建

    const arr = []; // 空数组
    const arr1 = [1,2,3,4,5];
    
  • 构造函数Array实例化

    // 1.直接实例化
    const arr = new Array(1,2,3,4,5);
    console.log(arr); // [1,2,3,4,5]
    
    // 2.实例化后赋值
    const arr1 = new Array(); 
    arr1[0] = 1;
    arr1[1] = 2;
    arr1[2] = 3;
    arr1[3] = 4;
    arr1[4] = 5;
    console.log(arr1); // [1,2,3,4,5]
    

  使用Array构造函数创建数组时,会存在一个比较奇怪的现象:构造函数在传入单个参数为整数时,所创建的数组为空数组,且数组的长度为该整数,若传入的单个参数为字符串时,所创建的数组才是包含该参数的长度为1的数组。

// 构造函数Array传入单个整数5时,此时创建的数组为[],且length为5
const arr = new Array(5);
console.log(arr)    // [,,,,,] 这里是指5个空位的空数组
console.log(arr[0]) // undefined

// 构造函数Array传入单个参数为'5',此时创建的数组为['5'];
const arr1 = new Array('5');
console.log(arr1) // ['5']

  为了避免Array构造函数创建数组时产生的歧义,ES6新增了Array.of()方法来创建数组

  • Array.of()方法创建

    语法:Array.of(...args),任意个参数按顺序成为创建数组的元素

    Array.of()方法可以创建可变参数的数组实例,其不必考虑参数的数量或类型,所创建的数组元素即为所传入的参数。

    const arr = Array.of(5);
    console.log(arr)  // [5]
    const arr1 = Array.of(1,2,3,4,5);
    console.log(arr1) // [1,2,3,4,5]
    const arr2 = Array.of(undefined);
    console.log(arr2) // [undefined]
    

  ES6还新增了Array.from()方法,可以从类数组或可迭代对象创建一个新的,浅拷贝的数组实例。

  • Array.from()方法创建

    语法:Array.from(arrayLike,mapFn,thisArg)

    其中,arrayLike为类数组对象(拥有length属性和若干索引属性的对象)或可迭代对象(SetMaparguments等),mapFn为可选参数,创建的新数组的每个元素都会执行该回调函数,thisArg可选参数,指定执行mapFnthis对象。

    // 1.String生成数组
    const str = 'string';
    const arr = Array.from(str);
    console.log(arr); // ['s','t','r','i','n','g']
    
    // 2.Set生成数组
    const set = new Set(['s','e','t']);
    const arr1 = Array.from(set);
    console.log(arr1); // ['s','e','t']
    
    // 3.Map生成数组
    const map = new Map([[1,'m'],[2,'a'],[3,'p']]);
    const arr2 = Array.from(map);
    const arr3 = Array.from(map.values());
    const arr4 = Array.from(map.keys());
    console.log(arr2); // [[1,'m'],[2,'a'],[3,'p']]
    console.log(arr3); // ['m','a','p']
    console.log(arr4); // [1,2,3];
    
    // 4.类数组对象arguments生成数组
    function f() {
      return Array.from(arguments);
    }
    console.log(f(1,2,3,4,5)); // [1,2,3,4,5]
    
    // 5.可迭代对象生成数组
    const numbers = {
      *[Symbol.iterator](){
        yield 1;
        yield 2;
        yield 3;
      }
    }
    const arr5 = Array.from(numbers,value => value +1);
    console.log(arr5); // [2,3,4]
    
    // 6.指定回调函数
    const arr6 = Array.from([1,2,3,4,5], item => item * 2);
    console.log(arr6); // [2,4,6,8,10]
    
    // 7.指定回调函数及执行回调函数的this对象
    const helper = {
      diff:1,
      add(value) {
        return value + this.diff;
      }
    }
    const arr7 = Array.from([1,2,3,4,5],helper.add,helper); 
    console.log(arr7); // [2,3,4,5,6]
    
    // 8.应用:利用Array.from和Set实现数组去重
    function unique(arr) {
      return Array.from(new Set(arr));
    }
    console.log(unique([1,1,2,2,3,3,4,4,5,5])); // [1,2,3,4,5]
    
2.内置迭代器

  ES6中有3种类型的集合对象:ArrayMap集合和Set集合,这三种集合对象包括其它可迭代对象都内置了3种迭代器:

  1. entries(): 返回一个迭代器,其值为多个键值对
  2. keys():返回一个迭代器,其值为集合的所有键名
  3. values():返回一个迭代器,其值为集合的值

  了解迭代器的概念就会知道,迭代器对象都有一个next()方法,每次调用都返回一个结果对象。结果对象有两个属性:一个是value,表示下一个将要返回的值;另一个是done,一个布尔值,当没有可返回数据时返回true

  • entries()迭代器

    调用entries()返回的迭代器每次调用next()方法时,都返回一个结果对象,对象中的value为一个数组,数组中的元素为集合中每个元素的键和值。若遍历的对象是数组,则第一个元素是数字类型的索引。若为Set集合,则第一个元素和第二个元素都是值。若为Map集合,第一个元素为键名。这里只介绍数组的示例。

    const colors = ['red','yellow','blue'];
    const iter = colors.entries();
    console.log(iter.next()); // { value: [0,'red'], done: false}
    console.log(iter.next()); // { value: [1,'yellow'], done: false}
    console.log(iter.next()); // { value: [2,'blue'], done: false}
    console.log(iter.next()); // { value: undefined, done: true}
    
  • keys()迭代器

    调用keys()返回的迭代器每次调用next()方法,返回的结果对象中的value为集合中存在的元素的键名。

    const colors = ['red','yellow','blue'];
    const iter1 = colors.keys();
    console.log(iter1.next()); // { value: 0, done: false}
    console.log(iter1.next()); // { value: 1, done: false}
    console.log(iter1.next()); // { value: 2, done: false}
    console.log(iter1.next()); // { value: undefined, done: true}
    
  • values()迭代器

    调用values()返回的迭代器每次调用next()方法,返回的结果对象中的value为集合中所存元素的值。

    const colors = ['red','yellow','blue'];
    const iter2 = colors.values();
    console.log(iter2.next()); // { value: 0, done: false}
    console.log(iter2.next()); // { value: 1, done: false}
    console.log(iter2.next()); // { value: 2, done: false}
    console.log(iter2.next()); // { value: undefined, done: true}
    

  另,ArraySet集合的默认迭代器为values()Map集合的默认迭代器是entries(),在 for...of循环中,若没有显式指定则会使用默认迭代器。

3.数组的检测与数组元素的检测

  数组的检测方法有Array.isArray,数组元素的检测方法有every()some()

  • Array.isArray():用于检测一个对象是否是Array对象

    语法:Array.isArray(obj)

    若传入的对象是Array,则返回true,否则返回false

    console.log(Array.isArray([])); // true
    console.log(Array.isArray([1])); // true
    console.log(Array.isArray(new Array())); // true
    console.log(Array.isArray({})); // false
    console.log(Array.isArray(null)); // false
    console.log(Array.isArray(undefined)); // false
    
  • every():检测数组的所有元素是否都能通过指定函数的测试,返回一个boolean

    语法:arr.every(callback(element, index, array), thisArg)

    其中,参数element为用于检测的当前值;index可选,为当前值的索引;array可选,为调用every的当前数组;thisArg为执行callback时使用的this对象

    [18,32,4,120,60].every(x => x > 10); // false
    [18,32,14,120,60].every(x => x > 10); // true
    
  • some():检测数组中是否有一个元素能通过指定函数的测试,返回一个boolean

    语法:arr.some(callback(element, index, array), thisArg)

    [2,1,4,8,7].some(x => x > 10); // false
    [2,1,14,8,7].some(x => x > 10); // true
    
4.数组的重排序

  数组的重排序有两种方法:sort()reverse()

  • sort():用原地算法对数组的元素进行排序,并返回数组。默认的排序顺序是将元素转换成字符串,然后根据逐个字符的unicode位点进行排序,该方法会改变原数组。

    语法:arr.sort(compareFn(firstEl, secondEl))

    其中,compareFn可选,用于指定按某种顺序进行排列的函数。若a和b是两个将要被比较的元素

    • 若`compareFn(a, b) < 0,那么a会排在b前面
    • 若compareFn(a, b) = 0,那么a和b的相对位置不变
    • 若compareFn(a, b) > 0,那么b会排在a前面
    // 数组的升序、降序排序
    const arr = [1,3,2,5,4];
    console.log(arr.sort((a, b) => a - b)); // [1,2,3,4,5]
    console.log(arr.sort((a, b) => b - a)); // [5,4,3,2,1]
    

  我们知道,常见的有八大排序算法:

  稳定的:冒泡排序 O(n2)、插入排序 O(n2)、基数排序 O(logRB)、归并排序 O(nlogn)

  不稳定的:选择排序 O(n2)、希尔排序 O(nlogn)、快速排序 O(nlogn)、堆排序 O(nlogn)

  这里笔者比较好奇sort()排序的底层究竟做了啥,它的算法时间复杂度又是多少呢?

  不同浏览器的sort()方法的实现不同,这里主要研究了V8引擎下的sort()方法的源码,发现对于长度<=10的数组使用的是插入排序,>10的数组使用的是插入排序+快速排序。这里截取了源码的一段:

// Insertion sort is faster for short arrays.
if (to - from <= 10) {
  InsertionSort(a, from, to);
  return;
}
if (to - from > 1000) {
  third_index = GetThirdIndex(a, from, to);
} else {
  third_index = from + ((to - from) >> 1);
}

  当然,这是chrome 70以前sort()的算法实现,从chrome 70开始,V8团队更新了sort()方法,使用了Timesort算法,这里笔者未作深究。

  • reverse():将数组中的元素颠倒,并返回该数组。该方法会改变原数组

    语法:arr.reverse()

    const arr = [1,2,3,4,5];
    console.log(arr.reverse()); // [1,2,3,4,5]
    
5.数组元素的操作

  针对数组的元素主要有数组元素的添加,删除和修改等操作,方法包括:

  添加:push()unshift()

  删除:pop()shift()

  修改:fill()copyWithin()

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

    语法:arr.push(element1,...,elementN)

    push方法具有通用性,可以和call()apply()一起使用,应用在类数组对象上。

    // 添加元素到数组
    const arr = [1,2,3,4,5];
    const total = arr.push(6,7,8,9,10);
    console.log(arr); // [1,2,3,4,5,6,7,8,9,10]
    console.log(total); // 10 total为数组的新长度值
    
    // 利用apply()合并两个数组
    const arr1 = [1,2,3,4,5];
    const arr2 = [6,7,8,9,10];
    Array.prototype.push.apply(arr1,arr2);
    console.log(arr1); // [1,2,3,4,5,6,7,8,9,10]
    
    // 类数组对象的应用
    const obj = {
      length: 0,
      addElem: function(elem){ [].push.call(this, elem) } 
    };
    obj.addElem({});
    obj.addElem({});
    console.log(obj); // { 0: {}, 1: {}, length: 2, addElem: ƒ}
    
  • pop():删除数组中的最后一个元素,并返回该元素。当数组为空时,返回undefined

    语法:arr.pop()

    pop方法也具有通用性,可以和call()apply()一起使用,应用在类数组对象上。

    // 删除数组的最后一个元素
    const arr = [1,2,3,4,5];
    const elem = arr.pop();
    console.log(arr); // [1,2,3,4]
    console.log(elem); // 5
    
  • unshift():将一个多个元素添加到数组的开头,并返回该数组的新长度

    语法:arr.unshift(element1,...,elementN)

    unshift同样可以通过call()apply()方法作用于类数组对象上。

    // 添加元素到数组
    const arr = [1,2,3,4,5];
    const total = arr.unshift(6,7,8,9,10);
    console.log(arr); // [6,7,8,9,10,1,2,3,4,5]
    console.log(total); // 10
    
    // 合并数组
    const arr1 = [1,2,3,4,5];
    const arr2 = [6,7,8,9,10];
    Array.prototype.unshift.apply(arr1,arr2);
    console.log(arr1); // [6,7,8,9,10,1,2,3,4,5];
    
  • shift():删除数组中的第一个元素,并返回该元素。当数组为空时,返回undefined

    语法:arr.shift()

    shift也同样可以通过call()apply()方法作用于类数组对象上。

    const arr = [1,2,3,4,5];
    const elem = arr.shift();
    console.log(arr); // [2,3,4,5]
    console.log(elem); // 1
    

  我们知道,栈的特点是后进先出,队列的特点是后进后出。因此,我们可以利用数组的push()pop()实现栈结构,push()shift()实现队列结构。

// 栈结构 后进先出
const Stack = function() {
    this.data = [];
    this.insert = push;
    this.delete = pop;
    this.clear = clear;
    this.length = length;
}
const push = function(element) {
    this.data.push(element);
    return this.data.length;
}
const pop = function() {
    return this.data.pop();
}
const clear = function() {
    this.data.length = 0;
}
const length = function() {
    return this.data.length;
}

const s = new Stack();
s.insert('first');
s.insert('second');
console.log(s.data); // ['first','second']
s.delete();
console.log(s.data); // ['first']
s.clear();
console.log(s.data); // []
// 队列结构 后进后出
const Queue = function() {
    this.data = [];
    this.insert = push;
    this.delete = shift;
    this.clear = clear;
    this.length = length;
}
const push = function(element) {
    return this.data.push(element);
}
const shift = function() {
    return this.data.shift();
}
const clear = function() {
    this.data.length = 0;
}
const length = function() {
    return this.data.length;
}

const q = new Queue();
q.insert('first');
q.insert('second');
console.log(q.data); // ['first','second']
q.delete();
console.log(q.data); // ['second']
q.clear();
console.log(q.data); // []

  再进一步思考,我们可以通过栈来实现DFS:首先将根节点入栈,然后出栈后检查此节点是否有子节点,有子节点就逆序推入栈中,再出栈,子节点逆序入栈,依次循环;通过队列实现BFS:首先将根节点入队列,然后出队列检查此节点是否有子节点,有子节点就顺序入队列,再出队列子,节点顺序入队列,依次循环

// 栈实现DFS
function deepTraversal(node) {
    const nodelist = [];
    if(node) {
        const stack = [];
        stack.push(node); // 根节点入栈
        while(stack.length !== 0) {
            const childItem = stack.pop(); // 出栈(数组的最后一个元素)
            nodelist.push(childItem); 
            const childrenList = childItem.children;// 获取出栈的节点的子节点,保证子节点先出栈
            for(let i = childrenList.length-1;i >= 0;i--) {
                stack.push(childrenList[i]); // 子节点逆序入栈
            }
        }
        return nodelist;
    }
}
// 队列实现BFS
function wideTraversal(node) {
    const nodelist = [];
    if(node) {
        const queue = [];
        queue.push(node); // 根节点入队列
        while(queue.length !== 0) {
            const childItem = queue.shift(); // 出队列(数组第一个元素)
            nodelist.push(childItem);
            const childrenList = childItem.children; // 获取出队列的节点的子节点
            for(let i = 0;i <= childrenList.length -1;i++) {
                queue.push(childrenList[i]); // 子节点顺序入队列,保证父节点先出队列
            }
        }
        return nodelist;
    }
}
  • fill():用一个固定值填充一 个数组从起始索引到终止索引(即替换数组的指定位置的元素),不包括终止索引。返回修改后的数组。

    语法:arr.fill(value, start, end)

    value为用来填充数组元素的值。start可选,为起始索引,默认值为0,当start为负数时,开始索引为length+startend可选,为终止索引,默认值为length,当end为负数时,终止索引为length+end

    fill为通用方法,类数组对象可通过call()apply()来调用fill

    const arr1 = [1,2,3,4,5].fill(4);
    const arr2 = [1,2,3,4,5].fill(4,1,2);
    const arr3 = [1,2,3,4,5].fill(4,4,4);
    const arr4 = [1,2,3,4,5].fill(4,-2);
    const arr5 = [1,2,3,4,5].fill(4,-4,-1);
    const arr6 = Array(5).fill(4);
    const arr7 = [].fill.call({length: 5},4);
    console.log(arr1);   // [4,4,4,4,4]
    console.log(arr2);   // [1,4,3,4,5]
    console.log(arr3);   // [1,2,3,4,5]
    console.log(arr4);   // [1,2,3,4,4]
    console.log(arr5);   // [1,4,4,4,5]
    console.log(arr6);   // [4,4,4,4,4]
    console.log(arr7);   // {0: 4, 1: 4, 2: 4, 3: 4, 4: 4, length: 5}
    
  • copyWithin():浅拷贝数组的一部分到同数组中的另一个位置,并返回它,不会改变原数组的长度。返回改变后的数组。

    语法:arr.copyWithin(target, start, end)

    target为复制序列到该索引的位置。当target为负数时,复制到的位置索引为length+target,当target大于length时,不发生拷贝,当targetstart之后,复制的序列将被修改以符合arr.length

    start为开始复制元素的起始索引。默认为0,当start为负数时,开始的索引为length+start

    end为开始复制元素的终止索引,不包括该位置的元素。默认为arr.length,当end为负数时,终止的索引为length+end

    const arr1 = [1,2,3,4,5].copyWithin(-2);
    const arr2 = [1,2,3,4,5].copyWithin(0,3);
    const arr3 = [1,2,3,4,5].copyWithin(0,3,4);
    const arr4 = [1,2,3,4,5].copyWithin(3,1,4);
    const arr5 = [1,2,3,4,5].copyWithin(1,-3,-1);
    const arr6 = [1,2,3,4,5].copyWithin(1,-3,-4);
    const arr7 = [].copyWithin.call({length: 5, 3: 1},0,3);
    console.log(arr1);   // [1,2,3,1,2]
    console.log(arr2);   // [4,5,3,4,5]
    console.log(arr3);   // [4,2,3,4,5]
    console.log(arr4);   // [1,2,3,2,3]
    console.log(arr5);   // [1,3,4,4,5]
    console.log(arr6);   // [1,2,3,4,5]
    console.log(arr7);   // {0: 1, 3: 1, length: 5}
    
6.数组元素的查找和过滤

  js中数组针对数组某个元素的查找,主要有indexOf()lastIndexOf()以及ES6新增的includes()find()findIndex()方法;filter()可以针对数组查找符合某个条件的所有元素。

  • indexOf():返回指定元素在数组中的第一个的索引,若不存在,则返回-1。从数组的前面向后查找

    语法:arr.indexOf(searchElment , fromIndex:option)

    其中,searchElement为要查找的元素,fromIndex可选,为开始查找的位置,fromIndex为负数时,表示从倒数第|fromIndex|个元素开始查找(也可以顺数fromIndex+arr.length),查找顺序仍然是从前向后。

    // 查找元素在数组中的位置
    const arr = [1,2,3,4,5];
    console.log(arr.indexOf(2));     //  1
    console.log(arr.indexOf(6));     // -1
    console.log(arr.indexOf(3,3));   // -1
    console.log(arr.indexOf(2,-1));  // -1
    console.log(arr.indexOf(4,-3));  // 3
    
  • lastIndexOf():返回指定元素在数组中的最后一个的索引,若不存在,则返回-1。从数组的后面向前查找。

    语法:arr.lastIndexOf(searchElement, fromIndex)

    searchElement为要查找的元素,fromIndex可选,为从此位置开始逆序查找,当fromIndex为负数时,表示从数组倒数第|fromIndex|个元素开始查找(也可以顺数fromIndex+arr.length),查找顺序仍然是从后往前。

    const arr = [1,2,3,4,5,3,1];
    console.log(arr.lastIndexOf(3));     //  5
    console.log(arr.lastIndexOf(6));     // -1
    console.log(arr.lastIndexOf(3,3));   // 2
    console.log(arr.lastIndexOf(3,-3));  // 2
    console.log(arr.lastIndexOf(3,-1));  // 5
    
  • includes():查找一个数组是否包含指定元素,若包含则返回true,否则返回false

    语法:arr.incluedes(searchElement, fromIndex)

    searchElment为要查找的元素,fromIndex可选,从fromIndex索引处开始查找,当fromIndex为负数时,则从arr.length+fromIndex的索引开始查找(或倒数第|fromIndex|个元素开始),若计算出来的索引仍然小于0,则整个数组都会搜索,查找顺序仍然时从前向后。

    arr = [1,2,3,4,5];
    console.log(arr.includes(1));      // true
    console.log(arr.includes(3,2));    // true
    console.log(arr.includes(3,-2));   // false
    console.log(arr.includes(5,-10));  // true
    
  • find():返回数组中满足提供测试的测试函数的第一个元素的值,若没有则返回undefined

    语法:arr.find(callback(element, index, array),thisArg)

    其中,callback为在数组每一项上执行的函数,有3个参数:element为当前遍历的元素;index可选,为当前遍历的索引;array也是可选,为调用findIndex的数组。thisArg可选,为执行回调时的this对象。

    // 用对象的属性查找数组里的某个对象
    const inventory = [
      {name: 'apples', quantity: 2},
      {name: 'bananas', quantity: 0},
      {name: 'cherries', quantity: 5}
    ]
    console.log(inventory.find(fruit => fruit.name === 'cherries')); 
    // {name: 'cherres', quantity: 5}
    
    // 寻找数组中的首个质数
    function isPrime(element) {
      let start = 2;
      while(start <= Math.sqrt(element)) {
        if(element % start++ < 1) {
          return false;
        }
      }
      return element > 1;
    }
    console.log([4,6,8,12].find(isPrime));      // undefined
    console.log([4,5,8,12].find(isPrime));      // 5
    console.log([3,4,5,7,8,12].find(isPrime));  // 3
    
  • findIndex():返回数组中满足提供的测试函数的第一个元素的索引。若没有则返回-1。

    语法:arr.findIndex(callback(element, index,array),thisArg)

    callback为在数组每一项上执行的函数,有3个参数:element为当前遍历的元素;index可选,为当前遍历的索引;array可选,为调用findIndex的数组。thisArg可选,为执行callback时的this对象。

    // 查找数组中首个质数元素的索引
    function isPrime(element) {
      let start = 2;
      while(start <= Math.sqrt(element)) {
        if(element % start++ < 1) {
          return false;
        }
      }
      return element > 1;
    }
    console.log([4,6,8,12].findIndex(isPrime));    // -1
    console.log([4,6,7,12].findIndex(isPrime));    // 2
    console.log([4,5,6,7,12].findIndex(isPrime));  // 1
    
  • filter():创建一个新数组,其包含通过所提供函数的测试的所有元素。

    语法:const newArray = arr.filter(callback(element,index,array),thisArg)

    其中,callback为测试数组的每个元素的函数。返回true表示该元素通过测试,保留该元素,false则不保留。有3个参数:element为当前元素;index为当前索引;array为调用filter的数组。thisArg为执行callback时的this对象。

    // 筛选满足条件的元素
    const arr = [12,5,10,24,48];
    const arr1 = arr.filter(element => element > 10);
    const arr2 = arr.filter(element => element <= 10);
    console.log(arr1); // [12,24,48]
    console.log(arr2); // [5,10]
    
    // 过滤json中的无效条目
    const arr = [
      { id: 15 },
      { id: -1 },
      { id: 0 },
      { id: 3 },
      { id: 12.2 },
      { },
      { id: null},
      { id: NaN },
      { id: 'undefiend' }
    ];
    let invalidEntries = 0;
    function filterByID(item) {
      if(item.id !== undefined 
         && typeof(item.id) === 'number' 
         && !isNaN(item.id) 
         && item.id !== 0) {
        return true;
      }
      invalidEntries++;
      return false;
    }
    const newArr = arr.filter(filterByID);
    console.log(newArr);          // [{id: 15},{id: -1},{id: 3},{id: 12.2}]
    console.log(invalidEntries);  // 5
    
7.数组的合并与切割

  js提供了数组的合并和分割的方法,可以针对一个或多个数组进行操作。

  合并的方法有:concat()join()

  切割的方法有:slice()splice()

  • concat():合并两个或多个数组。此方法不改变原数组,返回一个新数组。

    语法:const newArray = oldArray.concat(Array1,...,ArrayN)

    concat的参数为空,则返回调用此方法的现有数组的一个浅拷贝(即oldArray的浅拷贝)。

    // 合并多个数组
    const arr1 = [1,2,3];
    const arr2 = [4,5,6];
    const arr3 = ['a','b','c'];
    const arr = arr1.concat(arr2,arr3);
    console.log(arr); // [1,2,3,4,5,6,'a','b','c']
    
    // 返回原数组的浅拷贝
    const oldArray = [
      {
        person: {
        	name: 'zhang',
       		age: 28
      	},
        city: 'wuhan'
      },
      {
        person: {
          name: 'wang',
          age: 29
        },
        city: 'shenzheng'
      }
    ]
    const newArray = oldArray.concat();
    oldArray[0].person.age = 27;
    console.log(newArray); 
    // [{person: {name:'zhang',age:27},city:'武汉'},{person:{name:'wang',age:29},city:'深圳'}]
    
  • join():按指定分隔符合并数组或类数组对象的所有元素,连接成一个字符串并返回。若数组只有一个项目,那么返回时不使用分隔符。

    语法:arr.join(separator)

    separator可选,为指定连接数组每个元素的分隔符。若缺省该值,数组元素以逗号(,)分隔,若separator为空字符串(''),则数组元素之间没有任何字符。如果一个元素为undifinednull,它会被转换为空字符串。

    // 连接数组
    const arr = ['Winter',undefined,'Is',null,'Comming'];
    const str = arr.join();
    const str1 = arr.join(',');
    const str2 = arr.join('_');
    const str3 = arr.join('');
    console.log(str);    // Winter,,Is,,Comming
    console.log(str1);   // Winter,,Is,,Comming
    console.log(str2);   // Winter__Is__Comming
    console.log(str3);   // WinterIsComming
    
    // 连接类数组对象
    function f(a,b,c) {
      const s = Array.prototype.join.call(arguments);
      console.log(s); 
    }
    f(1,'cold',true); // 1,cold,true
    
  • slice():返回一个新的数组对象,这个对象是由beginend决定的原数组的浅拷贝(包含begin,不包含end)。原数组不改变。

    语法:arr.slice(begin, end)

    begin可选,指从该索引开始切割原数组元素。若省略begin,则slice从索引0开始,若begin为负数,则表示从倒数第几个元素开始切割(或顺数索引arr.length+begin开始)。

    end可选,指在该索引处结束切割原数组元素。若end省略,则slice提取到原数组的末尾,若end为负数,则表示在原数组的倒数第几个元素结束(或顺数索引arr.length+end结束),若end大于数组的长度,slice也会提取到原数组的末尾。

    // 返回现有数组的一部分
    const arr = ['Banana','Orange','Lemon','Apple','Mango'];
    const fruit = arr.slice();
    const fruit1 = arr.slice(1,3);
    const fruit2 = arr.slice(-1,3);
    const fruit3 = arr.slice(3,-1);
    const fruit4 = arr.slice(-1,-3);
    const fruit5 = arr.slice(-3,-1);
    console.log(fruit);    // ['Banana','Orange','Lemon','Apple','Mango']
    console.log(fruit1);   // ['Orange','Lemon']
    console.log(fruit2);   // []
    console.log(fruit3);   // ['Apple']
    console.log(fruit4);   // []
    console.log(fruit5);   // ['Lemon','Apple']
    
    // 类数组对象调用
    function list() {
      return [].slice.call(arguments);
    }
    list(1,2,3,4,5);   // [1,2,3,4,5]
    
  • splice():通过删除或替换现有数组的元素或原地添加新的元素来修改数组,并以数组的形式返回被修改的内容。此方法会改变原数组。返回值为被删除的元素组成的数组。

    语法:arr.splice(start, deleteCount, item1,item2,...)

    其中,start为指定修改的开始位置(包括该位置的元素),若超过了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组倒数第几个元素开始,若负数的绝对值大于数组的长度,则表示开始的位置为第0位。

    deleteCount可选,表示要移除的数组元素的个数。当deleteCount大于start之后的元素总数,则start后面的元素都被删除,若deleteCount省略或大于等于length-start,那么start后面的元素都被删除,当deleteCount为0或负数,则不移除元素,这种情况至少应该添加一个新元素。

    item1,item2,...表示要添加进数组的元素,从start位置开始,如果不指定,则splice只删除元素。

    const arr1, arr2, arr3, arr4, arr5 = [1,2,3,4,5];
    
    console.log(arr1.splice(2));          // [3,4,5]
    console.log(arr1);                    // [1,2]
    
    console.log(arr2.splice(1,5));        // [2,3,4,5]
    console.log(arr2);                    // [1]
    
    console.log(arr3.splice(-1,3,4,5));   // [5]
    console.log(arr3);                    // [1,2,3,4,4,5]
    
    console.log(arr4.splice(-1,-1,4,5));  // []
    console.log(arr4);                    // [1,2,3,4,5,4,5]
    
    console.log(arr5.splice(6,2,4,5));    // []
    console.log(arr5);                    // [1,2,3,4,5,4,5]
    
8.数组的遍历

  js中遍历数组的方法有:forEach()map()reduce()reduceRight()

  • forEach():遍历数组的每个元素,执行一次给定的函数。forEach除非抛出异常,否则无法跳出循环,且不对未初始化的值进行任何操作。返回值为undefined

    语法:arr.forEach(callback(currentValue, index, array), thisArg)

    其中,callback为遍历每个元素执行的函数,该函数有三个参数:

    currentValue为当前遍历的元素;index可选,为当前遍历的索引;array可选,为执行forEach的数组。

    thisArg可选,为执行回调函数时的this对象。

    // 遍历稀疏数组
    const arrSparse = [1,3,,7];
    let numCallbackRuns = 0;
    arrSparse.forEach(function(element) {
      console.log(element);numCallbackRuns++;
    });
    console.log('numCallbackRuns: ',numCallbackRuns);
    
    // 1
    // 3
    // 7
    // numCallbackRuns: 3
    
  • map():创建一个新数组,其结果是遍历数组的每个元素,执行指定的函数后的返回的值。不会改变原数组。

    语法:const newArray = arr.map(callback(currentValue, index, array),thisArg)

    callback为生成新数组元素的函数,该函数有三个参数:

    currentValue为当前遍历的元素;index可选,为当前遍历的索引;array可选,为调用map的数组。

    thisArg为执行callback函数的this对象。

    // 求数组中每个元素的平方
    const arr = [1,2,3,4,5];
    const newArr = arr.map(item => Math.pow(item,2));
    console.log(newArr);    // [1,4,9,16,25]
    
    // map格式化数组中的对象
    const arr = [
      {key: 1, value: 10},
      {key: 2, value: 20},
      {key: 3, value: 30}
    ];
    const newArr = arr.map(function(obj) {
      const rObj = {};
      rObj[obj.key] = obj.value;
      return rObj;
    })
    console.log(newArr); // [{1: 10},{2: 20},{3: 30}]
    
  • reduce():对数组中的每个元素执行一个指定的reducer函数(升序执行),将其结果汇总为单个返回值。

    语法:arr.reduce(callback(accumulator, currentValue, index, array), initialValue)

    其中,callback为遍历每个元素执行的函数,有四个参数:

    accumulator为累计器累计回调的返回值,即上一次调用时返回的累积值。

    currentValue为当前遍历的元素;index可选,为当前遍历的索引;array可选,为调用reduce()的数组。

    initialValue可选,作为第一次调用callback函数时的第一个参数的值,若没有提供,则将使用数组的第一个元素,且遍历跳过该元素。返回值为函数累积处理的结果。

    // 累加数组里的所有值
    const arr = [1,2,3,4,5];
    const total = arr.reduce((acc,cur) => acc + cur, 0); 
    console.log(total);    // 15
    
    // 将二维数组转化为一维数组
    const arr1 = [[1,2],[3,4],[5,6]];
    const flattened = arr1.reduce((acc, cur) => acc.concat(cur), []);
    console.log(flattened);    // [1,2,3,4,5,6]
    
    // 计算数组中每个元素出现的次数
    const names = ['Alice', 'Bob', 'Tiff','Bruce', 'Alice'];
    const countNames = names.reduce(function(allNames, name){
      if(name in allNames){
        allNames[name]++;
      } else {
        allNames[name] = 1;
      }
    	return allNames
    },{});
    console.log(countNames);  // {'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1}
    
    // 数组去重
    const arr2 = [1,3,5,7,5,3,1];
    const newArr = arr2.reduce((acc, cur) => {
      if(!acc.includes(cur)) {
        acc.push(cur)
      } 
      return acc;
    }, []);
    console.log(newArr);    // [1,3,5,6]
    
  • reduceRight():接受一个函数作为累加器,从右到左遍历数组的元素,将其结果汇总为单个值。

    语法:arr.reduceRight(callback(accumulator, currentValue, index, array), initialValue)

    其中,callback为遍历每个元素执行的回调函数,有四个参数:

    accumulator为累加器累计回调的返回值,即上一次调用回调函数的返回值。

    currentValue为当前遍历的元素;index可选,为当前遍历的索引;array为调用callback的数组。

    initialValue可选,为首次调用callback函数时,累加器accumulator的值。若没提供初始值,则将使用数组的最后一个元素作为初始值,且遍历跳过该元素。返回值为累计执行的返回值。

    // 计算数组元素的和
    const arr = [0,1,2,3,4];
    const sum = arr.reduceRight((acc, cur) => acc + cur, 0);
    console.log(sum);    // 10
    
    // 扁平化一个二维数组
    const arr2 = [[0,1],[2,3],[4,5]];
    const flattened = arr2.reduceRight((acc, cur) => acc.concat(cur));
    console.log(flattened);  // [4,5,2,3,0,1]
    
    // reduce与reduceRight的区别
    const arr3 = ['1','2','3','4','5'];
    const arr4 = arr3.reduce((acc, cur) => acc + cur);
    const arr5 = arr3.reduceRight((acc, cur) => acc + cur);
    console.log(arr4);    // '12345'
    console.log(arr5);    // '54321'
    
9.数组扁平化

  js中,可能存在这样中情况,比如数组中嵌套了数组(即数组的元素仍是数组),当我们想将所有的元素展开组成一个一维数组,即数组扁平化。那么此时就要用到flat()flatMap()方法。

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

    语法:const newArr = arr,flat(depth)

    depth可选,指定要提取嵌套数组的嵌套深度,默认值为1。返回值为扁平化后的新数组。

    // 扁平化嵌套数组
    const arr1 = [1,2,[3,4]];
    const arr2 = arr1.flat();
    console.log(arr2);    // [1,2,3,4]
    
    const arr3 = [1,2,[3,4,[5,6]]];
    const arr4 = arr3.flat();
    const arr5 = arr3.flat(2);
    console.log(arr4);    // [1,2,3,4,[5,6]]
    console.log(arr5);    // [1,2,3,4,5,6]
    
    // 使用Infinity可展开任意深度的嵌套数组
    const arr6 = [1,2,[3,4,[5,6,[7,8,[9,10]]]]];
    const arr7 = arr6.flat(Infinity);
    console.log(arr7);    // [1,2,3,4,5,6,7,8,9,10]
    
    // 扁平化会移除数组空项
    const arr8 = [1,2,,4,5];
    const arr9 = arr8.flat();
    console.log(arr9);    // [1,2,4,5]
    

    当然,我们也可以通过递归 + isArray来扁平化嵌套数组:

    const arr = [1,2,3,[1,2,3,[1,2,3,[1,2,3]]]];
    function flatten(arr) {
      return arr.reduce((acc, cur) => acc.concat(Array.isArray(cur)?flatten(cur):cur),[])
    }
    console.log(flatten(arr)); // [1,2,3,1,2,3,1,2,3,1,2,3]
    
  • flatMap():首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。它与map连着深度值为1的flat几乎相同,但效率更高。

    语法: const newArr = arr.flatMap(callbakc(cuttrentValue, index, array),thisArg)

    其中,callback为映射每个元素的映射函数,有三个参数:

    currentValue为当前处理的元素;index可选,为当前元素的索引;array可选,为调用flatMap的数组。

    thisArg可选,为执行callback函数时的this对象。

    // map()与flatMap()的区别
    const arr = [1,2,3,4,5];
    const arr1 = arr.map(x => [x * 2]);
    const arr2 = arr.flatMap(x => [x * 2]);
    const arr3 = arr.flatMap(x => [[x * 2]]);
    
    console.log(arr1);  // [[2],[4],[6],[8],[10]]
    console.log(arr2);  // [2,4,6,8,10]
    console.log(arr3);  // [[2],[4],[6],[8],[10]]
    
【参考文献】:

developer.mozilla.org/zh-CN/docs/…