1. 定义

数组的标准定义是:

一个存储元素的线性集合(collection),

元素可以通过索引来任意存取,

索引通常是数字,用来计算元素之间存储位置的偏移量。

几乎所有的编程语言都有类似的数据结构。

然而 JavaScript 的数组却略有不同。

JavaScript 中的数组是一种特殊的对象,

用来表示偏移量的索引(index)是该对象的属性,

索引可能是整数。

然而,这些数字索引在内部被转换为字符串类型,

这是因为 JavaScript 对象中 的属性名必须是字符串。

数组在 JavaScript 中只是一种特殊的对象,所以效率上不如其他 语言中的数组高。

  1. 使用数组

2.1 创建数组

  1. // 通过 [] 操作符声明一个数组变量:
  2. const numbers = [];
  3. console.log(numbers.length); // 显示 0
  4. // 在声明数组变量时,直接在 [] 操作符内放入一组元素
  5. const numbers = [1,2,3,4,5];
  6. console.log(numbers.length); // 显示 5
  7. // 调用 Array 的构造函数创建数组:
  8. const numbers = new Array();
  9. console.log(numbers.length); // 显示 0
  10. // 在调用 Array 的构造函数时,可以只传入一个参数,用来指定数组的长度
  11. const numbers = new Array(10);
  12. console.log(numbers.length); // 显示 10
  13. // 在脚本语言里很常见的一个特性是,数组中的元素不必是同一种数据类型,这一点和很多 编程语言不同
  14. const objects = [1, "Joe", true, null];
  15. // 调用 Array.isArray() 来判断一个对象是否是数组
  16. const numbers = 3;
  17. const arr = [7,4,1776];
  18. console.log(Array.isArray(numbers)); // 显示 false
  19. console.log(Array.isArray(arr)); // 显示 true

大多数 JavaScript 专家推荐使用 [] 操作符,和使用 Array 的构造函数相比,这种方式被认为效率更高(具体参见 O’Reilly 出 版的 JavaScript: The Definitive Guide 和 JavaScript: The Good Parts 这两本书)。

2.2 读写数组

arr[]操作符

在一条赋值语句中,可以使用 [] 操作符将数据赋给数组,比如下面的循环,将 1~100 的 数字赋给一个数组:

  1. var nums = [];
  2. for (var i = 0; i < 100; ++i) {
  3. nums[i] = i+1;
  4. }

还可以使用 [] 操作符读取数组中的元素,如下所示:

  1. var numbers = [1,2,3,4,5];
  2. var sum = numbers[0] + numbers[1] + numbers[2] + numbers[3] + numbers[4];
  3. console.log(sum); // 显示 15

如果要依次读取数组中的所有元素,使用 for 循环无疑会更简单:

  1. var numbers = [1,2,3,5,8,13,21];
  2. var sum = 0;
  3. for (var i = 0; i < numbers.length; ++i) {
  4. sum += numbers[i];
  5. }
  6. console.log(sum); // 显示 53

注意,这里使用数组的 length 属性来控制循环次数,而不是直接使用数字。

JavaScript 中 的数组也是对象,数组的长度可以任意增长,超出其创建时指定的长度。

length 属性反映 的是当前数组中元素的个数,使用它,可以确保循环遍历了数组中的所有元素

2.3 由字符串生成数组

arr.split()

调用字符串对象的 split() 方法也可以生成数组。该方法通过一些常见的分隔符,比如分 隔单词的空格,将一个字符串分成几部分,并将每部分作为一个元素保存于一个新建的数 组中。

下面的这一小段程序演示了 split() 方法的工作原理:

  1. var sentence = "the quick brown fox jumped over the lazy dog";
  2. var words = sentence.split(" ");
  3. for (var i = 0; i < words.length; ++i) {
  4. console.log("word " + i + ": " + words[i]);
  5. }

该程序的输出为:

  1. word 0: the
  2. word 1: quick
  3. word 2: brown
  4. word 3: fox
  5. word 4: jumped
  6. word 5: over
  7. word 6: the
  8. word 7: lazy
  9. word 8: dog

2.4.对数组的整体性操作

有几个操作是将数组作为一个整体进行的。

首先,可以将一个数组赋给另外一个数组:

  1. var nums = [];
  2. for (var i = 0; i < 10; ++i) {
  3. nums[i] = i+1;
  4. }
  5. var samenums = nums;

但是,当把一个数组赋给另外一个数组时,只是为被赋值的数组增加了一个新的引用。
当你通过原引用修改了数组的值,另外一个引用也会感知到这个变化。

下面的代码展示了这种情况:

  1. var nums = [];
  2. for (var i = 0; i < 100; ++i) {
  3. nums[i] = i+1;
  4. }
  5. var samenums = nums;
  6. nums[0] = 400;
  7. console.log(samenums[0]); // 显示 400

这种行为被称为浅复制(浅拷贝),新数组依然指向原来的数组。
一个更好的方案是使用深复制,将原数组中的每一个元素都复制一份到新数组中。可以写一个深复制函数来做这件事:

  1. function copy(arr1, arr2) {
  2. for (var i = 0; i < arr1.length; ++i) {
  3. arr2[i] = arr1[i];
  4. }
  5. }

这样,下述代码片段的输出就和我们希望的一样了:

  1. var nums = [];
  2. for (var i = 0; i < 100; ++i) {
  3. nums[i] = i+1;
  4. }
  5. var samenums = [];
  6. copy(nums, samenums);
  7. nums[0] = 400;
  8. console.log(samenums[0]); // 显示 1
  1. 存取函数

JavaScript 提供了一组用来访问数组元素的函数,叫做存取函数,这些函数返回目标数组 的某种变体

3.1 查找元素

arr.indexOf() 和 arr.lasrIndexOf()

indexOf() 函数是最常用的存取函数之一,用来查找传进来的参数在目标数组中是否存在。 如果目标数组包含该参数,就返回该元素在数组中的索引;如果不包含,就返回 -1。下面 是一个例子:

  1. var names = ["David", "Cynthia", "Raymond", "Clayton", "Jennifer"];
  2. var name = "Cynthia";
  3. var position = names.indexOf(name);
  4. console.log(position) // 1

如果数组中包含多个相同的元素,indexOf() 函数总是返回第一个与参数相同的元素的索 引。有另外一个功能与之类似的函数:lastIndexOf(),该函数返回相同元素中最后一个元 素的索引,如果没找到相同元素,则返回 -1。下面是一个例子:

  1. var names = ["David", "Mike", "Cynthia", "Raymond", "Clayton", "Mike", "Jennifer"];
  2. var name = "Mike";
  3. var firstPos = names.indexOf(name);
  4. console.log("First found " + name + " at position " + firstPos);
  5. // First found Mike at position 1
  6. var lastPos = names.lastIndexOf(name);
  7. console.log("Last found " + name + " at position " + lastPos);
  8. // Last found Mike at position 5

3.2 数组的字符串表示

arr.join() 和 arr.toString()

有两个方法可以将数组转化为字符串:join() 和 toString()。这两个方法都返回一个包含 数组所有元素的字符串,各元素之间用逗号分隔开。下面是一些例子:

  1. var names = ["David", "Cynthia", "Raymond", "Clayton", "Mike", "Jennifer"];
  2. var namestr = names.join();
  3. console.log(namestr); // David,Cynthia,Raymond,Clayton,Mike,Jennifer
  4. namestr = names.toString();
  5. console.log(namestr); // David,Cynthia,Raymond,Clayton,Mike,Jennifer

3.3 由已有的数组创建新数组

arr.concat() 和 arr.splice()

concat() 和 splice() 方法允许通过已有数组创建新数组。concat 方法可以合并多个数组 创建一个新数组,splice() 方法截取一个数组的子集创建一个新数组。

我们先来看看 concat() 方法的工作原理。该方法的发起者是一个数组,参数是另一个数 组。作为参数的数组,其中的所有元素都被连接到调用 concat() 方法的数组后面。下面的 程序展示了 concat() 方法的工作原理:

  1. var cisDept = ["Mike", "Clayton", "Terrill", "Danny", "Jennifer"];
  2. var dmpDept = ["Raymond", "Cynthia", "Bryan"];
  3. var itDiv = cis.concat(dmp);
  4. console.log(itDiv);
  5. itDiv = dmp.concat(cisDept);
  6. console.log(itDiv);

输出为:

  1. Mike,Clayton,Terrill,Danny,Jennifer,Raymond,Cynthia,Bryan Raymond,Cynthia,Bryan,Mike,Clayton,Terrill,Danny,Jennifer

第一行首先输出 cis 数组里的元素,第二行首先输出 dmp 数组里的元素。
splice() 方法从现有数组里截取一个新数组。该方法的第一个参数是截取的起始索引,第 二个参数是截取的长度。下面的程序展示了 splice() 方法的工作原理:(改变了原数组)

  1. var itDiv = ["Mike","Clayton","Terrill","Raymond","Cynthia","Danny","Jennifer"];
  2. var dmpDept = itDiv.splice(3,3);
  3. var cisDept = itDiv;
  4. console.log(dmpDept); // Raymond,Cynthia,Danny
  5. console.log(cisDept); // Mike,Clayton,Terrill,Jennifer

splice() 方法还有其他用法,比如为一个数组增加或移除元素,

  1. 可变函数

4.1 为数组添加元素

有两个方法可以为数组添加元素:push() 和 unshift()。push() 方法会将一个元素添加到 数组末尾:

  1. var nums = [1,2,3,4,5];
  2. console.log(nums); // 1,2,3,4,5
  3. nums.push(6);
  4. console.log(nums); // 1,2,3,4,5,6

也可以使用数组的 length 属性为数组添加元素,但 push() 方法看起来更直观:

  1. var nums = [1,2,3,4,5];
  2. console.log(nums); // 1,2,3,4,5
  3. nums[nums.length] = 6;
  4. console.log(nums); // 1,2,3,4,5,6

和在数组的末尾添加元素比起来,在数组的开头添加元素更难。如果不利用数组提供的可 变函数,则新的元素添加进来后,需要把后面的每个元素都相应地向后移一个位置。下面 的代码展示了这一过程:

  1. var nums = [2,3,4,5];
  2. var newnum = 1;
  3. var N = nums.length;
  4. for (var i = N; i >= 0; --i) {
  5. nums[i] = nums[i-1];
  6. }
  7. nums[0] = newnum;
  8. console.log(nums); // 1,2,3,4,5

随着数组中存储的元素越来越多,上述代码将会变得越来越低效。
unshift() 方法可以将元素添加在数组的开头,下述代码展示了该方法的用法:

  1. var nums = [2,3,4,5];
  2. console.log(nums); // 2,3,4,5
  3. var newnum = 1;
  4. nums.unshift(newnum);
  5. console.log(nums); // 1,2,3,4,5
  6. nums = [3,4,5];
  7. nums.unshift(newnum,1,2);
  8. console.log(nums); // 1,2,3,4,5

第二次出现的 unshift() 方法展示了可以通过一次调用,为数组添加多个元素。

4.2 从数组中删除元素

使用 pop() 方法可以删除数组末尾的元素:

  1. var nums = [1,2,3,4,5,9]; nums.pop(); console.log(nums); // 1,2,3,4,5

如果没有可变函数,从数组中删除第一个元素需要将后续元素各自向前移动一个位置,和 在数组开头添加一个元素一样低效:

  1. var nums = [9,1,2,3,4,5]; console.log(nums); for (var i = 0; i < nums.length; ++i) { nums[i] = nums[i+1]; } console.log(nums); // 1,2,3,4,5,

除了要将后续元素前移一位,还多出了一个元素。当打印出数组中的元素时,会发现最后 多出一个逗号。
shift() 方法可以删除数组的第一个元素,下述代码展示了该方法的用法:

  1. var nums = [9,1,2,3,4,5]; nums.shift(); console.log(nums); // 1,2,3,4,5

这回数组末尾那个多余的逗号消失了。pop() 和 shift() 方法都将删掉的元素作为方法的 返回值返回,因此可以使用一个变量来保存删除的元素:

  1. var nums = [6,1,2,3,4,5]; var first = nums.shift(); // first gets the value 9 nums.push(first); console.log(nums); // 1,2,3,4,5,6

4.3 从数组中间位置添加和删除元素

删除数组中的第一个元素和在数组开头添加一个元素存在同样的问题——两种操作都需要将 数组中的剩余元素向前或向后移,然而 splice() 方法可以帮助我们执行其中任何一种操作。

使用 splice() 方法为数组添加元素,需提供如下参数:

  • 起始索引(也就是你希望开始添加元素的地方);
  • 需要删除的元素个数(添加元素时该参数设为 0);
  • 想要添加进数组的元素。

看一个简单的例子。下面的程序在数组中间插入元素:

  1. var nums = [1,2,3,7,8,9];
  2. var newElements = [4,5,6];
  3. nums.splice(3,0,newElements);
  4. console.log(nums); // 1,2,3,4,5,6,7,8,9

要插入数组的元素不必组织成一个数组,它可以是任意的元素序列,比如:

  1. var nums = [1,2,3,7,8,9];
  2. nums.splice(3,0,4,5,6);
  3. console.log(nums);

在上面的例子中,参数 4、5、6 就是我们想插入数组 nums 的元素序列。
下面是使用 splice() 方法从数组中删除元素的例子:

  1. var nums = [1,2,3,100,200,300,400,4,5];
  2. nums.splice(3,4);
  3. console.log(nums); // 1,2,3,4,5

4.4 为数组排序

剩下的两个可变方法是为数组排序。第一个方法是 reverse(),该方法将数组中元素的顺 序进行翻转。下面这个例子展示了该如何使用该方法:

  1. var nums = [1,2,3,4,5];
  2. nums.reverse();
  3. console.log(nums); // 5,4,3,2,1

对数组进行排序是经常会遇到的需求,如果元素是字符串类型,那么数组的可变方法 sort() 就非常好使:

  1. var names = ["David","Mike","Cynthia","Clayton","Bryan","Raymond"]; names.sort();
  2. console.log(names); // Bryan,Clayton,Cynthia,David,Mike,Raymond

但是如果数组元素是数字类型,
sort() 方法的排序结果就不能让人满意了:

  1. var nums = [3,1,2,100,4,200];
  2. nums.sort();
  3. console.log(nums); // 1,100,2,200,3,4

sort() 方法是按照字典顺序对元素进行排序的,因此它假定元素都是字符串类型,在上一 个例子中,即使元素是数字类型,也被认为是字符串类型。为了让 sort() 方法也能排序数 字类型的元素,可以在调用方法时传入一个大小比较函数,排序时,sort() 方法将会根据 该函数比较数组中两个元素的大小,从而决定整个数组的顺序。
对于数字类型,该函数可以是一个简单的相减操作,从一个数字中减去另外一个数字。如 果结果为负,那么被减数小于减数;如果结果为 0,那么被减数与减数相等;如果结果为 正,那么被减数大于减数。

将这些搞清楚之后,传入一个大小比较函数,再来看看前面的例子:

  1. function compare(num1, num2) {
  2. return num1 - num2;
  3. }
  4. var nums = [3,1,2,100,4,200];
  5. nums.sort(compare);
  6. console.log(nums); // 1,2,3,4,100,200

sort() 函数使用了 compare() 函数对数组按照数字大小进行排序,而不是按照字典顺序。

  1. 迭代器方法

最后一组方法是迭代器方法。这些方法对数组中的每个元素应用一个函数,可以返回一个 值、一组值或者一个新数组。

5.1 不生成新数组的迭代器方法

我们要讨论的第一组迭代器方法不产生任何新数组,相反,它们要么对于数组中的每个元 素执行某种操作,要么返回一个值。

这组中的第一个方法是 forEach(),该方法接受一个函数作为参数,对数组中的每个元素 使用该函数。下面这个例子展示了如何使用该方法:

  1. function square(num) {
  2. console.log(num, num * num);
  3. }
  4. var nums = [1,2,3,4,5,6,7,8,9,10];
  5. nums.forEach(square);

该程序的输出为:

  1. 1 1
  2. 2 4
  3. 3 9
  4. 4 16
  5. 5 25
  6. 6 36
  7. 7 49
  8. 8 64
  9. 9 81
  10. 10 100

另一个迭代器方法是 every(),该方法接受一个返回值为布尔类型的函数,对数组中的每 个元素使用该函数。如果对于所有的元素,该函数均返回 true,则该方法返回 true。下面 是一个例子:

  1. function isEven(num) {
  2. return num % 2 == 0;
  3. }
  4. var nums = [2,4,6,8,10];
  5. var even = nums.every(isEven);
  6. if (even) {
  7. console.log("all numbers are even");
  8. } else {
  9. console.log("not all numbers are even");
  10. }

输出为:

  1. all numbers are even

将数组改为:

  1. var nums = [2,4,6,7,8,10];

输出为:

  1. not all numbers are even

some() 方法也接受一个返回值为布尔类型的函数,只要有一个元素使得该函数返回 true, 该方法就返回 true。比如:

  1. function isEven(num) {
  2. return num % 2 == 0;
  3. }
  4. var nums = [1,2,3,4,5,6,7,8,9,10];
  5. var someEven = nums.some(isEven);
  6. if (someEven) {
  7. console.log("some numbers are even");
  8. } else {
  9. console.log("no numbers are even");
  10. }
  11. nums = [1,3,5,7,9];
  12. someEven = nums.some(isEven);
  13. if (someEven) {
  14. console.log("some numbers are even");
  15. } else {
  16. console.log("no numbers are even");
  17. }

该程序的输出为:

  1. some numbers are even
  2. no numbers are even

reduce() 方法接受一个函数,返回一个值。该方法会从一个累加值开始,不断对累加值和 数组中的后续元素调用该函数,直到数组中的最后一个元素,最后返回得到的累加值。下 面这个例子展示了如何使用 reduce() 方法为数组中的元素求和:

  1. function add(runningTotal, currentValue) {
  2. return runningTotal + currentValue;
  3. }
  4. var nums = [1,2,3,4,5,6,7,8,9,10];
  5. var sum = nums.reduce(add);
  6. console.log(sum); // 显示 55

reduce() 方法和 add() 函数一起,从左到右,依次对数组中的元素求和,其执行过程如下 所示:

  1. add(1,2) -> 3
  2. add(3,3) -> 6
  3. add(6,4) -> 10
  4. add(10,5) -> 15
  5. add(15,6) -> 21
  6. add(21,7) -> 28
  7. add(28,8) -> 36
  8. add(36,9) -> 45
  9. add(45,10) -> 55

reduce() 方法也可以用来将数组中的元素连接成一个长的字符串:

  1. function concat(accumulatedString, item) {
  2. return accumulatedString + item;
  3. }
  4. var words = ["the ", "quick ","brown ", "fox "];
  5. var sentence = words.reduce(concat);
  6. console.log(sentence); // 显示 "the quick brown fox"

JavaScript 还提供了 reduceRight() 方法,和 reduce() 方法不同,它是从右到左执行。下面 的程序使用 reduceRight() 方法将数组中的元素进行翻转:

  1. function concat(accumulatedString, item) {
  2. return accumulatedString + item;
  3. }
  4. var words = ["the ", "quick ","brown ", "fox "];
  5. var sentence = words.reduceRight(concat);
  6. console.log(sentence); // 显示 "fox brown quick the"

5.2 生成新数组的迭代器方法

有两个迭代器方法可以产生新数组:map() 和 filter()。

map() 和 forEach() 有点儿像,对 数组中的每个元素使用某个函数。

两者的区别是 map() 返回一个新的数组,该数组的元素 是对原有元素应用某个函数得到的结果。

下面给出一个例子:

  1. function curve(grade) {
  2. return grade += 5;
  3. }
  4. var grades = [77, 65, 81, 92, 83];
  5. var newgrades = grades.map(curve);
  6. console.log(newgrades); // 82, 70, 86, 97, 88

下面是对一个字符串数组使用 map() 方法的例子:

  1. function first(word) {
  2. return word[0];
  3. }
  4. var words = ["for","your","information"];
  5. var acronym = words.map(first);
  6. console.log(acronym.join("")); // 显示 "fyi"

在上面这个例子中,数组 acronym 保存了数组 words 中每个元素的第一个字母。然而,如 果想将数组显示为真正的缩略形式,必须想办法除掉连接每个数组元素的逗号,如果直接 调用 toString() 方法,就会显示出这个逗号。使用 join() 方法,为其传入一个空字符串 作为参数,则可以帮助我们解决这个问题。
filter() 和 every() 类似,传入一个返回值为布尔类型的函数。

和 every() 方法不同的是, 当对数组中的所有元素应用该函数,结果均为 true 时,该方法并不返回 true,而是返回 一个新数组,该数组包含应用该函数后结果为 true 的元素。

下面是一个例子:

  1. function isEven(num) {
  2. return num % 2 == 0;
  3. }
  4. function isOdd(num) {
  5. return num % 2 != 0;
  6. }
  7. var nums = [];
  8. for (var i = 0; i < 20; ++i) {
  9. nums[i] = i+1;
  10. }
  11. var evens = nums.filter(isEven);
  12. console.log("Even numbers: ");
  13. console.log(evens);
  14. var odds = nums.filter(isOdd);
  15. console.log("Odd numbers: ");
  16. console.log(odds);

该程序的执行结果如下:

  1. Even numbers:
  2. 2,4,6,8,10,12,14,16,18,20
  3. Odd numbers:
  4. 1,3,5,7,9,11,13,15,17,19

下面是另一个使用 filter() 方法的有趣案例:

  1. function passing(num) {
  2. return num >= 60;
  3. }
  4. var grades = [];
  5. for (var i = 0; i < 20; ++i) {
  6. grades[i] = Math.floor(Math.random() * 101);
  7. }
  8. var passGrades = grades.filter(passing);
  9. console.log("All grades: );
  10. console.log(grades);
  11. console.log("Passing grades: ");
  12. console.log(passGrades);

程序显示:

  1. All grades:
  2. 39,43,89,19,46,54,48,5,13,31,27,95,62,64,35,75,79,88,73,74
  3. Passing grades:
  4. 89,95,62,64,75,79,88,73,74

当然,还可以使用 filter() 方法过滤字符串数组,下面这个例子过滤掉了那些不包含 “cie”的单词:

  1. function afterc(str) {
  2. if (str.indexOf("cie") > -1) {
  3. return true;
  4. }
  5. return false;
  6. }
  7. var words = ["recieve","deceive","percieve","deceit","concieve"];
  8. var misspelled = words.filter(afterc);
  9. console.log(misspelled); // 显示 recieve,percieve,concieve
  1. 二维和多维数组

JavaScript 只支持一维数组,但是通过在数组里保存数组元素的方式,可以轻松创建多维 数组。本节将讨论如何在 JavaScript 中创建二维数组。

6.1 创建二维数组

二维数组类似一种由行和列构成的数据表格。在 JavaScript 中创建二维数组,需要先创建 一个数组,然后让数组的每个元素也是一个数组。最起码,我们需要知道二维数组要包含 多少行,有了这个信息,就可以创建一个 n 行 1 列的二维数组了:

  1. var twod = [];
  2. var rows = 5;
  3. for (var i = 0; i < rows; ++i) {
  4. twod[i] = [];
  5. }

这样做的问题是,数组中的每个元素都是 undefined。更好的方式是遵照 JavaScript: The Good Parts(O’Reilly)一书第 64 页的例子,Crockford 通过扩展 JavaScript 数组对象,为 其增加了一个新方法,该方法根据传入的参数,设定了数组的行数、列数和初始值。下面 是这个方法的定义:

  1. Array.matrix = function(numrows, numcols, initial) {
  2. var arr = [];
  3. for (var i = 0; i < numrows; ++i) {
  4. var columns = [];
  5. for (var j = 0; j < numcols; ++j) {
  6. columns[j] = initial;
  7. }
  8. arr[i] = columns;
  9. }
  10. return arr;
  11. }

下面是测试该方法的一些测试代码:

  1. var nums = Array.matrix(5,5,0);
  2. console.log(nums[1][1]); // 显示 0
  3. var names = Array.matrix(3,3,"");
  4. names[1][2] = "Joe";
  5. console.log(names[1][2]); // display"Joe"

还可以仅用一行代码就创建并且使用一组初始值来初始化一个二维数组:

  1. var grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]]; console.log(grades[2][2]); // 显示 89

对于小规模的数据,这是创建二维数组最简单的方式。

6.2 处理二维数组的元素

处理二维数组中的元素,有两种最基本的方式:按列访问和按行访问。我们将使用前面创 建的数组 grades 来展示这两种方式的工作原理。

对于两种方式,我们均使用一组嵌入式的 for 循环。对于按列访问,外层循环对应行,内 层循环对应列。以数组 grades 为例,每一行对应一个学生的成绩记录。我们可以将该学生 的所有成绩相加,然后除以科目数得到该学生的平均成绩。下面的代码展示了这一过程:

  1. var grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
  2. var total = 0;
  3. var average = 0.0;
  4. for (var row = 0; row < grades.length; ++row) {
  5. for (var col = 0; col < grades[row].length; ++col) {
  6. total += grades[row][col];
  7. }
  8. average = total / grades[row].length;
  9. console.log("Student " + parseInt(row+1) + " average: " + average.toFixed(2));
  10. total = 0;
  11. average = 0.0;
  12. }

内层循环由下面这个表达式控制:

  1. col < grades[row].length

这个表达式之所以可行,是因为每一行都是一个数组,我们可以使用数组的 length 属性判 断每行包含多少列。
以下为程序的输出:

  1. Student 1 average: 81.33
  2. Student 2 average: 79.67
  3. Student 3 average: 91.33

对于按行访问,只需要稍微调整 for 循环的顺序,使外层循环对应列,内层循环对应行即 可。下面的程序计算了一个学生各科的平均成绩:

  1. var grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
  2. var total = 0;
  3. var average = 0.0;
  4. for (var col = 0; col < grades.length; ++col) {
  5. for (var row = 0; row < grades[col].length; ++row) {
  6. total += grades[row][col];
  7. }
  8. average = total / grades[col].length;
  9. console.log("Test " + parseInt(col+1) + " average: " + average.toFixed(2));
  10. total = 0;
  11. average = 0.0;
  12. }

该程序的输出为:

  1. Test 1 average: 85.33
  2. Test 2 average: 84.33
  3. Test 3 average: 82.67

6.3 参差不齐的数组

参差不齐的数组是指数组中每行的元素个数彼此不同。有一行可能包含三个元素,另一行 可能包含五个元素,有些行甚至只包含一个元素。很多编程语言在处理这种参差不齐的数 组时表现都不是很好,但是 JavaScript 却表现良好,因为每一行的长度是可以通过计算得 到的。

为了给个示例,假设数组 grades 中,每个学生成绩记录的个数是不一样的,不用修改代 码,依然可以正确计算出正确的平均分:

  1. var grades = [[89, 77],[76, 82, 81],[91, 94, 89, 99]];
  2. var total = 0;
  3. var average = 0.0;
  4. for (var row = 0; row < grades.length; ++row) {
  5. for (var col = 0; col < grades[row].length; ++col) {
  6. total += grades[row][col];
  7. }
  8. average = total / grades[row].length;
  9. console.log("Student " + parseInt(row+1) + " average: " + average.toFixed(2));
  10. total = 0;
  11. average = 0.0;
  12. }

注意第一名同学只有两门课的成绩,而第二名同学有三门课的成绩,第三名同学有四门课 的成绩。因为程序在内层的 for 循环中计算了每个数组的长度,即使数组中每一行的长度 不一,程序依然不会出什么问题。该段程序的输出为:

  1. Student 1 average: 83.00
  2. Student 2 average: 79.67
  3. Student 3 average: 93.25
  1. 对象数组

到现在为止,本章讨论的数组都只包含基本数据类型的元素,比如数字和字符串。数组还 可以包含对象,数组的方法和属性对对象依然适用。

请看下面的例子:

  1. function Point(x,y) {
  2. this.x = x;
  3. this.y = y;
  4. }
  5. function displayPts(arr) {
  6. for (var i = 0; i < arr.length; ++i) {
  7. console.log(arr[i].x + ", " + arr[i].y);
  8. }
  9. }
  10. var p1 = new Point(1,2);
  11. var p2 = new Point(3,5);
  12. var p3 = new Point(2,8);
  13. var p4 = new Point(4,4);
  14. var points = [p1,p2,p3,p4];
  15. for (var i = 0; i < points.length; ++i) {
  16. console.log("Point " + parseInt(i+1) + ": " + points[i].x + ", " + points[i].y);
  17. }
  18. var p5 = new Point(12,-3);
  19. points.push(p5);
  20. console.log("After push: ");
  21. displayPts(points);
  22. points.shift();
  23. console.log("After shift: ");
  24. displayPts(points);

这段程序的输出为:

  1. Point 1: 1, 2
  2. Point 2: 3, 5
  3. Point 3: 2, 8
  4. Point 4: 4, 4
  5. After push:
  6. 1, 2
  7. 3, 5
  8. 2, 8
  9. 4, 4
  10. 12, -3
  11. After shift:
  12. 3, 5
  13. 2, 8
  14. 4, 4
  15. 12, -3

使用 push() 方法将点 (12, -3) 添加进数组,使用 shift() 方法将点 (1, 2) 从数组中移除。

  1. 对象中的数组

在对象中,可以使用数组存储复杂的数据。本书中讨论的很多数据都被实现成一个对象, 对象内部使用数组保存数据。

下面的例子展示了书中用到的很多技术。在例子中,我们创建了一个对象,用于保存观测 到的周最高气温。该对象有两个方法,一个方法用来增加一条新的气温记录,另外一个方 法用来计算存储在对象中的平均气温。代码如下所示:

  1. function weekTemps() {
  2. this.dataStore = [];
  3. this.add = add;
  4. this.average = average;
  5. }
  6. function add(temp) {
  7. this.dataStore.push(temp);
  8. }
  9. function average() {
  10. var total = 0;
  11. for (var i = 0; i < this.dataStore.length; ++i) {
  12. total += this.dataStore[i];
  13. }
  14. return total / this.dataStore.length;
  15. }
  16. var thisWeek = new weekTemps();
  17. thisWeek.add(52);
  18. thisWeek.add(55);
  19. thisWeek.add(61);
  20. thisWeek.add(65);
  21. thisWeek.add(55);
  22. thisWeek.add(50);
  23. thisWeek.add(52);
  24. thisWeek.add(49);
  25. console.log(thisWeek.average()); // 显示 54.875

add() 方法中用到了数组的 push() 方法,将元素添加到数组 dataStore 中,为什么这个方 法名要叫 add() 而不是 push() ?这是因为在定义方法时,使用一个更直观的名字是常用的 技巧,不是所有人都知道 push 一个元素是什么意思,但是所有人都知道 add 一个元素是什 么意思。