JavaScript之数组

云鱼Cloudy

概念

JavaScript数组是JavaScript对象的特殊形式。数组索引实际上和碰巧是整数的属性名差不多,使用方括号访问数组元素就像用方括号访问对象的属性一样。JavaScript将指定的数字索引值转换成字符串——索引值1变成 “1”——然后将其作为属性名来使用。

数组的特别之处在于,当使用小于2^32的非负整数作为属性名时数组会自动维护其length属性值。通常,数组的实现是经过优化的,用数字索引来访问数组元素一般来说比访问常规的对象属性要快很多。

注意,可以使用负数或非整数来索引数组。这种情况下,数值转换为字符串,字符串作为属性名来用。

数组相关函数

数组遍历方法

  1. for循环
  2. Array.prototype.forEach()
    forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。没有返回值

    array.forEach(callback(currentValue[, index, array]]), [thisArg])

    实例:遍历并输出所有数组元素

    function logArrayElements(element, index, array) {
      console.log('a[' + index + '] = ' + element);
    }
    
    // 注意索引 2 被跳过了,因为在数组的这个位置没有项
    [2, 5, , 9].forEach(logArrayElements);
    // logs:
    // a[0] = 2
    // a[1] = 5
    // a[3] = 9
  3. Array.prototype.map()
    map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,不改变原数组

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

    实例1: 求数组中每个元素的平方根

    var numbers = [1, 4, 9];
    var roots = numbers.map(Math.sqrt);
    // roots的值为[1, 2, 3], numbers的值仍为[1, 4, 9]

    实例2:在一个String上使用 map 方法获取字符串中每个字符所对应的 ASCII 码组成的数组:

    var map = Array.prototype.map
    var a = map.call("Hello World", function(x) { 
      return x.charCodeAt(0); 
    })
    // a的值为[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]
  4. Array.prototype.filter()
    filter()方法创建一个新数组,返回一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

    var newArray = array.filter(callback(element[, index[, array]])[, thisArg])

    实例:使用filter()创建具有非零id的元素的 json。

    var arr = [
      { id: 15 },
      { id: -1 },
      { id: 0 },
      { id: 3 },
      { id: 12.2 },
      { },
      { id: null },
      { id: NaN },
      { id: 'undefined' }
    ];
    
    // 判断当前元素是否为数字
    function isNumber(obj) {
      return obj !== undefined && typeof(obj) === 'number' && !isNaN(obj);
    }
    //callback过滤函数
    function filterByID(item) {
      if (isNumber(item.id) && item.id !== 0) {
        return true;
      } 
      invalidEntries++;
      return false; 
    }
    
    var arrByID = arr.filter(filterByID);
    // Filtered Array:
    // [{ id: 15 }, { id: -1 }, { id: 3 }, { id: 12.2 }]
  5. Array.prototype.reduce()
    方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
    语法:

    var result = array.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

    reduce为数组中的每一个元素依次执行callback函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:

    accumulator 累计器
    currentValue 当前值
    currentIndex 当前索引
    array 数组

    回调函数第一次执行时,accumulator和currentValue的取值有几种情况:
    如果调用reduce()时提供了initialValue,accumulator取值为initialValue,currentValue取数组中的第一个值;
    如果没有提供initialValue,那么accumulator取数组中的第一个值,currentValue取数组中的第二个值。
    如果数组为空且没有提供initialValue,会抛出TypeError。
    如果数组仅有一个元素(无论位置如何)并且没有提供initialValue, 或者有提供initialValue但是数组为空,那么此唯一值将被返回并且callback不会被执行。

    reduce()如何运行:
    假如运行下段reduce()代码:

    [0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array){
      return accumulator + currentValue;
    });

    Callback 被调用四次,每次调用的参数和返回值如下表:
    图片描述
    您还可以提供Arrow Function来代替完整的函数。 下面的代码将产生与上面的代码中相同的输出:

    [0, 1, 2, 3, 4].reduce((prev, curr) => prev + curr );

    如果你打算提供一个初始值作为reduce()方法的第二个参数,以下是运行过程及结果:

    [0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => { returnaccumulator+ currentValue; }, 10);    // =>20

    实例1:计算数组中每个元素出现的次数

    var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
    var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
    var countedNames = names.reduce(function (allNames, name) { 
      if (name in allNames) {
        allNames[name]++;
      }
      else {
        allNames[name] = 1;
      }
      return allNames;
    }, {});
    // countedNames is:
    // { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

    实例2: 数组去重

    let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
    let result = arr.sort().reduce(function(init, currentValue){
        if(init.length === 0 || init[init.length - 1] !== currentValue){
        init.push(current);
      }
        return init;
    }, []);    // => [1, 2, 3, 4, 5]
    此处额外附上其他数组去重方法以作对比参考
    // 方法一
    var norepeat = funtion(arr){
        return arr.filter(function(val, index, array){
            return array.indexOf(val) === index;
        });
    }
    norepeat()
    
    // 方法二
    var set = new Set(arr);

    实例3:按顺序运行Promise

     // Runs promises from array of functions that can return promises
     // in chained manner
     // @param {array} arr - promise arr
     // @return {Object} promise object
     
    function runPromiseInSequence(arr, input) {
      return arr.reduce(
        (promiseChain, currentFunction) => promiseChain.then(currentFunction),
        Promise.resolve(input)
      );
    }
    
    // promise function 1
    function p1(a) {
      return new Promise((resolve, reject) => {
        resolve(a * 5);
      });
    }
    
    // promise function 2
    function p2(a) {
      return new Promise((resolve, reject) => {
        resolve(a * 2);
      });
    }
    
    // function 3  - will be wrapped in a resolved promise by .then()
    function f3(a) {
     return a * 3;
    }
    
    // promise function 4
    function p4(a) {
      return new Promise((resolve, reject) => {
        resolve(a * 4);
      });
    }
    
    const promiseArr = [p1, p2, f3, p4];
    runPromiseInSequence(promiseArr, 10)
      .then(console.log);   // 1200
  6. Array.prototype.some()Array.prototype.every()

    some()方法测试是否至少有一个元素可以通过被提供的函数方法。该方法返回一个Boolean类型的值。some()被调用时不会改变数组

    array.some(callback(element[, index[, array]])[, thisArg])

    实例:检测在数组中是否有元素大于 10。

    function isBiggerThan10(element, index, array) {
      return element > 10;
    }
    
    [2, 5, 8, 1, 4].some(isBiggerThan10);  // false
    [12, 5, 8, 1, 4].some(isBiggerThan10); // true

    every()方法就像数学中的“针对所有”的量词,当且仅当针对数组中的所有元素调用判定函数都返回true,它才返回true。和some有相似之处。
    实例:

    a = [1,2,3,4,5];
    a.every(function(x) { return X < 10; }) // => true:所有的值<10
    a.every(function(x) { return x % 2 === 0; }) // => false: 不是所有的值都是偶数

js数组方法

  1. Array.prototype.push()Array.prototype.pop()

    push()方法向数组的末尾添加一个或更多元素,并返回新的长度。
    pop()方法删除并返回数组的最后一个元素。

  2. Array.prototype.unshift()Array.prototype.shift()

    shift()方法删除并返回数组的第一个元素。
    unshift()方法向数组的头部添加一个或更多元素,并返回新的长度。

    注意,当使用多个参数调用unshift()时它的行为令人惊讶。参数是一次性插入的(就像splice()方法)而非一次一个地插入。这意味着最终的数组中插入的元素的顺序和它们在参数列表中的顺序-致。而假如元素是一次一个地插入,它们的顺序应该是反过来的。

  3. Array.prototype.reverse()

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

  4. Array.prototype.sort()
    方法用原地算法对数组的元素进行排序,并返回数组。排序算法现在是稳定的。默认排序顺序是根据字符串Unicode码点。由于它取决于具体实现,因此无法保证排序的时间和空间复杂性。sort()原数组上原地排序,会导致原数组改变

    var a = [33, 4, 1111, 222];
    a.sort();
    //字母表顺序: 1111,222,33,4
    a.sort(function(a, b) {    // =>a: [4, 33, 222, 1111]
    return a - b;    // 根据顺序,返回负数、0、正数
    });
    a.sort( function(a, b) {return b - a});    // =>a: [1111, 222, 33, 4]
  5. Array.prototype.concat(array2)
    方法用于合并两个或多个数组。concat()方法不会更改原数组,而是返回一个新数组
  6. Array.prototype.join()
    方法将数组中所有元素都转化为字符串并连接在一起,返回最后生成的字符串。可以指定一个可选的字符串在生成的字符串中来分隔数组的各个元素。如果不指定分隔符,默认使用逗号。join()方法不该变原数组。
    实例:字符串数组连接成字符串

    var arr = new Array(3)
    arr[0] = "George"
    arr[1] = "John"
    arr[2] = "Thomas"
    arr.join()
    // output: George,John,Thomas
  7. Array.prototype.slice(start, end)

    slice()方法返回指定数组的一个片段或子数组。它的两个参数分别指定了片段的开始和结束的位置。返回的数组包含第一个参数指定的位置和所有到但不含第二个参数指定的位置之间的所有数组元素。如果只指定一个参数,返回的数组将包含从开始位置到数组结尾的所有元素。如参数中出现负数,它表示相对于数组中最后一个元素的位置。slice()方法不会改变原数组

    // 语法:
    str.slice(beginSlice[, endSlice])
    
    beginSlice:
    必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。
    
    endSlice:
    可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
    
    //实例: 
    var arr = new Array(3)
    arr[0] = "George"
    arr[1] = "John"
    arr[2] = "Thomas"
    arr.slice(1) // output: John,Thomas
    
  8. Array.prototype.splice() :方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组
    语法:

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

    实例:

    // 示例1: 添加元素
    var fruits = ["Banana", "Orange", "Apple", "Mango"];
    fruits.splice(2,1,"Lemon","Kiwi");    // output: Banana,Orange,Lemon,Kiwi,Mango
    
    // 示例2: 删除元素
    var fruits = ["Banana", "Orange", "Apple", "Mango"];
    fruits.splice(2,2);    // output: Banana,Orange
  9. Array.prototype.indexOf()Array.prototype.lastIndexOf()
    index0f()和lastIndex0f()搜索整个数组中具有给定值的元素,返回找到的第一个元素的索引或者如果没有找到就返回-1。index0f( )从头至尾搜索,而lastIndexOf()则反向搜索。
  10. Array.prototype.reduce(reducer)
    方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值
  11. Array.prototype.map():对数组的每一项应用回调函数,返回新数组
  12. Array.prototype.some():数组中只需有一项满足给定条件则返回true。
  13. Array.prototype.every():数组的每一项都满足给定条件则返回true。
  14. Array.prototype.filter():方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
  15. Array.prototype.forEach():数组遍历,与for循环一样,没有返回值

数组类型

给定一个未知的对象,判定它是否为数组通常非常有用。在ECMAScript 5中,可以使用Array.isArray()函数来做这件事情:

Array.isArray([]) //=>true
Array.isArray({}) // => false

在ECMAScript 3中isArray( )函数的代码可以这样书写:

var isArray = Function.isArray || function(o) {
return typeof o === "object" && Object.prototype.toString. call(o) === "[object Array]";
}; 

类数组对象

我们已经看到,JavaScript数组的有- - 些特性是其他对象所没有的:

* 当有新的元素添加到列表中时,自动更新length属性。
* 设置length为一个较小值将截断数组。
* 从Array.prototype中继承一些有用的方法。
* 其类属性为“Array' 。

这些特性让JavaScript数组和常规的对象有明显的区别。但是它们不是定义数组的本质特性。一种常常完全合理的看法把拥有-个数值length属性和对应非负整数属性的对象看做一种类型的数组。

实例:为一个常规对象增加了一些属性使其变成类数组对象,然后遍历生成的伪数组的“元素”:

var a = {};    // 从一个常规空对象开始
// 添加一些属性,称为"类数组"
var i = 0;
while(i < 10) {
    a[i] = i * i;
    i++;
}
a.length = i;
// 现在,当做真正的数组遍历它
var total = 0;
for(var j = 0; j < a.length; j++)
total += a[j] ;    // => 287

JavaScript中的Arguments对象就是一个类数组对象。一些DOM方法(如document.getElementsByTagName()也返回类数组对象。
下面有一个函数可以用来检测类数组对象:

//判定o是否是一个类数组对象
//字符串和函数有length属性,但是它们
//可以用typeof检测将其排除。在客户端JavaScript中,DOM文 本节点
//也有length属性,需要用额外判断o. nodeType != 3将其排除
function isArrayLike(o) {
  return o && typeof o === "object" && isFinite(o.length) && o.length >= 0 && o.length === Math.floor(o.length) && o.length < 4294967296;             
}

参考:

* 《JavaScript权威指南》第六版
* [MDN Web 文档](https://developer.mozilla.org/zh-CN/)

推荐阅读:
【专题:JavaScript进阶之路】
JavaScript之“use strict”
JavaScript之new运算符
JavaScript之call()理解
JavaScript之对象属性


我是Cloudy,年轻的前端攻城狮一枚,爱专研,爱技术,爱分享。
个人笔记,整理不易,感谢阅读、点赞和收藏。
文章有任何问题欢迎大家指出,也欢迎大家一起交流前端各种问题!
阅读 1.3k

JavaScript进阶之路
JavaScript进阶之路,夯实基础,查漏补缺,温故知新。

感谢阅读、浏览和关注!

2.7k 声望
378 粉丝
0 条评论
你知道吗?

感谢阅读、浏览和关注!

2.7k 声望
378 粉丝
宣传栏