# 数组

javascript 数组是无类型的，索引是基于零的 32 位数值，且是动态的和稀疏的。

javascript 数组是 javascript 对象的特殊形式。

数组继承自 `Array.prototype` 中的属性，它定义了一套丰富的数组操作方法，大多数这些方法是通用的，这意味着它们不仅对真正的数组有效，而且对“类数组对象”同样有效。

数组直接量的允许有可选的结尾的逗号，故`[,,]`只有两个元素而非三个。

### 数组读写

区分数组的索引和对象的属性名是非常有用的。所有的索引都是属性名，但只有在 `0 ～ 2^32-2` 之间的整数属性名才是索引。所有的数组都是对象，可以为其创建任意名字的属性。但如果使用的属性是数组的索引，数组的特殊行为就是根据需要更新它们的 `length` 属性值。

使用了非负整数的字符串，它就当做数组索引，而非对象属性。当时用了一个浮点数和一个整数相等时情况也一样：

```javascript
a[-1.23] = true // 这将创建一个名"-1.23"的属性
a['1000'] = 0 // 这是数组的第1001个元素
a[1.0] // 和a[1]相等
```

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

既然数组是对象，那么它们可以从原型中继承元素，在 `ES5` 中，数组可以定义元素的 `getter` 和 `setter` 方法。如果一个数组确实继承了元素或使用了元素的 `getter` 和 `setter` 方法，你应该期望它使用非优化的代码路径：访问这种数组元素的时间会与常规对象属性的查找时间相近。

### 稀疏数组

稀疏数组就是包含从 `0` 开始的不连续索引的数组，通常，数组的 `length` 属性值代表数组中元素的个数。如果数组是稀疏的，`length` 属性值大于元素的个数。

```javascript
a = new Array(5) // 数组没有元素，但length为5
a = [] // length = 0
a[1000] = 0 // 添加一个元素，length为1001
```

### 数组长度

数组有两个特殊的行为。第一个：如果为一个数组元素赋值，它的索引 `i` 大于或等于现有数组的长度时，`length` 属性的值将设置 `i+1`。第二个：设置 `length` 属性为一个小于当前长度的非负整数 `n` 时，当前数组中那些索引值大于或等于 `n`的元素将从中删除：

```javascript
a = [1, 2, 3, 4, 5]
a, (length = 0) //删除所有的元素，a为[ ]
```

在 ES5 中，可用 `Object.defineProperty()`让数组的 `length` 属性变成只读的：

```javascript
a = [1, 2, 3]
Object.defineProperty(a, 'length', { writable: false })
a.length = 0 // a不会改变
```

类似的，如果让一个数组元素不能配置，就不能删除它。

### 数组删除

可以像删除对象属性一样使用 `delete` 运算符来删除数组元素。删除数组元素与为其赋值 `undefined` 值是类似的(但有一些微妙的区别)。注意，对一个数组元素使用 `delete` 不会修改数组的 `length` 属性，也不会将元素从高索引出移下来填充已删除属性留下的空白。如果从数组中删除一个元素。如果从数组中删除一个元素，它就变成稀疏数组。

### 数组方法

#### join()

`Array.join()`方法将数组中所有元素都转化为字符串拼接在一起，返回最后生成的字符串。可以指定一个可选的字符串在生成的字符串中来分隔数组的各个元素。如果不指定分隔符。默认使用逗号。

`Array.join()`方法是 `String.split()`方法的逆向操作，后者是将字符串分隔成若干块来创建一个数组。

#### reverse()

`Array.reverse()`方法将数组中的元素颠倒顺序，返回逆序的数组。它采取了替换，换句话，是在原数组中重新排列它们，而非创建新的数组。

#### sort()

`Array.sort()`方法将数组中的元素排序并返回排序后的数组。当不带参数调用 `sort()`时，数组元素以字母表顺序排序，如果数组包含 `undefined` 元素，它们会被排到数组的尾部。

为了按照其他方式而非字母表示进行数组排序，必须给 `sort()`方法传递一个比较函数。该函数决定了它的两个参数在排好序的数组中的先后顺序。假设第一个参数应该在前，比较函数应该返回一个小于 `0` 的数值。假设两个值相等，函数应该返回 `0`.例如：

```javascript
a.sort(function(a, b) {
  return a - b
})
```

#### contact

`Array.concat()`方法创建并返回一个新数组，它的元素包括调用 `concat()`的原始数组的元素和 `concat()`的每个参数。如果这些参数中的任何一个自身是数组，则连接的是数组的元素，而非数组本身。但要注意，`concat()`不会递归扁平化数组的数组。`concat()`也不会修改调用的数组。例如：

```javascript
    var a = [1,2,3];
    a.concat(4,5)          // 返回[1,2,3,4,5]
    a.concat(4,[5,[6,7])  // 返回[1,2,3,4,5,[6,7]]
```

#### slice()

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

#### splice()

`Array.splice()`方法是在数组中插入或删除元素的通用方法，并修改调用的的数组。`splice()`的第一个参数指定了插入和删除的起始位置。第二个参数指定了应该从数组中删除的元素的个数。如果省略第二个参数，从起始点开始到数组结尾的所有元素都将被删除。`splice()`返回一个由删除元素组成的数组，或者如果没有删除元素就返回一个空数组。

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

#### push()和 pop()

`push()`方法在数组的尾部添加一个或多个元素，并返回数组新的长度。`pop()`方法则相反：它删除数组数组的最后一个元素，减小数组长度并返回它删除的值。注意，两个方法都修改并替换原始数组。

#### unshift()和 shift()

`unshift()`在数组的头部添加一个多多个元素。`shift()`删除数组的第一个元素并将其返回，然后把所有随后的元素下移一个位置来填补数组头部的空缺。当多个参数调用 `unshift()`时，参数是一次性插入的而非一次一个地插入。

### ES5 数组方法

ES5 定义了 9 个方法来遍历、映射、过滤、检测、简化和搜索数组。ES5 中的数组方法都不会修改它们调用的原始数组。

#### forEach()

`forEach()`方法从头到尾遍历数组，为每个元素调用指定的函数。传递的函数作为 `forEach()`的第一个参数，然后 `forEach()`使用三个参数调用该函数：数组元素、元素的索引和数组本身。如果只关心数组元素的值，可以只写一个参数---额外的参数都将忽略。

注意，`forEach()`无法再所有元素都传递给调用的函数之前终止遍历。如果要提前终止，必须把 `forEach()`方法放在一个 `try` 块中，并能抛出一个异常，如果 `forEach()`调用函数抛出 `foreach.break` 异常，循环会提前终止：

```javascript
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')
```

#### map()

`map()`方法将调用的数组的每个元素传递给指定的函数，并返回一个数组，包含该函数的返回值。`map()`返回的是新数组：它不会修改调用的数组。如果是稀疏数组，返回的也是相同方式的稀疏数组，它具有相同的长度，相同的缺失元素。

#### filter()

`filter()`方法返回的数组元素是调用的数组一个子集。传递的函数是用来逻辑判定的：该函数返回 `true` 或 `false`。注意，`filter()`会跳过稀疏数组中缺少的元素，它的返回数组总是稠密的。为了压缩稀疏数组的空缺，代码如下：

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

#### every()和 some()

`every()`和 `some()`方法是数组的逻辑判定：它们对数组元素应用指定的函数进行判定，返回 `true` 或 `false`。

`every()`当且仅当针对数组中的所有元素调用判定函数都返回 `true`，它才返回 `true`。

`some()`方法当数组中至少有一个元素调用判定函数返回 `true`，它就返回 `true`。

根据数学上的惯例，在空数组上调用时，`every()`返回 `true`，`some()`返回 `fasle`。

#### reduce()和 reduceRight()

`reduce()`和 `reduceRight()`方法使用指定的函数将数组元素进行组合，生成单个值。例如：

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

`reduce()`需要两个参数。第一个是执行化简操作的函数。化简的任务就是用某种方法把两个值组合或化简为一个值，并返回化简后的值。上例中，第二个参数是一个传递给函数的初始值。

第一次调用函数时，第一个参数是一个初始值，他就是传递 `reduce()`的第二个参数。在接下来的调用中，这个值就是上一次化简函数的返回值。在上面的第一个例子中，第一次调用化简函数时的参数是 `0` 和 `1`.将两者相加并返回 1.再次调用时的参数是 `1` 和 `2`，它返回 `3`.然后它计算 `3+3=6`、`6+4=10`，最后计算 `10+5=15`。最后的值是 `15`，`reduce()`返回这个值。

当不指定初始值调用 `reduce()`时，它将使用数组的第一个元素作为其初始值。

`reduceRight()`的工作原理和 `reduce()`一样，不同的是它按照数组索引从高到低处理数组。

`reduce()`和 `reduceRight()`都能接收一个可选的参数，它指定了化简函数调用时的 `this` 关键字的值。

#### indexOf()和 lastIndexOf()

`indexof()`和 `lastIndexOf()`搜索整个数组中具有给定值的元素，返回找到的第一个元素的索引或者如果没有找到就返回 `-1`。`indexOf()`从头至尾搜索，而 `lastIndexOf()`则反向搜索。

第一个参数是需要搜索的值，第二个参数是可选的：它指定数组中的一个索引，从那里开始搜索，如果省略该参数，`indexOf()`从头开始搜索，而 `lastIndexOf()`从末尾开始搜索，第二个参数也可以是负数，他代表相对数组末尾的偏移量。

### 数组类型

在 ES5 中，可以使用 `Array.isArray()`函数来检测未知对象是否是数组：

```javascript
Array.isArray([]) // => true
```

在 ES3 自定义的 `isArray()`函数的代码可以这样写：

```javascript
var isArray =
  Function.isArray ||
  function(o) {
    return (
      typeof o === 'object' &&
      object.prototype.toString.call(o) === '[onject Array]'
    )
  }
```

### 类数组对象

当在类数组上，数组的静态函数版本非常有用。由于 Firefox 将其直接在 `Array` 构造函数上直接定义为函数，如同 `Array.join(a,"+")` 一般，因此，我们可以这样书写：

```javascript
Array.join =
  Array.join ||
  function(a, sep) {
    return Array.prototype.join.call(a, sep)
  }
```

### 字符串数组

字符串的行为类似于数组使得通用的数组方法可以应用到字符串上，例如：

```javascript
s = 'js'
Array.prototype.join.call(s, ' ') // => "j s"
```

字符串是只读的。如 `push()`、`pop()`、`sort()`等数组方法会修改数组，它们在字符串上是无效的。使用这些方法会导致错误，并且没有提示。
