JavaScript -- 数组

数组对于一个编程语言而言可谓举足轻重,当然 JavaScript 也对其相当重视,下面我就将自己接触到的数组有关的属性和方法记录下来。此文章会不断完善,以供自己或他人查看。

简单介绍

有两种方式声明一个数组,如下所示

var arrayOne = new Array();
var arrayTwo = [];

按理说,以上两种方式声明的数组功能都是相同的,但是不建议使用第一种方式,准确的说,为了可读性以及代码的执行速度,我们选择第二种方式。

虽然第一种方式不建议使用,但为了给其面子还是来介绍一下它的使用方法。Array 有三个构造方法。

  1. 默认的无参构造方法

    var array = new Array();
    

这种方式声明的数组默认数组长度为 0 。

  1. 带有数组元素的构造方法

    var array = new Array(1, 2, 3, 4);
    
  2. 指定数组长度的构造方法

    var array = new Array(5);
    

数组的长度需要设定在 0 - 2^32 -1 之间的一个整数, 如果不是, 将会抛出 RangeError 异常。在事先不知道数组长度的情况下还是不要盲目使用这个方法。举个例子来说,当前我们指定数组的长度为 5,如果我们放进数组 10 个元素,数组的长度会自动增加到 10 。而如果我们只放进数组 1 个元素,那么其它的 4 个位置不就虚位以待了吗? 这在无形中也就浪费了我们的内存资源。

也许你已经看到了,上面介绍 2,3 两种 Array 构造方法存在冲突,如果我们指定如下代码,浏览器将会如何解释我们的代码呢?

var array = new Array(1);

其实以上代码会被默认解释为一个长度为 1 的空数组,如果你真的想将传进去的一个值作为数组中的元素,那么你只能将这个值当做字符串传进去了。

var array = new Array("1");

当然,我相信大部分人也不会用 new Array() 去声明一个数组,那么自然也不会有这样的问题存在了。

JavaScript 中, 数组被当做是一个对象,我们可以用以下代码去检测。

console.log(typeof(array));  // return object 

既然如此,那么问题来了。 对于一个已知变量,我们怎么判断它是不是数组呢?显然我们不可以利用 typeof 了。有三种方法可以鉴别,下面来一一介绍。

  1. ECMAScript5 中有一个新方法 Array.isArray(),这个方法可以帮主我们鉴别变量是否为数组,如果是数组,返回 true, 否则返回 false。 但是这种方法存在一个问题,那就是老的浏览器并不支持 ECMAScript5

  2. 为了解决上一个方法中出现的问题,我们可以自己定义一个 isArray() 方法。

     function isArray(x) {
         return x.constructor
                 .toString()
                 .indexOf("Array") > -1;
     }
    
  3. 第三种方法需要借助 instanceof 关键词,这个方法可以确定一个对象是否由某一个构造函数所创建。

     array instanceof Array
    

在使用数组的时候需要尤其注意,因为数组是通过下标来操作元素的,比如 a[0],a[1] ...。但是有些时候由于错误的操作,极可能引发不可预知的问题,就像下面这样。

    var person = [];
    person["name"] = "hwaphon";
    person["age"] = 20;

可见,我们声明 person 为一个数组,却以操作对象的方式操作它,这个时候 JavaScript 会自动将其重定位为一个对象,这也就意味着数组当中的方法可能就失效了。比如说,这个时候调用 person.length 会返回 0 。但是值得注意的是,由于在重定位之前 person 是利用数组的构造函数产生的,所以这个时候如果你利用 Array.isArray() 去检测 person 会发现仍然返回 true

方法

说到数组,那么我们的本心就是利用数组来存储一组数据,那么也就面临了问题,我们该如何存取数据呢?

首先,用如下代码声明一个数组。

var array = [];

可以利用如下代码一直往数组的末尾添加一个元素。

array[array.length] = element;

至于如何获取数组中的值,可以直接指定数组的下标,比如说 a[0](如果这个元素尚没有指定值,将会返回 undefined)。

存取元素都解决了,还需要面对的一个问题是如何删除元素,或许我们可以通过下面这段代码删除最后一个元素(至于其它位置的元素,只需改变 i 的值即可)

    for (var i = 0; i < array.length; i++) {
        array[i] = array[i + 1];
    }

恩,看上去是这么回事,而且最后一个元素会被赋值为 undefined,非常完美。可是当调用 array.length 的时候发现数组的长度并没有改变,因为这样我们只是将值给删除了而已。没关系,还有办法,我们在重新声明一个数组,将当前数组非 undefined 的值复制进去就是了。哎,好像又需要写一个函数专门去做这件事了。体贴的 JavaScript 设计者自然也想到了这一点,我们可以使用其内置的方法去解决删除的问题,比如 pop(), shift(), splice()。下面来探讨一下这些方法的使用。

我们可以使用 push(), unshift() 方法在数组中插入元素,二者的区别在于 push() 方法会将元素插在数组的末尾,unshift() 方法将元素插在数组的开头。

    array.unshift(0);
    array.push(5);

使用 pop(), shift() 方法在数组中删除元素,二者的区别在于 pop() 删除数组的末尾元素,shift() 方法删除数组的起始元素。

    array.pop();
    array.shift();

可见,我们可以使用 push() 和 shift() 模仿队列的工作方式。

还有一个比较强大的方法,那就是 splice() 方法,我们可以利用它删除任何位置的任何长度元素,也可以替换掉数组中的一部分元素。

    array.splice(1,2);

以上代码的意思是,从数组下标 1 开始,连续删除两个元素。

    array.splice(1,0,5,6,7,8,9);

以上代码的意思是从数组下标 1 开始,删除 0 个元素,并且将 5,6,7,8,9 五个元素插入到数组当中去,而且是插入在下标 1 的后面。

  • 数组复制
    使用 slice(start, end) 方法,可以完成对一个数组的复制,当然可以选择性复制。

      var fullCopy = array.slice();
      var partCopy = array.slice(1,3);
    

上面的第一种方法,是将 array 这个数组中的所有元素复制下来。 第二种方法指定了起始和终止下标,所以复制的是 array 中数组下标 1-3 的元素。

  • 数组合并

      var arrayOne = [0, 1, 2];
      var number = 3;
      var arrayTwo = [4, 5, 6];
    
      var resultArray = arrayOne.concat(number, arrayTwo);
    

concat() 中的参数可指定多个,不过要注意先后顺序。

  • 迭代器函数
    JavaScript 内置了很多迭代器函数,下面来一一介绍。

every(callback, thisArg) 方法会迭代数组中的每一个元素,直到有一个元素返回了 false。这个方法有两个参数,第一个参数是回调函数,而且该回调函数中还包含了三个参数,分别是 currentValue, index, arrayObj。第二个参数 thisArg 是可选的,用于指定回调函数中的 this 指针指向何处,如果不指定,则 this 指针为 undefined。这个方法有什么用呢?我们可以用于判断一组数据中的所有元素是否满足同一条件。比如说,我们用下面这个数组保存了 一部分人的年龄信息。
var personAge = [18,25,102,-6];

我们可以用 every() 函数判断所有的值是否都为正数,因为一个人的年龄不可能为负数吧。

    personAge.every(function(currentValue, index){
        if (currentValue < 0) {
            console.log("Index : " + index + ", value : " + currentValue);
            return false;
        }
        return true;
    });

every() 方法对应的还有一个方法叫做 some() ,这二者的用法是一样的。这二者的差别在于,every() 方法用于检测一个数组中的元素是否全都满足同一条件,一旦有一个元素不满足条件,方法就会执行结束。而 some() 方法用于检测一个数组中是否有一个元素满足指定条件,如果有一个元素满足,也即回调函数返回了 true, 那么some() 方法就会结束执行。举个例子,高中的时候每次考完试,总有老师会问,有没有同学考到 90 分啊,当然老师的目的并不是想准确的知道班上有多少人考到 90 分,他只是想知道是否有人突破这一分数线,所以他想得到的答案为 是或否。我们可以用 some() 来模拟这一场景。

    var studentGrade = [55,66,77,88,99];

    studentGrade.some(function(currentValue, index) {
        if (currentValue >= 90) {
            console.log("I got " + currentValue + " points!");
            return true;
        }
        return false;
    })

一般情况下,我们会利用 for 循环打印整个数组的值,但是为了方便我们也可以使用 forEach() 方法。这个方法的使用和上述介绍的 some(), every() 相同。比如下面利用 forEach() 遍历整个数组。

    var numbers = [55,66,77,88,99];

    numbers.forEach(function(currentVaule, index) {
        console.log("Index : " + index + ", value : " + currentVaule);
    });

下面介绍两种能够返回新数组的迭代器方法。filter(callback, thisArg) 可以根据条件返回一个新的数组。

    var numbers = [55,66,77,88,99];

    var evenNumbers = numbers.filter(function(currentValue, index) {
        if (currentValue % 2 == 0) {
            return true;
        }
        return false;
    });

    for (var i = 0; i < evenNumbers.length; i++) {
        console.log(evenNumbers[i]);
    } 

上面打印的结果为 66,88。 也就是说,filter() 方法会将满足条件的值返回给新的数组。如果你想求一个数组中所有元素的和,那么 reduce(callback, thisArg) 方法能够帮到你,不过值得注意的是,这个方法的 callback 方法相比于上述介绍的有些差距,它的callback 形如 function(previousValue, currentValue, index, arrayObj)。下面利用这个函数来求一个数组的和。

    var numbers = [55,66,77,88,99];

    var result = numbers.reduce(function(previousValue, currentValue, index) {
        return previousValue + currentValue;
    });

    console.log(result);
  • 排序

可以利用 reverse() 方法将一个数组中的元素逆序排列。值得注意的是这里的逆序排列只是将数组中的元素按照初始时的反过来的排放而已,并没有涉及到排序问题。
var numbers = [1, 2, 3, 6, 5, 4, 11, 12, 13, 16, 15];

    numbers.reverse();
    for (var i = 0; i < numbers.length; i++) {
        console.log(numbers[i]);
    }

这段代码的返回结果为 15,16,13,12,11,4,5,6,3,2,1

上面没有涉及到排序,当然我们可以利用 sort() 方法对数组进行排序。
var numbers = [1, 2, 3, 6, 5, 4, 11, 12, 13, 16, 15];

    numbers.sort();
    for (var i = 0; i < numbers.length; i++) {
        console.log(numbers[i]);
    }

这段代码的返回结果为 1,11,12,13,15,16,2,3,4,5,6。 确定这是排序结果?天了噜,这简直是在逗我。为什么会这样呢? 因为 sort() 函数是将数组中的元素当做字符串来排序的,字符串又是根据字符在 ASCII 码来进行排序的。所以,我们只能自己去定义排序规则。

    var numbers = [1, 2, 3, 6, 5, 4, 11, 12, 13, 16, 15];

    numbers.sort(function(first, second){
        return first - second;
    });

    for (var i = 0; i < numbers.length; i++) {
        console.log(numbers[i]);
    }

你可以根据自己的需要,为 sort() 指定用于比较的函数,此函数有三种类型的返回值,即 正数,0, 负数。

  • 输出为字符串

为了满足某种需要,我们可能会将一个数组作为一个字符串返回,最简单的办法是借助于 toString()
var numbers = [66,77,88,99];

    var numberString = numbers.toString();
    console.log(numberString);

执行结果为 66,77,88,99。 可见,toString() 方法默认将两个数组元素用逗号隔开,当然我们还可以使用 join() 改变这一默认表现。
var numbers = [66,77,88,99];

    var numberString = numbers.join("->");
    console.log(numberString);

这时候的输出结果为 66->77->88->99


ES6 新增

  1. Array.from()

该方法可以将一些特定的对象转换成数组对象,比如该对象具有 Iterator 接口或者该对象拥有 length 属性。在 ES5 中,如果我们想把 arguments 转换成一个数组,一般会使用以下语句。

    [].slice.call(arguments);
    Array.prototype.slice.call(arguments);

利用 Array.from() 可以简化上述过程。

   Array.from(arguments);

这个函数一共有三个参数,第二个参数的作用类似于 map, 即对每个位置的元素进行处理,然后写入数组。第三个参数用于绑定 this

  1. Array.of()

用于弥补 Array 构造函数的不足,因为对于 new Array() 语句而言,如果只指定一个参数的话,那一定是数组的长度。比如下面这条语句用于创建一个长度为 1 的数组。

   var array = new Array(1);

而与此不同的是,Array.of() 无论传入多少参数,都会构建一个数组,并把参数当作元素传入数组,比如下面这条语句就是创建一个长度为 1 而且有一个值为 1 的元素的数组。

    var array = Array.of(1);

ES5 中,可以用以下语句模拟 Array.of() 的功能。

    function ArrayOf() {
        return [].slice.call(arguments);
    }
  1. copyWithin()

使用这个方法可以将数组内部指定位置的值复制到其他位置,这个变动发生在数组内部。此函数接受三个参数,第一个参数是必须指定的,用于设置从哪个位置开始替换数据。第二个参数是可选的,用于设置从哪个位置开始读取数据,默认为 0,如果为负数,则实际值为当前值加上数组长度。第三个参数是可选的,用于设置在哪个位置终止读取数据,默认值为数组长度的值,如果是负数,则实际值为当前值加上数组长度。下面来看个例子。

    var array = [1, 2, 3, 4, 5];
    array.copyWithin(0, 3, 5);
    console.log(array);

以上操作的意思就是将位置处在 [3, 5) 的元素放在位置 0 及其以后的位置上。所以上面代码的操作结果就是 [4, 5, 3, 4, 5]。

  1. find() 和 findIndex()

从方法的名字中都可以猜出这两个函数的用途,find() 用于查找数组中第一个满足条件的元素值,这个函数像是 some()filter() 的结合。findIndex() 用于返回数组中第一个满足条件的元素的下标。

    var array = [1, 2, 3, 4, 5];

    // result = 4
    var result = array.find(function(value, index) {
        return value >= 4;
    });

findIndex()find() 用法一样,不过是返回的下标。值得一提的是 find()findIndex() 都能识别 NaN, 而对于 indexOf()lastIndexOf() 则无法识别 NaN

  1. fill()

这个方法一般用与初始化空数组,它可以将数组中的所有元素置为相同的一个值,比如声明一个长度为 5 的数组,并将所有的值都置为 0 。

new Array(5).fill(0);
  1. entries(), keys(), values()

这三个方法用于遍历数组,entries() 用于对键值对进行遍历,keys() 用于对键进行遍历, values() 用于对值进行遍历,下面是一个例子。

    var array = ['a', 'b', 'c', 'd', 'e'];

    for(let index of array.keys()) {
        console.log(index); // 0 1 2 3 4 5
    }

    for (let value of array.values()) {
        console.log(value); // a b c d e 
    }

    for(let item of array.entries()) {
        console.log(item);  // [0, 'a'] [1, 'b'] ...
    }

不过遗憾的是,在我的电脑上没有一个浏览器支持 values() 方法,而 keys()entries() 都是没有问题的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 数组的定义 数组是按序号排列的一组值,每个值的位置都有编号(从0开始)。数组本质上是一种特殊的对象。它的键名是按(...
    Allin_Lin阅读 535评论 0 0
  • Javascript数组基本操作 Javascript中的数组是一种特殊的对象,用来表示偏移量的索引是该对象的属性...
    haveSnap阅读 272评论 0 0
  • 数组的创建 数组是值的有序集合。每个值叫做元素,每个元素在数组中都有数字位置编号,也就是索引。JS中的数组是弱类型...
    supa同学阅读 459评论 0 3
  • 昨夜托生病的福,我独自一人住在暂时闲置的父母家。缩在暖暖的被窝里,一连看了三票电影,外面下起了雪。 《ONCE》是...
    低谷为陵阅读 214评论 1 0
  • 往事随风云网鱼,幽蘭问羽君已去! 罪酒无双风飘絮,咫尺天涯万里居! 潇洒不言一声笑,淡看寰宇任君遥! 此生纵横生死...
    佰贰拾贰阅读 146评论 0 1