第七章:Javascript数组

数组是值的有序结合。每一个值叫作一个元素,而每一个元素在数组中都有一个位置,用数字表示,称为索引。javascript

javascript数组是无类型的:数组的元素能够是任意类型,而且同一个数组中的不一样元素也可能有不一样的类型。数组的元素多是对象或其余数组,这容许建立更加复杂的数据结构,如对象的数组和数组的数组。java

javascript数组的索引是基本零的32位数值:第一个元素的索引为0,最大的索引为4 294 967 294(2的32次方-2),数组最大能容纳4 294 967 295个元素。node

javascript数组是动态的:根据须要他们会增加或缩减,而且在建立数组时无需声明一个固定大小或者在数组大小变化时无需重新分配空间,javascript数组多是稀疏的:数组元素的索引不必定要连续的,该属性就是数组元素的个数。征对稀疏数组,length比全部元素的索引要大。算法

javascript数组是javascript对象的特殊形式,数组索引实际上和碰巧是整数的属性名差很少。咱们将在本文(章)更多讨论特殊化的数组。一般数组的实现是通过优化的,用数组索引来访问数组元素通常来讲比访问常规的对象属性要快不少。编程

数组继承自Array.prototype的属性,它定义了一套丰富的数组操做方法,8和9节涵盖这方面的内容。大多这些方法是通用的,这意味着他们不只对正真的数组有效,并且对“类数组对象”一样有效。11节讨论类数组对象。在ECMAScript5中,字符串的行为与字符数组相似,12节将讨论。数组

1.建立数组浏览器

使用直接量建立数组是最简单方法,在方括号中将组组元素用逗号隔开便可,如:数据结构

            var empty = [];
            var primes = [2, 3, 5, 7, 11];
            var misc = [1.1, true, "a", ]; //三个不一样类型的元素和结尾的逗号

数组直接量中的值不必定是要常量,它们能够是任意的表达式app

            var base = 1024;
            var table = [base, base + 1, base + 2, base + 3]

它能够包含对象直接量或其余数组直接量函数式编程

        var b = [[1,{x:1,y:2}],[2,{x:3,y:4}]];

若是省略数组直接量中的某个值,省略的值的元素将被赋予undefined

            var count = [1, , 3] //数组中有三个元素,第二个值为undefined
            var undefs = [, , ] //两个元素,都是undefined

调用构造函数Array()是建立数组的令一种方法。用三种方法调用构造函数

  • 调用时没有参数
  • 调用时有一个值是参数,它指定长度
  • 显式指定两个或多个数组元素或者数组的一个非数组元素
        //调用时没有参数
        var a =new Array();

该方法建立一个没有任何元素的空数组,等于数组直接量[].

            var a = new Array(10);

该方法建立指定长度的数组,当预先知道所需元素的个数时,这种形式的Array()构造函数能够用来分配一个数组空间。注意,数组中没有存储值。甚至数组的索引,“0”,“1”等还未定义。

            var a = new Array(5, 4, 3, 2, 1, "testing,testing")

用这种形式,构造的函数参数将会成为新的数组元素,使用数组字面量比这样使用Array()构造函数要简单多了。

2.数组元素的读和写

使用[]操做符来访问数组中的一个元素。数组的引用位于方括号的左边。方括号中是一个返回非负整数值的任意表达式。使用该表达式能够读也能够写数组中的一个元素。所以,以下的代码都是合法的javascript语句

            var a = ["world"]; //从一个元素的数组开始
            var value = a[0]; //读取第0个元素
            a[1] = 3.14; //写第1个元素
            i = 2;
            a[i] = 3; //写第2个元素
            a[i + 1] = "hello"; //写第3个元素
            a[a[i]] = a[0] //读取第0和第2个元素,写第3个元素

请记住:数组是对象的特殊形式。使用方括号访问数组元素就像使用方括号访问对象的属性同样。javascript将制定的数组索引替换成字符串——索引1变成"1",而后将其做为属性名来使用。关于索引值从数字转换为字符串没有什么特别之处:对常规对象也能够这么作:

        o = {}; //建立一个普通的对象
        o[1] = "none" ;//用一个整数来索引它

数组的特别之处在于,当使用小于2的32次方的非负数整数做为属性名时,数组会自动维护其length属性值。如上,建立仅有一个元素的数组,而后在一、二、三、处进行赋值,当咱们这么作时,数组的length变为4

清晰地区分数组的索引和数组的属性名是很是有用的。全部的索引都是其属性名,但只有0到(2的32次方-2)的整数属性名才是索引。全部的数组都是对象,能够为其建立任意名字的属性。但若是使用的属性是数组的索引,数组的特殊行为就是将根据须要更新它们的length属性值。

注意,可使用负数或非整数来索引数组。这种状况下,数值转换为字符串。字符串做为属性名来用。既然名字不是非负整数,就只能当作常规的对象属性,而非数组的全部。一样,若是凑巧使用了是非负整数的字符串,它就当作数组索引,而非对象的属性。当使用一个浮点数和一个整数相等时状况也是同样的:

            a[-1.23] = true; //这将建立一个名为"-1.23"的属性
            a["1000"] = 0; //这是数组的第1001个元素
            a[1.000] //和a[1]相等

事实上数组索引仅仅是对象属性名的一种特殊类型,这意味着javascript数组没有“越界”错误的概念。当试图查询对象中不存在的属性时,不会报错。只会获得undefined值。相似于对象,对于对象一样存在这种状况。

既然数组是对象 ,那么它们能够从原型中继承元素。在ECMAScript5中,数组能够定义元素的getter和setter方法(6.6)。若是一个数组确实继承了元素或使用了元素的getter和setter方法,你应该指望它使用非优化的代码路径:访问这种数组的元素的时间会与常规对象属性的查找时间相近。

3.稀疏数组

稀疏数组就是包含从0开始的不连续索引的数组。一般,数组的length属性值表明了数组中元素的个数。若是数组是稀疏的,那么length的值远大于元素的个数。可使用Array构造函数或简单地指定数组的索引值大于当前数组长度来建立稀疏数组。

        a = new Array(5);//数组中没有元素,但a.length值是5
        a = []; //建立一个空数组,length的值为0
        a[1000] = 0;//赋值添加一个元素,但设置的length值为1001

后面你也能够delete操做符产生稀疏数组。

足够稀疏的数组一般在实现上比稠密是数组更慢、内存利用率更高,在这样的数组中查找元素的时间与常规对象属性的查找时间同样长。

注意,当数组直接量中省略值时不会建立稀疏数组。省略的元素在数组中是存在的,其值为undefined。这和数组元素根本不在是有一些微妙的区别的。能够用in操做符检测二者的区别。

            var a1 = [, , , ]; //数组是[undefined,undefined,undefined]
            var a2 = new Array(3); //该数组根本没有元素
            0 in a1; //=>true a1在索引0处有一个元素;
            0 in a2; //=>false a2在索引0处没有元素

当使用for/in循环时,a1和a2的区别也很明显(第6节)

须要注意的是,当省略数组直接量中的值时(使用连续的逗号,好比[1,,,,3]),这时获得的也是稀疏数组,省略掉的芝是不存在的:

            var a1 = [, ]; //此时数组没有元素,长度为1
            var a2 = [undefined]; //此时数组包含一个值为undefined的元素
            0 in a1; //=>false: a1在索引0处没有元素
            0 in a2; //=>true: a2在0处有一个值为undefined的元素

在一些旧版的实现中,如(firefox3),在存在连续逗号的状况下,插入undefined值的操做与此不一样,在这些实现中,[1,,3]和[1,undefined,3]是如出一辙的。

了解稀疏数组是了解javascript数组真实本质的一部分。尽管如此,实际碰到的绝大多数数组并非稀疏数组。而且,若是碰到稀疏数组,你的代码极可能像对待非稀疏数组同样去对待他们,只不过他们包含一些undefined值。

4.数组的长度

每一个数组都有一个length属性。就是这个属性使其区别于常规的javascript。征对稠密数组,length属性值表明了元素中的个数,其值比数组中的最大索引数大1:

            [].length //=>0:数组没有元素
            ['a', 'b', 'c'].length //=>3 最大是索引为2,length为3

当数组是稀疏的时,length的值远远大于元素的个数。并且关于此咱们就能够说的一切也就是数组长度保证大于它的每一个元素的索引值。换一种话说,在数组中,确定找不到一个元素的索引值大于它的长度。为了维持此规则不变,数组有两个特殊行为。第一个如同上面的描述:若是为一个数组元素赋值,它的索引i大于或等于现有的数组的长度时,length的值将设置为i+1.

第二个特殊行为就是设置length属性为一个小于当前长度的非负整数n时,当前数组中的那些索引值大于或等于n的元素将从中删除,例:

            var a = [1, 2, 3, 4, 5]; //从5个元素的数组开始
            a.length = 3; //如今a为 [1, 2, 3]
            a.length = 0; //删除全部的元素a为[]
            a.length = 5; //长度为5,可是没有元素,就行new Array(5)

还能够将数组的length属性值设置为大于当前的长度。实际上不会向数组中添加新的元素,它只是在的尾部建立一个空的区域。

在ECMAScript5中,可使用Object.defineProperty()将数组的属性变成只读的:

            var a = [1, 2, 3];
            Object.defineProperty(a, "length", {writable: false});
            a.length = 0;
            console.log(a);//=> [1, 2, 3]

相似的,若是让一个数组元素不能配置,就不能删除它。若是不能删除它,length的属性不能设置小于不可配置元素的索引值(见6.7节的Object.seal()和Object.freeze()方法)

5.数组元素的添加和删除

咱们已经见过添加数组元素最简单的方法:为新索引赋值

            a = []; //开始是一个空数组
            a[0] = "zero"; //想其中添加元素
            a[1] = "one";
            a;//=> ["zero", "one"]

可使用push()方法在数组的末尾增长一个或多个元素

在数组的末尾压入一个元素与给数组a[a.length]赋值是同样的,可使用unshift()方法给首部插入一个元素,而且将其余元素移动到更高的索引处;

        a = []; //开始是空数组
        a.push("zero"); //在末尾添加一个元素。 a = ["zero"]
        a.push("one","two");//再添加两个元素
        a;//=>["zero", "one", "two"]
        a.unshift("start");
        a;//=> ["start", "zero", "one", "two"]

能够像删除对象属性同样使用delete运算符来删除数组元素

            a = [1, 2, 3];
            delete a[1];
            1 in a; //=>false:数组索引1并未在数组中定义
            a.length; //=>3: delete操做并不影响数组的长度

删除数组元素与为其赋值undefined值是相似的(但有一些微妙的区别)。对一个数组元素使用delete不会修改数组的length属性;也不会将元素从高索引处移下来填充已经删除的元素空白。若是从一个数组中删除一个元素,它就变成稀疏数组

从上文看出,简单设置length属性也能够删除数组尾部的元素。数组由pop()方法(它和push一块儿使用),后者使减小长度1并返回删除元素的值。还有一个shift()方法(它和unshift()一块儿使用),从数组头部删除一个元素。和delete不一样的是shift()方法将全部的元素下移到比当前元素索引低1的地方(本文8小节涵盖pop()和shift()的内容)。

最后,splice()是一个通用的方法来插入,删除或替换的数组元素方法。它会根据须要修改length属性并移动圆度到更高或较低的索引处(本文8小节详述)。

6.数组遍历

使用for循环(5.5.iii)是遍历数组的经常使用方法:

var keys = Object.keys(o); //得到o对象属性名组成的数组
            var values = []; //在树组中存储匹配属性的值
            for (var i = 0; i < keys.length; i++) { //对于数组中的每一个索引
                var key = keys[i]; //得到索引处的键值
                values[i] = o[key]; //在values数组中保存属性值
            }

在嵌套循环或其余它性能很是重要的上下文中,能够看到这种基本的数组遍历须要优化,数组的长度应该只是查询一次而非每次循环要查询:

            for (var i = 0, len = keys.length; i < len; i++) {
                //循环体仍然不变
            }
             //从写
            var keys = Object.keys(o); //得到o对象属性名组成的数组
            var values = []; //在树组中存储匹配属性的值
            for (var i = 0, len = keys.length; i < len; i++) { //对于数组中的每一个索引
                var key = keys[i]; //得到索引处的键值
                values[i] = o[key]; //在values数组中保存属性值
            }

加速这些数组是稠密的,而且全部的元素都是合法数据。不然,使用数组元素之间都应该检测他们,若是想要排除null、undefined和不存在的元素,代码以下:

                for (var i = 0; i < keys.length; i++) {
                    if (!keys[i]) continue;//循环体
                }

若是只想跳过undefined和不存在的元素

        for (var i = 0; i < keys.length; i++) {
             if (!keys[i] === undefined) continue; //跳过undefined和不存在的元素
            //循环体    
        }

最后,若是只想跳过不存在的元素而仍然要处理存在的undefined的元素,代码以下

            for (var i = 0; i < keys.length; i++) {
                if (!(i in keys)) continue; //跳过不存在的元素
            }

还可使用for/in循环(5.4.iiii),处理稀疏数组。循环每次将一个可枚举的属性名(包括数组索引)赋值给循环变量,不存在的索引将不会遍历到:

            for (var index in sparseArray) {
                var value = sparseArray[index];
                //此处可以使用索引和值作一些事情
            }

在6.5节已经注意到for/in循环可以枚举继承的属性名,如添加到Array.prototype的方法。因为这个缘由,在数组上不该该使用for/in循环,除非使用额外的检测方法来过滤掉不想要的属性。以下代码:取一便可:

            for (var i in a) {
                if (!a.hasOwnProperty(i)) continue; //跳过继承的属性
                //循环体
            }
            
            for (var i in a) {
                //跳过不是非负整数的i
                if (String(Math.floor(Math.abs(Number(i)))) !== i) continue;
            }

ECMAScript规范容许for/in循环以不一样的顺序遍历对象的属性。一般数组元素的遍历实现是升序的,但不能保证必定是这样的。特别地,若是数组同时拥有对象属性和数组元素,返回的属性名极可能是按照建立的顺序而非数组的大小顺序。如何处理这个问题的实现各不相同,若是 算法依赖于遍历的顺序,那么最好不要使用for/in循环而使用常规的for循环。这些方法中最经常使用的就是forEach()方法

            var data = [1, 2, , , , 3, , 4, 5];
            var sumOfSquares = 0; //获得数据的平方和
            data.forEach(function(x) { //把每一个元素传递给此函数
                sumOfSquares += x * x; //平方相加
            });
            sumOfSquares; //=>55: 1+4+9+16+25

forEach()和相关的遍历方法使得数组拥有拥有简单而强大的函数式编程风格。它涵盖在本文第9节中。当涉及到函数式编程时,本文8节将再次碰到他们。

7.多维数组

javascript不支持真正的多维数组,但能够用数组的数组来近似。访问数组的数组中的元素,只要简单地使用两次[]操做符便可。例如,假设matrix[x]的每一个元素是包含一个数值数组,访问数组中特定数值的代码为matrix[x][y]。这里有一个具体的例子,它使用二维数组做为一个99乘法表:

             //建立一个多维数组
            var table = new Array(10); //表格10行
            for (var i = 0; i < table.length; i++)
                table[i] = new Array(10); //每行10列
             //初始化数组
            for (var row = 0; row < table.length; row++) {
                for (col = 0; col < table[row].length; col++) {
                    table[row][col] = row * col;
                }
            }
             //使用多维数组来计算(查询)8*9
            table[8][9]; //=>72

8.数组方法

ECMAScript3在Array.prototype中定义了一些颇有臫的操做数组的函数,这意味着这些函数做为人和数组的方法都是可用的。下面介绍ECMAScript3 中的这些方法。像一般同样,完整的例子见第四部分数组的内容。ECMAScript5新增了一些新的数组遍历方法;它们涵盖在本文的第9节中。

i.join()

Array.join()方法将数组中全部元素都转化为字符串并链接在一块儿,返回最后生成的字符串。能够指定一个可选的字符串在生成是的字符串中来分隔数组的各个元素。如过不指定分隔符,则使用默认的逗号

            var a = [2131, 551, 235, "hello", 123]
            a.join(); //=>2131,551,235,hello,123
            a.join(" "); //=> 2131 551 235 hello 123
            a.join(""); //=>2131551235hello123
            a.join("-"); //=> 2131-551-235-hello-123
            var b = new Array(10);
            b.join('-'); //=>---------9个连字号组成的字符串

Array.join()方法是String.split()方法的逆向操做,后者是将字符串分隔开来建立一个数组

ii.reverse()

Array.reverse()方法将数组的元素颠倒顺序,返回逆序数组。它采起了替换;换句话说,它不经过从新排列的元素建立新的数组,而是在原先的数组中从新排列它们。例如

            //使用reverse()和join()方法生成字符串
            var a = [234, 4332, 4234, 324, 324];
            a.reverse().join(); //=>324,324,4234,4332,234

iii.sort()

Array.sort()方法将数组中的元素排序后并返回排序后的数组。当不带参数调用sort()时,数组元素以字母表顺序排序(若有必要将临时转化为字符串进行比较):

            var a = new Array("banana", "cheery", "apple");
            a.sort(); //=> ["apple", "banana", "cheery"]
            var s = a.join("-"); //=>"apple-banana-cheery"

若是数组包含undefined元素,它们会被排除到数组的尾部。

为了按照其它方式而非字母顺序表属性进行数组排序,必须给sort()方法传递一个比较函数。该函数决定了它的两个参数在拍好的数组中的前后顺序。假设第一参数应该在前,比较函数应该返回一个小于0的数值。而且,假设两个值相等(也就是说他们的属性可有可无),函数应该返回0。所以,例如,用数值大小而非字母表顺序进行数组排序。代码以下

            var a = [33, 4, 1111, 222, 45555];
            a.sort(); //=>[1111, 222, 33, 4, 45555] :字母表顺序
            a.sort(function(a, b) { //=>[4, 33, 222, 1111, 45555] 数值顺序
                return a - b; //根据数据,返回负数,0,正数
            });
            a.sort(function(a, b) {return b - a}); //=> [45555, 1111, 222, 33, 4] 数值大小相反的顺序

 注意:这里使用匿名函数表达式很是方便。既然比较函数只使用了一次,就不必给它们命名了。

另外一个数组元素排序的例子 ,也许须要对一个字符串数组执行不区分大小写的字母表排序,比较函数首先将参数都转换为小写字符串(toLowerCase()方法),再开始比较

            a = ['ant', 'Bug', 'cat', 'Dog', 'egg', 'Fox', 'google']
            a.sort(); //=> ["Bug", "Dog", "Fox", "ant", "cat", "egg", "google"]:区分大小写的排序
            a.sort(function(s, t) { //不区分大小写排序
                var a = s.toLowerCase();
                var b = t.toLowerCase();
                if (a < b) return -1;
                if (a > b) return 1;
                return 0;
            }); //=> ["ant", "Bug", "cat", "Dog", "egg", "Fox", "google"]

iiii.concat()

Array.concat()方法建立并返回一个新数组,它的元素包含调用concat()原始数组的元素和concat()的每一个参数。若是这些参数中任何一个自身是数组,则链接的是数组的元素,而非数组自己。

但要注意:concat()不会递归扁平化数组的数组。concat()也不会修改调用的属性。例如:

            var a = [1, 2, 3];
            a.concat(4, 5); //=> [1, 2, 3, 4, 5]
            a.concat([4, 5]); //=>[1, 2, 3, 4, 5]
            a.concat([4, 5], [6, 7]); //=>[1, 2, 3, 4, 5, 6, 7]
            a.concat(4, [5, [6, 7]]); //=>[1, 2, 3, 4, 5, [6,7]]

iiiii.slice()

Array.slice()方法返回指定数组的一个片断或子数组。它的两个参数分别指定了片断的开始和结束的位置。*返回的数组包含第一个参数和全部到但不含第二个参数定制的位置之间的全部数组元素。*若是只指定一个参数,则返回数组将从开始的位置到数组结尾的全部元素。*若是参数中出现了负数,它表示相对于数组中最后一个元素的位置。例如参数“-1”指定了最后一个元素,而-3指定了倒数第三个元素。

注意slice()不会修改调用的数组.下面有示例:

            var a = [1, 2, 3, 4, 5];
            a.slice(0, 3); //=> [1, 2, 3]
            a.slice(3); //=> [4, 5]
            a.slice(1, -1); //=> [2, 3, 4]
            a.slice(-3, -2); //=> [3]
            a; //=>[1, 2, 3, 4, 5]

iiiiii.splice()

Array.splice()方法是在数组中插入或删除元素的通用方法。不一样于slice()和concat(),splice()会修改调用的数组。

splice()可以从数组中删除元素,插入元素到数组中或者同时完成这两种操做。在插入或删除点以后的数组元素会根据须要增长或减小它们的索引值,所以数组的其它部分仍然保持连续。

splice()第一个参数指定了插入(或)删除的起始位置。第二个参数指定了应该从数组中删除的元素的个数。若是省略第二个参数,从起点开始到数组结尾的全部元素都将被删除。splice()返回一个由删除元素组成的数组,或者没有删除就返回一个空数组。

            var a = [1, 2, 3, 4, 5, 6, 7, 8];
            a.splice(4); //=> [5, 6, 7, 8] ,a是 [1, 2, 3, 4]
            a.splice(1,2) //=> [2, 3] ,a是 [1, 4]
            a.splice(1,1) //=>[4],a是[1]

splice()的前两个参数指定了要删除的数组的元素。紧随其后的任意个参数指定了须要插入数组中的元素,从第一个参数指定的位置开始插入。例如

            var a = [1, 2, 3, 4, 5];
            a.splice(2,0,'a','b')//=>返回[],a的值是[1, 2, "a", "b", 3, 4, 5]
            a.splice(2,2,[1,2,3],3)//=>返回 ["a", "b"]
            //a 的值是[1, 2, Array[3], 3, 3, 4, 5] 
            //[1, 2, [1,2,3], 3, 3, 4, 5]

注意区别于concat(),splice()会插入数组自己而非数组的元素。

iiiiiii.push()和pop()

push()和pop()方法容许将数组当作栈来使用。push()方法在数组的尾部添加一个或多个元素,并返回新的数组长度。pop()方法相反:它删除数组的最后一个元素,减少数组长度,并返回它删除的值。

注意:两个方法都修改并替换原始数组而非生成一个修改版的新数组。组合使用push()和pop()方法可以使javascript数组实现先进后出的栈。例如:

            var stack = []; //stack:[]
            stack.push(1, 2); //stack:[1,2] 返回2
            stack.pop(); //stack:[1] //返回2
            stack.push(3); //stack:[1,3] //返回2
            stack.pop(); //stack:[1] //返回3
            stack.push([4, 5]); //stack:[1,[4,5]] //返回2
            stack.pop() //stack:[1] //返回[4,5]
            stack.pop() //stcck:[] //返回1

 iiiiiiii.unshift()和shift()

unshift()和shift()方法和行为都相似push()和pop(),不同是的unshift()和shift()方法在数组的头部而非尾部进行元素的插入和删除操做。unshfit()在数组的头部添加一个或多个元素,并将已存在的元素移动到更高的索引的位置来得到足够的空间,最后返回数组的新长度。shift()删除数组的第一个元素并将其返回。而后并把全部的元素下移一个位置来填补数组头部的空缺。

            var a = []; //a:[]
            a.unshift(1); //a:[1] 返回1
            a.unshift(22); //a:[22,1] 返回2
            a.shift(); //a:[1] 返回22
            a.unshift(3, [4, 5]); //a:[3,[4,5],1] 返回3
            a.shift(); //a:[[4,5],1] 返回3
            a.shift(); //a:[1] 返回[4,5]
            a.shift(); //a:[] 返回1

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

iiiiiiiii,toString()和toLocaleString()

数组和其它javascript对象同样拥有toString()方法。征对数组,该方法将其每一个元素转化为字符串(若有必要调用元素的toString() 方法),而且输出逗号分隔的字符串列表。注意,输出不包括方括号或其它任何形式的包裹数值的分隔符。例如

            [1, 2, 3, "hello"].toString(); //=>'1,2,3,hello'
            ["a", "b", "c", "d"].toString(); //=>'a,b,c,d'
            [1, 2, [4, 5, 6], 7].toString(); //=>'1,2,4,5,6,7'

注意,这里与不使用任何参数的join()方法返回的字符串是同样的。

toLocaleString()是toString()方法的本地化版本。它调用元素的toLocaleString()方法将每一个数组元素转化为字符串,并使用本地化(和自定义实现的)分隔符将这些字符串链接起来生成最终的字符串。

9.ECMAScript5中的数组方法

ECMAScript5定义了9个新的方法来遍历、映射、过滤、检测、简化和搜索数组。

在开始介绍以前,颇有必要对ECMAScript5中的数组方法作一个概述。首先,大多数方法的第一个参数接收一个函数,而且对数组的每一个元素(或几个元素)调用一次该函数。若是是稀疏数组,对不存在的元素不调用传递的函数。 在大多数状况下,调用提供的函数提供三个参数:数组元素、元素的索引和数组自己。 一般,只须要第一个参数值,第二个参数是可选的。若是有两个参数,则调用的函数被看作是第二个参数的方法。也就是说:在调用函数时候传递进去的第二个参数做为它的this关键字值来使用

被调用函数的返回值很是重要,可是不一样的方法处理返回值的方式也不同。ECMAScript5中的数组方法都不会修改他们调用的原始数组。固然传递这些方法的函数是能够修改这些数组的。

i.forEach()方法

forEach()方法从头至尾遍历数组,为每一个元素调用指定的函数。如上所述:传递的函数做为forEach()的第一个参数。而后forEach()使用3个个参数调用该函数:数组元素、元素的全部和数组自己。若是只关心数组元素的值,能够编写只有一个参数的函数——额外的参数将忽略。

            var data = [1, 2, 3, 4, 5]; //要 求和的数组
             //计算数组的和
            var sum = 0; //初始值0
            data.forEach(function(value) {
                sum += value; //将每一个值累加到sum上
            });
            sum; //=>15
             //给每一个数组元素自加1
            data.forEach(function(v, i, a) {
                a[i] = v + 1;
            });
            data; //=> [2, 3, 4, 5, 6]

注意的是:forEach()没法在全部元素都传递给调用的函数以前终止遍历。也就是说,没有像for循环中使用的相应的break语句。若是要提早终止。必须把forEach()方法放在一个try块中,并能抛出一个异常。若是forEach()调用的函数能抛出foreach.break异常。循环会提早终止

            function foreach(a, f, t) {
                try {
                    a.forEach(f, t);
                } catch (e) {
                    if (e === foreach.break) return;
                    else throw e;
                }
            }
            foreach.break = new Error("StopIteration");

ii.map()方法

map()方法调用的数组的每一个元素传递给指定的函数,并返回一个数组,它包含该函数的返回值。例如

            a = [1, 2, 3]
             b = a.map(function(x) {
                return x * x;
            });
            b; //=> [1, 4, 9]

传递给map()的函数调用方式和传递给forEach的调用方式同样。但传递给map()的函数应该有返回值。注意:map()返回是的新数组,它不修改调用的数组。若是是稀疏数组,返回的也是相同方式的稀疏数组:它具备相同的长度,相同的缺失元素

iii.filter()

filter()方法返回的数组元素是调用的数组的一个子集。传递的函数是用来逻辑断定的:该函数返回true或false。调用断定函数就像调用forEach()和map()同样。若是返回值为true或能转化为true的值,那么传递给断定函数的元素就是这个子集的成员,它将被添加到一个做为返回值的数组中。例如:

            a = [5, 4, 3, 2, 1];
            smallvalues = a.filter(function(x) {
                return x < 3
            }); //=> [2, 1]
            everyother = a.filter(function(x, i) {
                return i % 2 == 0
            });//=> [5, 3, 1]

注意:filter()跳过稀疏数组中缺乏的元素,它返回的数组老是稠密的。为了压缩稀疏数组的空缺。代码以下:

            var dense = sparse.filter(function() {
                return true;
            });

甚至压缩空缺并删除undefined和null元素,能够这样使用filter():

            a = a.filter(function(x) {
                return x !== undefined && x != null;
            });

iiii.every()和some()

every()和some()方法是数组的逻辑断定:他们对数组元素应用指定的函数进行断定,返回true或false。

every()函数就像数学中的“征对全部”的量词∀ :当且仅当征对数组中的全部元素调用断定函数都返回true,它才返回true

            var a = [1, 2, 3, 4, 5];
            a.every(function(x) {return x < 10;}); //=>true:全部的值都小于10
            a.every(function(x) {return x % 2 === 0;}); //=>fasle :不是全部的值都是偶数

some()方法就像数学中的"存在"量词:当数组中只要有一个元素调用断定函数返回true,它就返回true;而且当且仅当数值中全部元素断定调用函数都返回false,它才返回false.

            var a = [1, 2, 3, 4, 5];
            a.some(function(x) {return x % 2 === 0;}); //true :a含有偶数值
            a.some(isNaN) //=>false a不包含非数值元素

注意,一旦every()和some()确认该返回什么值什么值它们就会中止遍历数组元素。some()在断定函数第一次返回true后就返回true,但若是断定函数一直返回false,它将遍历整个数组。every()刚好相反:它在 断定函数第一次返回false后就返回false,但若是断定函数一直返回true,它将遍历整个数组。

注意:根据数学上的惯例,在空数组时,every()返回true,some()返回false

iiiii,redice()和reduceRight()

reduce()和reduceRight()方法使用指定的函数将数组元素进行组合,生成单个值。这在函数编程中是常见的操做。也能够称为“注入”和“折叠”,例举说明它是如何工做的。

            var a = [1, 2, 3, 4, 5];
            var sum = a.reduce(function(x, y) {return x + y}, 0); //数组求和
            var produce = a.reduce(function(x, y) {return x * y}, 1); //数组求积
            var max = a.reduce(function(x,y){return (x>y)?x:y;}); //求最大值

reduce()须要两个参数,一第一个是执行化简操做的函数。化简函数的任务就是用某种方法把两个值组合成化简一个值,并返回化简后的值。在上述例子中,函数经过加法,乘法或最大值法合成两个值。第二个(可选)的参数是第一个传递给函数的初始值。

 reduce()使用的函数与forEach()和map()使用的函数不一样。比较熟悉的是,数组元素、元素的索引和数组自己做为第2-4个参数传递给函数。第一个参数是到目前为止的化简操做累计的结果。第一次操做函数时,第一个参数是一个初始值,它就是传递给reduce()的第二个参数。在接下来的调用中,这个值就是上一次化简函数的返回值。在上面的第一个例子中,第一次调用化简函数时的参数是0和1。这二者相加并返回1.在此调用的时的参数是1和2,它返回3.而后计算3+3=6,6+4=10,最后计算10+5=15.最后的值是15.reduce()返回这个值。

可能已经注意到了,上面三次调用reduce()时只有一个参数:没有指定初始值。当不指定初始值调用reduce()时,它将使用数组的第一个元素做为其初始值。这意味着第一次调用化简函数就使用了第一个和第二个数组元素做为其第一个和第二个参数。在上面求和和求积的例子中,能够省略初始值参数。

在空数组上,不带初始值参数调用reduce()致使类型错误异常。若是调用它的时候只有一个值--reduce()芝是简单地返回那个值而不会调用化简函数。

reduceRight()工做原理和reduce()同样,不一样的是它按照数组索引从高到低,(从右至左)处理数组,而不是从低到高。若是简化操做的优先顺序是从右到左,你可能想使用它,例如:

            var a = [2, 3, 4];
             //计算2^(3^4)。乘方操做的优先顺序是从右到左
            var big = a.reduceRight(function(accumlator, value) {
                return Math.pow(value, accumlator);
            }); //2.4178516392292583e+24

注意:reduce()和reduceRight()都能接受一个可选参数,它指定了化简函数调用的this关键字的值。可选的初始值参数仍然须要占一个位置。若是想要化简函数做为一个特殊对象的方法调用,请参看function.blind()方法。

值得注意的是,上面描述的every()和some()方法是一种类型的数组化操做。可是不一样的是,他们会尽早终止遍历而不老是访问每个数组元素。

为了简单起见,到目前位置所展现的例子都是数值的,但数学计算不是reduce()和reduceRight()的惟一意图。考虑下6.2中的union()函数,它计算两个对象的并集,并返回另外一个新对象,因此它的工做原理和一个简化函数同样,而且可使用reduce()来把它通常化,计算任意数目对象的“并集”。

        var objects =[{x:1},{y:2},{z:3}];
        var merged = objects.reduce(union); //=>{x:1,y:2,z:3}

回想一下,当两个对象拥有同名的属性时,union()函数使用第一个参数的属性值,这样,reduce()和reduceRighet()在师院union()时给出了不一样的结果:

         function extend(o, p) {
                    for (prop in p) { //遍历p中全部的属性
                        o[prop] = p[prop]; //将遍历属性添加至o中
                    }
                    return o;
                }
          
        function union(o, p) {return extend(extend({}, o), p);};
        
        var objects =[{x:1,a:1},{y:2,a:2},{z:3,a:3}]; 
        var leftunion = objects.reduce(union); //=>{x: 1, a: 3, y: 2, z: 3}
        var rightunion = objects.reduceRight(union); //=> {z: 3, a: 1, y: 2, x: 1}

 iiiiii.indexOf()和lastIndexOf()

indexOf()和lastIndexOf()搜着这个数组时具备给定值的元素,返回找到的第一个元素的全部或者没有找到就返回-1.indexOf()从头到尾搜索,而lastIndexOf()则反方向搜索。

            a = [0, 1, 2, 1, 0];
            a.indexOf(1); //=>1:a[1]是1
            a.lastIndexOf(1); //=>3:a[3]是1
            a.indexOf(3) //=>-1:没有找到值为3的元素

对于本节描述的其它方法,indexOf()和lastIndexOf()方法不接受一个函数做为其参数,第一个参数须要搜索的值,第二个参数是可选的:它指定数组中的一个索引,从哪里开始搜索。若是省略该参数,indexOf()从头开始搜索,而lastIndexOf()从末尾开始搜索。第二个参数也能够是负数,它表示相对数组末尾的偏移量,对于splice()方法:例如:-1指定数组的最后一个元素。

以下函数在一个数组中搜索指定的值并返回包含全部匹配的数组索引的一个数组,它展现了如何运用 indexOf的第二个参数来查找除了第一个之外匹配的值。

                //在数组中查找全部出现的x,并返回一个包含匹配索引的数组
            function findall(a, x) {
                var results = [], //将会返回的数组
                    len = a.length, //待搜索数组的长度
                    pos = 0; //开始搜索的位置
                while (pos < len) { //循环搜素多个元素...
                    pos = a.indexOf(x, pos); //搜素
                    if (pos === -1) break; //未找到,就完成搜素
                    results.push(pos); //不然,在数组中存储索引
                    pos = pos + 1; //并从下一个位置开始搜索
                }
                return results; //返回包含索引的数组
            };

注意,字符串也有indexOf()和lastIndexOf()方法,它们和数组方法功能相似。

10.数组类型

(本文未完结,稍后将更新以下小节,欢迎留意,你们顺即可以浏览前6章节)

在本章咱们处处都能看见数组是具备特殊行为的对象。给定一个未知的对象,断定它是否为数组一般很是有用,在ECMAScript5中,可使用Array.isArray()函数来作这件事情.

            a = [1, 2, 1];
            b = "hello";
            Array.isArray(a); //true
            Array.isArray(b); //false

可是在ECMAScript5之前,要区分数组和非数组对象却使人惊讶的困难。typeof()操做符在这里帮不上忙:它只返回:object(而且对除了函数之外的对象都是如此)instanceof操做符只能用于简单情形:

            a = [1, 2, 1];
            b = "hello";
            a instanceof Array; //=>true
            b instanceof Array; //=>false
            ({}) instanceof Array; //=>false

解决方案是检查对象的类属性6.8.ii。对数组而言改属性的值老是“Array”,

所以在ECMAScript3中isArray()函数代码能够这样写

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

实际上,此类属性检测就是ECMAScript5中Array.isArray()所作的事情。此例子中使用了6.4.ii中Object.prototype.toString()方法

11.类数组对象

咱们已经看到,javascript数组的有一些特性是其它对象所没有的:

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

这些特性让javascript数组和常规的对象有明显的区别,可是他们不是定义数组的本质特性。一种经常彻底合理的见解把拥有一个数值length属性和对于非负整数属性的对象看作一种类型的数组。

实践中这些“类数组”对象实际上偶尔出现,虽然不能再他们上直接调用数组的方法或者指望length属性有什么特殊行为,可是仍然能够用征对真正数组遍历的代码来实现遍历它们,结论就是不少数组算法征对类数组对象工做得很好,就像征对真正的数组同样。若是算法把数组当作只读的或者若是它们至少保持数组长度不变,也尤为是这种状况。

 

如下代码为一个常规对象增长了一些属性使其变成类数组对象,而后遍历生成伪数组“元素”:

            var a = {}; //从一个常规空对象开始
             //添加一组属性,称为“类数组”
            var i = 0;
            while (i < 10) {
                a[i] = i * i;
                i++;
            }
            a.length = i;
            console.log(a) //如今当真正的数组遍历它
            var total = 0;
            for (var j = 0; j < a.length; j++)
                total += a[j];
            console.log(total)

8.3.ii节描述Arguments对象就是一个类数组的对象。在客户端javascript中,一些DOM方法(如document.getElementsByTagName())也返回类数组对象。下面有一个函数 能够用来检测类数组对象

             //断定o是不是一个类数组对象
             //字符串和函数都length属性,可是他们能够有typeOf检测将其排除
             //在客户端javascript中,DOM文本节点也有length属性,须要用额外的o.nodetype != 3将其排除
            function isArrayLike(o) {
                if (o && //o非null、undefined等
                    typeof o === "object" && //o是对象
                    isFinite(o.length) && //o.length是有限数
                    o.length >= o && //o.length是非负数
                    o.length === Math.floor(o.length) && //o.length是整数
                    o.length < 4294967296) //o.length < 2^32
                    return true;
                else
                    return fasle; //不然它不是
            }

在7.12节看到ECMAScript5中字符串的行为与数组相似(而且有些浏览器在ECMAScript5以前已经让字符串变成可索引的了)。而后,相似上述的类数组对象在检测方法征对字符串经常返回false———他们通知最好当作字符串处理,而非数组。
javascript数组方法是特地定义为通用的,所以他们不只应用在正真的数组,并且在类数组对象上都能正常工做。在ECMAScript5中,全部数组方法都是通用的。在ECMAScript3中,除了toString()和toLocaleString觉得全部的方法也是通用的(concat()方法是一个特例:虽然能够用在相似的数组对象上,但它没有将那个对象扩充进返回的数组中)。
既然类数组没有继承Array.prototype,那就不能再它上面直接调用数组方法。尽管如此,能够间接的使用Function.call方法调用

          var a ={"0":"a","1":"b","2":"c",length:3}
          Array.prototype.join.call(a,"+") //=>"a+b+c"
          Array.prototype.slice.call(a,0) //=>["a","b","c"]:真正的数组副本
          Array.prototype.map.call(a.Function(x){
              return x.toUpperCase();
          }) //=>["A","B","C"] 

上文小节的isArray()方法以前咱们就已经见过call()方法,8.7.iii节涵盖Function对象call()方法的更多内容。

ECMAScript1.5数组方法是在Firefox 1.5中引入的。因为它们写法的通常性,Firefox还将这些方法的版本在Array构造函数上直接定义为函数,使用这些方法定义的版本,上个例子能够这么写:

            var a = {"0": "a","1": "b","2": "c",length: 3};
            Array.join(a, "+");
            Array.slice(a, 0);
            Array.map(a, function(x) {
                return x.toUpperCase();
            })

用在类数组对象上时,数组方法的静态函数版本方法很是有用。但既然它们不是标准的,不能指望它们在全部浏览器都有定义。能够这样书写代码保证它们以前是存在的

            Array.join = Array.join || function(a, serp) {
                return Array.prototype.join.call(a, serp);
            };
            Array.slice = Array.slice || function(a, form, to) {
                return Array.prototype.slice.call(a, form, to);
            };
            Array.map = Array.map || function(a, f, thisArg) {
                return Array.prototype.map.call(a, f, thisArg);
            };

 12.做为数组的字符串

在ECMAScript5(在众多最新浏览器已经实现--包括ie8,遭遇ECMAScript5)中,字符串的行为相似于只读数组。除了用charAt()方法来访问单个字符之外,还可使用方括号

        var s = "test";
        s.charAt(0); //=> "t"
        s[0] ;  // =>"t"

固然征对字符串的typeOf操做符仍然返回"String",可是若是给Array.isArray()传递字符串,它将返回false.

可索引字符串的最大好处就是简单,用方括号代替了chartAt()调用,这样更加简洁、可读性更高效。不只如此,字符串的行为相似于数字的事实是的通用数组方法能够应用到字符串上。例如:

            var s = "javascript"
            Array.prototype.join.call(s, " "); //=>' j a v a s c r i p t'
            Array.prototype.filter.call(s, function(x) { //过滤字符串中的字符
                    return x.match(/[^aeiou]/); //匹配非元音字符
                }).join("") //=>jvscrpt

请记住,字符串是不可变值,故当他们做为数组看待时,他们是只读的。如push()、sort()、reverse()、splice()、等数组方法会修改数组,它们在字符串上是无效的。不只如此,使用数组方法修改字符串会致使错误,出错的时候没有提示。

7章完结,欢迎你们关注下章内容:函数。你们顺即可以浏览前6章节)

相关文章
相关标签/搜索