索引集合类 (Indexed collections)

这个章节主要介绍了以索引进行排序的数据集合。包括数组以及类似于数组的数据结构,如 TypedArray 

数组对象(Array object)

数组(array)是一个有序的数据集合,我们可以通过数组名称(name)和索引(index)进行访问。例如,我们定义了一个数组emp,数组中的每个元素包含了一个雇员的名字以及其作为索引的员工号。那么emp[1]将会代表1号员工,emp[2]将会代表2号员工,以此类推。

JavaScript中没有明确的数组数据类型。但是,我们可以通过使用内置Array对象和它的方法对数组进行操作。Array对象有很多操作数组的方法,比如合并、反转、排序等。数组对象有一个决定数组长度和使用正则表达式操作其他属性的属性。

创建数组(creating an array)

以下语句创建等效的数组:

var arr = new Array(element0, element1, ..., elementN);
var arr = Array(element0, element1, ..., elementN);
var arr = [element0, element1, ..., elementN];

// 译者注: var arr=[4] 和 var arr=new Array(4)是不等效的,
// 后者4指数组长度,所以使用字面值(literal)的方式应该不仅仅是便捷,同时也不易踩坑

element0, element1, ..., elementN 是数组元素的值的列表。当这些值被指定后,数组将被初始化,他们将被作为数组元素。数组的length属性也会被设为参数的个数。

括号语法被称为 "数组字面值" 或 "数组初始化器", 它比其他创建数组的方式更便捷,所以通常是首选。详细内容参见 Array literals 。

为了创建一个长度不为0,但是又没有任何元素的数组,可选以下任何一种方式:

var arr = new Array(arrayLength);
var arr = Array(arrayLength);

// 这样有同样的效果
var arr = [];
arr.length = arrayLength;

注意: 以上代码,数组长度(arrayLength)必须为一个数字(Number)。否则,将会创建一个只有单个(所输入的)元素的数组。 调用 arr.length 会返回数组长度,但是数组实际上包含了空的(undefined)元素。 因此在数组上使用 for...in 循环,将不会返回任何的值 。

除了如上所示创建新定义的变量,数组(array)也可以作为一个属性(property)分配给一个新的或者已存在的对象(object):

var obj = {};
// ...
obj.prop = [element0, element1, ..., elementN];

// OR
var obj = {prop: [element0, element1, ...., elementN]}

如果你希望用单个元素初始化一个数组,而这个元素恰好又是数字(Number),那么你必须使用括号语法。当单个的数字(Number)传递给Array()构造函数时,将会被解释为数组长度,并非单个元素。

var arr = [42];      // 创建一个只有唯一元素的数组:
                     // the number 42.
var arr = Array(42); // 创建一个没有元素的数组, 
                     // 但是数组的长度被设置成42.

// 上面的代码与下面的代码等价
var arr = [];
arr.length = 42;

如果N不是一个整数,调用Array(N)将会报RangeError错误, 以下方法说明了这种行为:

var arr = Array(9.3);  // RangeError: Invalid array length

如果你需要创建任意类型的单元素数组,安全的方式是使用字面值。或者在向数组添加单个元素之前先创建一个空的数组。

填充数组(populating an array)

你可以通过给元素赋值来填充数组,例如:

var emp = [];
emp[0] = "Casey Jones";
emp[1] = "Phil Lesh";
emp[2] = "August West";

注意:如果你在以上代码中给数组操作符的是一个非整形数值,那么将作为一个代表数组的对象的属性(property)创建,而非作为数组的元素。

var arr = [];
arr[3.4] = "Oranges";
console.log(arr.length);                // 0
console.log(arr.hasOwnProperty(3.4));   // true

你也可以在创建数组的时候去填充它:

var myArray = new Array("Hello", myVar, 3.14159);
var myArray = ["Mango", "Apple", "Orange"]

引用数组元素(referring to array elements)

您通过可以使用元素的序号来引用数组的元素。例如,假设你定义了如下数组:

var myArray = ["Wind", "Rain", "Fire"];

你可以用 myArray[0]引用第一个元素,myArray[1]引用第二个元素。元素的索引是从0开始的。

注意:数组操作符(方括号 [ ])也可以用来访问数组的属性(在 JavaScript 中,数组也是对象)。例如:

var arr = ["one", "two", "three"];
arr[2];  // three
arr["length"];  // 3

理解 length

在实施层面, JavaScript实际上是将元素作为标准的对象属性来存储,把数组索引作为属性名。长度属性是特殊的,它总是返回最后一个元素的索引值加1(下例中, Dusty 的索引是30,所以cats.length 返回 30 + 1)。记住, JavaScript 数组索引是基于0的: 他们从0开始,而不是1。这意味着数组长度属性将比最大的索引值大1:

var cats = [];
cats[30] = ['Dusty'];
console.log(cats.length); // 31

你也可以分配length属性。写一个小于数组元素数量的值会缩短数组,写0会彻底清空数组:

var cats = ['Dusty', 'Misty', 'Twiggy'];
console.log(cats.length); // 3

cats.length = 2;
console.log(cats); // logs "Dusty,Misty" - Twiggy has been removed

cats.length = 0;
console.log(cats); // logs nothing; the cats array is empty

cats.length = 3;
console.log(cats); // [undefined, undefined, undefined]

遍历数组(interating over array)

遍历数组元素并以某种方式处理每个元素是一个常见的操作。以下是最简单的方式:

var colors = ['red', 'green', 'blue'];
for (var i = 0; i < colors.length; i++) {
  console.log(colors[i]);
}

如果你确定数组中没有一个元素的求值是false —— 如果你的数组只包含DOM节点,如下,你可以选择一个更高效的土法子:

var divs = document.getElementsByTagName('div');
for (var i = 0, div; div = divs[i]; i++) {
  /* Process div in some way */
}

这样避免了检测数组长度的开销,额外的好处是确保了div变量当前在每次循环中都被重新赋值为当前项。

forEach() 方法提供了遍历数组元素的其他方法:

var colors = ['red', 'green', 'blue'];
colors.forEach(function(color) {
  console.log(color);
});

被传递给forEach的函数会在数组的每个元素像上执行一次,元素作为参数传递给该函数。未赋值的值不会在forEach循环迭代。

注意,在数组定义时省略的元素不会在forEach遍历时被列出,但是手动赋值为undefined的元素是会被列出的:

var array = ['first', 'second', , 'fourth'];

// returns ['first', 'second', 'fourth'];
array.forEach(function(element) {
  console.log(element);
})

if(array[2] === undefined) { console.log('array[2] is undefined'); } // true

var array = ['first', 'second', undefined, 'fourth'];

// returns ['first', 'second', undefined, 'fourth'];
array.forEach(function(element) {
  console.log(element);
})

一旦 JavaScript 元素被保存为标准的对象属性,通过for...in 循环来迭代数组将变得不明智,因为正常元素和所有可枚举的属性都会被列出。

数组的方法(array methods)

Array 对象具有下列方法:

concat() 连接两个数组并返回一个新的数组。

var myArray = new Array("1", "2", "3");
myArray = myArray.concat("a", "b", "c"); 
// myArray is now ["1", "2", "3", "a", "b", "c"]

join(deliminator = ',') 将数组的所有元素连接成一个字符串。

var myArray = new Array("Wind", "Rain", "Fire");
var list = myArray.join(" - "); // list is "Wind - Rain - Fire"

push() 在数组末尾添加一个或多个元素,并返回数组操作后的长度。

var myArray = new Array("1", "2");
myArray.push("3"); // myArray is now ["1", "2", "3"]

pop() 从数组移出最后一个元素,并返回该元素。

var myArray = new Array("1", "2", "3");
var last = myArray.pop(); 
// myArray is now ["1", "2"], last = "3"

shift() 从数组移出第一个元素,并返回该元素。

var myArray = new Array ("1", "2", "3");
var first = myArray.shift(); 
// myArray is now ["2", "3"], first is "1"

unshift() 在数组开头添加一个或多个元素,并返回数组的新长度。

var myArray = new Array ("1", "2", "3");
myArray.unshift("4", "5"); 
// myArray becomes ["4", "5", "1", "2", "3"]

slice(start_index, upto_index) 从数组提取一个片段,并作为一个新数组返回。

var myArray = new Array ("a", "b", "c", "d", "e");
myArray = myArray.slice(1, 4); // starts at index 1 and extracts all elements
                               // until index 3, returning [ "b", "c", "d"]

splice(index, count_to_remove, addElement1, addElement2, ...)从数组移出一些元素,(可选)并替换它们。

var myArray = new Array ("1", "2", "3", "4", "5");
myArray.splice(1, 3, "a", "b", "c", "d"); 
// myArray is now ["1", "a", "b", "c", "d", "5"]
// This code started at index one (or where the "2" was), 
// removed 3 elements there, and then inserted all consecutive
// elements in its place.

reverse() 颠倒数组元素的顺序:第一个变成最后一个,最后一个变成第一个。

var myArray = new Array ("1", "2", "3");
myArray.reverse(); 
// transposes the array so that myArray = [ "3", "2", "1" ]

sort() 给数组元素排序。

var myArray = new Array("Wind", "Rain", "Fire");
myArray.sort(); 
// sorts the array so that myArray = [ "Fire", "Rain", "Wind" ]

sort() 也可以带一个回调函数来决定怎么比较数组元素。这个回调函数比较两个值,并返回3个值中的一个:

例如,下面的代码通过字符串的最后一个字母进行排序:

var sortFn = function(a, b){
  if (a[a.length - 1] < b[b.length - 1]) return -1;
  if (a[a.length - 1] > b[b.length - 1]) return 1;
  if (a[a.length - 1] == b[b.length - 1]) return 0;
}
myArray.sort(sortFn); 
// sorts the array so that myArray = ["Wind","Fire","Rain"]
  • 如果 a 小于 b ,返回 -1(或任何负数)
  • 如果 a 大于 b ,返回 1 (或任何正数)
  • 如果 ab 相等,返回 0。

indexOf(searchElement[, fromIndex]) 在数组中搜索searchElement 并返回第一个匹配的索引。

var a = ['a', 'b', 'a', 'b', 'a'];
console.log(a.indexOf('b')); // logs 1
// Now try again, starting from after the last match
console.log(a.indexOf('b', 2)); // logs 3
console.log(a.indexOf('z')); // logs -1, because 'z' was not found

lastIndexOf(searchElement[, fromIndex]) 和 indexOf 差不多,但这是从结尾开始,并且是反向搜索。

var a = ['a', 'b', 'c', 'd', 'a', 'b'];
console.log(a.lastIndexOf('b')); // logs 5
// Now try again, starting from before the last match
console.log(a.lastIndexOf('b', 4)); // logs 1
console.log(a.lastIndexOf('z')); // logs -1

forEach(callback[, thisObject]) 在数组每个元素项上执行callback

var a = ['a', 'b', 'c'];
a.forEach(function(element) { console.log(element);} ); 
// logs each item in turn

map(callback[, thisObject]) 在数组的每个单元项上执行callback函数,并把返回包含回调函数返回值的新数组(译者注:也就是遍历数组,并通过callback对数组元素进行操作,并将所有操作结果放入数组中并返回该数组)。

var a1 = ['a', 'b', 'c'];
var a2 = a1.map(function(item) { return item.toUpperCase(); });
console.log(a2); // logs A,B,C

filter(callback[, thisObject]) 返回一个包含所有在回调函数上返回为true的元素的新数组(译者注:callback在这里担任的是过滤器的角色,当元素符合条件,过滤器就返回true,而filter则会返回所有符合过滤条件的元素)。

var a1 = ['a', 10, 'b', 20, 'c', 30];
var a2 = a1.filter(function(item) { return typeof item == 'number'; });
console.log(a2); // logs 10,20,30

every(callback[, thisObject]) 当数组中每一个元素在callback上被返回true时就返回true(译者注:同上,every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值)。

function isNumber(value){
  return typeof value == 'number';
}
var a1 = [1, 2, 3];
console.log(a1.every(isNumber)); // logs true
var a2 = [1, '2', 3];
console.log(a2.every(isNumber)); // logs false

some(callback[, thisObject]) 只要数组中有一项在callback上被返回true,就返回true(译者注:同上,类似every,不过前者要求都符合筛选条件才返回true,后者只要有符合条件的就返回true)。

function isNumber(value){
  return typeof value == 'number';
}
var a1 = [1, 2, 3];
console.log(a1.some(isNumber)); // logs true
var a2 = [1, '2', 3];
console.log(a2.some(isNumber)); // logs true
var a3 = ['1', '2', '3'];
console.log(a3.some(isNumber)); // logs false

以上方法都带一个被称为迭代方法的的回调函数,因为他们以某种方式迭代整个数组。都有一个可选的第二参数 thisObject,如果提供了这个参数,thisObject 变成回调函数内部的 this 关键字的值。如果没有提供,例如函数在一个显示的对象上下文外被调用时,this 将引用全局对象(window).

实际上在调用回调函数时传入了3个参数。第一个是当前元素项的值,第二个是它在数组中的索引,第三个是数组本身的一个引用。 JavaScript 函数忽略任何没有在参数列表中命名的参数,因此提供一个只有一个参数的回调函数是安全的,例如 alert

reduce(callback[, initialValue]) 使用回调函数 callback(firstValue, secondValue) 把数组列表计算成一个单一值(译者注:他数组元素两两递归处理的方式把数组计算成一个值)

var a = [10, 20, 30];
var total = a.reduce(function(first, second) { return first + second; }, 0);
console.log(total) // Prints 60

reduceRight(callback[, initalvalue])reduce()相似,但这从最后一个元素开始的。

reducereduceRight 是迭代数组方法中最不被人熟知的两个函数.。他们应该使用在那些需要把数组的元素两两递归处理,并最终计算成一个单一结果的算法。

多维数组(multi-dimensional arrays)

数组是可以嵌套的, 这就意味着一个数组可以作为一个元素被包含在另外一个数组里面。利用JavaScript数组的这个特性, 可以创建多维数组。

以下代码创建了一个二维数组。

var a = new Array(4);
for (i = 0; i < 4; i++) {
  a[i] = new Array(4);
  for (j = 0; j < 4; j++) {
    a[i][j] = "[" + i + "," + j + "]";
  }
}

这个例子创建的数组拥有以下行数据:

Row 0: [0,0] [0,1] [0,2] [0,3]
Row 1: [1,0] [1,1] [1,2] [1,3]
Row 2: [2,0] [2,1] [2,2] [2,3]
Row 3: [3,0] [3,1] [3,2] [3,3]

数组和正则表达式

当一个数组作为字符串和正则表达式的匹配结果时,该数组将会返回相关匹配信息的属性和元素。 RegExp.exec(), String.match() 和 String.split() 的返回值是一个数组。 使用数组和正则表达式的的更多信息, 请看 Regular Expressions.

使用类数组对象(array-like objects)

一些 JavaScript 对象, 例如 document.getElementsByTagName() 返回的 NodeList 或者函数内部可用的 arguments 对象,他们表面上看起来,外观和行为像数组,但是不共享他们所有的方法。arguments 对象提供一个 length 属性,但是不实现 forEach() 方法, 例如:

Array的原生(prototype)方法可以用来处理类似数组行为的对象,例如: :

function printArguments() {
  Array.prototype.forEach.call(arguments, function(item) {
    console.log(item);
  });
}

Array的常规方法也可以用于处理字符串,因为它提供了序列访问字符转为数组的简单方法:

Array.prototype.forEach.call("a string", function(chr) {
  console.log(chr);
});

数组推导式(Array comprehensions)

JavaScript 1.7 被介绍并计划在 ECMAScript 7, array comprehensions 被规范化并提供一个有用的快捷方式,用来实现如何在另一个数组的基础上构造一个新的数组。推导式可以经常被用在那些需要调用 map()filter()函数的地方,或作为一种结合这两种方式。

下面的推导式创建一个数字数组并且创建一个新的数组,数组的每个元素都是原来数值的两倍(译者注:这种形式类似于Python的列表推导式)。

var numbers = [1, 2, 3, 4];
var doubled = [for (i of numbers) i * 2];
console.log(doubled); // logs 2,4,6,8

这跟下面的map()方法的操作是等价的。

var doubled = numbers.map(function(i){return i * 2;});

推导式也可以用来筛选满足条件表达式的元素. 下面的推导式用来筛选是2的倍数的元素:

var numbers = [1, 2, 3, 21, 22, 30];
var evens = [i for (i of numbers) if (i % 2 === 0)];
console.log(evens); // logs 2,22,30

filter() 也可以达到相同的目的:

var evens = numbers.filter(function(i){return i % 2 === 0;});

map() 和filter() 类型的操作可以被组合(等效)为单个数组推导式。这里就有一个过滤出偶数,创建一个它的倍数数组的例子:

var numbers = [1, 2, 3, 21, 22, 30];
var doubledEvens = [i * 2 for (i of numbers) if (i % 2 === 0)];
console.log(doubledEvens); // logs 4,44,60

数组推导式隐含了块作用域。新的变量(如例子中的i)类似于是采用 let声明的。这意味着他们不能在推导式以外访问。

数组推导式的输入不一定必须是数组; 迭代器和生成器 也是可以的。

甚至字符串也可以用来作为输入; 实现filter或者map行为 (参考上面类似数组行为的对象)如下:

var str = 'abcdef';
var consonantsOnlyStr = [c for (c of str) if (!(/[aeiouAEIOU]/).test(c))  ].join(''); // 'bcdf'
var interpolatedZeros = [c+'0' for (c of str) ].join(''); // 'a0b0c0d0e0f0'

不过,输入形式是不能保存的,所以我们要使用join()回复到一个字符串。

类型化数组(Typed Arrays )

JavaScript typed arrays 是类数组对象(array-like object),其提供访问原始二进制数据的机制。 就像你知道的那样, Array 对象动态增长和收缩,可以有任何JavaScript值。但对于类型化数组,JavaScript引擎执行优化使得这些数组访问速度快速。 随着Web应用程序变得越来越强大,添加音频和视频处理等功能、可以使用 WebSockets 、使用原始数据, 这都需要访问原始的二进制数据,所以专门的优化将有助于JavaScript代码能够快速和容易地操纵原始二进制数据类型的数组。

缓冲区和视图:类型化的数组结构

为了实现最大的灵活性和效率,JavaScript类型数组被分解为缓冲(Buffer)和视图(views)。缓冲(由ArrayBuffer 实现)是代表数据块的对象,它没有格式可言,并没有提供任何机制来访问其内容。为了访问包含在缓冲区中的内存,您需要使用视图。视图提供了一个上下文,即数据类型、起始偏移量和元素数,这些元素将数据转换为实际类型数组。

Typed arrays in an ArrayBuffer

ArrayBuffer

 DataView来代表特定格式的缓冲区,并从而实现读写缓冲区的内容。

类型数组视图(Typed array views)

类型数组视图具有自描述性的名字,并且提供数据类型信息,例如Int8, Uint32, Float64等等。如一个特定类型数组视图Uint8ClampedArray. 它意味着数据元素只包含0到255的整数值。它通常用于Canvas数据处理,例如.

Type Value Range Size in bytes Description Web IDL type Equivalent C type
Int8Array -128 to 127 1 8-bit two's complement signed integer byte int8_t
Uint8Array 0 to 255 1 8-bit unsigned integer octet uint8_t
Uint8ClampedArray 0 to 255 1 8-bit unsigned integer (clamped) octet uint8_t
Int16Array -32768 to 32767 2 16-bit two's complement signed integer short int16_t
Uint16Array 0 to 65535 2 16-bit unsigned integer unsigned short uint16_t
Int32Array -2147483648 to 2147483647 4 32-bit two's complement signed integer long int32_t
Uint32Array 0 to 4294967295 4 32-bit unsigned integer unsigned long uint32_t
Float32Array 1.2x10-38 to 3.4x1038 4 32-bit IEEE floating point number ( 7 significant digits e.g. 1.1234567) unrestricted float float
Float64Array 5.0x10-324 to 1.8x10308 8 64-bit IEEE floating point number (16 significant digits e.g. 1.123...15) unrestricted double double
BigInt64Array -263 to 263-1 8 64-bit two's complement signed integer bigint int64_t (signed long long)
BigUint64Array 0 to 264-1 8 64-bit unsigned integer bigint uint64_t (unsigned long long)

更多信息参考 JavaScript typed arrays 与参考文档中 TypedArray对象的不同