严格来说,在 JavaScript 中并不存在数组这个数据类型,但 JavaScript 却提供了一种具有数组特性的对象
并且通过一定的封装,以及提供一系列的语法糖,让这个对象用起来像真正的数组一样方便
1、创建数组
(1)数组字面量
数组字面量由零个或多个用逗号分隔的表达式组成,每个表达式的值可以是任意类型,所有表达式用方括号括起来
> // 创建一个空数组 > var empty = [] > // 创建一个带有内容的数组 > var fruit = ['apple', 'banana', 'cherry']
检查一下刚刚创建的数组,发现它是一个 object 类型
> typeof fruit // 'object'
在创建数组的时候,数组中的每个元素都会得到一个默认的属性名,第一个为 '0',第二个为 '1',以此类推
最终,属性名和元素值以键值对的形式存储下来,类似于下面直接用对象直接量创建的一个对象
> var fruit = {'0': 'apple', '1': 'banana', '2': 'cherry'}
当然,两者(使用数组直接量创建数组 和 使用对象直接量创建对象)还是有很多不同之处的
- 数组继承自 Array.prototype,对象继承自 Object.prototype,两者具有的方法有所不同
- 数组拥有一个神奇的 length 属性,而对象没有
(2)new Array()
可以使用 new 运算符加上 Array 构造函数创建一个数组
> // 创建一个空数组 > var empty = new Array()
2、元素的增删改查
(0)数组的索引
对于常规的数组而言,数组的索引是从零开始的连续增加的正整数,但是在 JavaScript 中情况就不一样了
因为数组也是对象的一种,所以理论上我们可以使用任意合法的字符串索引数组,就像索引对象属性一样
那么怎么区分数组索引和对象属性呢?
JavaScript 规定,只有在 [0, 2**32) 之间的整数才能作为数组索引,否则只能将其当作对象属性
(1)元素的读取(查)
使用方括号操作符( [] )可以访问数组中的元素
方括号的左边是数组的引用,方括号的里面是一个返回非负整数的任意表达式,作为数组的索引
> var fruit = ['apple', 'banana', 'cherry'] > fruit[0] // 'apple'
不要忘了哦,数组是对象的一种特殊形式,所以使用方括号访问数组元素就像使用方括号访问对象属性一样
JavaScript 会把数字索引值转化为字符串索引值,然后将其作为属性名使用
(2)元素的设置(改)
设置元素的值同样可以使用方括号运算符
> var fruit = ['apple', 'banana', 'cherry'] > fruit[0] = 'almond' > fruit[0] // 'almond'
(3)元素的删除(删)
- delete 运算符:在原数组中将指定索引的元素值设置为 undefined
> var drink = ['milk', 'tea', 'coffee'] > delete drink[1] > drink[1] // undefined > drink[2] // coffee
- pop 方法:在原数组中删除最后一个元素,并返回删除的元素
> var drink = ['milk', 'tea', 'coffee', 'beer'] > var drink_popped = drink.pop() > drink // ['milk', 'tea', 'coffee'] > drink_popped // 'beer'
- shift 方法:在原数组中删除最前一个元素,并返回删除的元素
> var drink = ['milk', 'tea', 'coffee', 'beer'] > var drink_shifted = drink.shift() > drink // ['tea', 'coffee', 'beer'] > drink_shifted // 'milk'
- splice 方法:在原数组中删除指定元素,第一个参数指定起始索引,第二个参数指定长度,返回删除的元素
> var drink = ['milk', 'tea', 'coffee', 'coke', 'sprite', 'beer'] > var drink_spliced = drink.splice(3, 2) > drink // ['milk', 'tea', 'coffee', 'beer'] > drink_spliced // ['coke', 'sprite']
(4)元素的增加(增)
- 添加元素最简单的方式就是给新索引赋值
> var food = ['rice', 'meat', 'vegetable'] > food[3] = 'noodle' > food // ['rice', 'meat', 'vegetable', 'noodle']
- push 方法:在原数组的最后添加一个元素,返回新数组中元素的个数
> var food = ['rice', 'meat', 'vegetable'] > var food_length = food.push('bread') > food // ['rice', 'meat', 'vegetable', 'bread'] > food_length // 4
- unshift 方法:在原数组的最前添加一个元素,返回新数组中元素的个数
> var food = ['rice', 'meat', 'vegetable'] > var food_length = food.unshift('bread') > food // ['bread', 'rice', 'meat', 'vegetable'] > food_length // 4
- splice 方法:除了删除数组中的元素,splice 方法还可以通过第三个可选的参数添加元素
> var food = ['rice', 'meat', 'vegetable'] > var food_spliced = food.splice(2, 0, 'fish') > food // ['rice', 'meat', 'fish', 'vegetable'] > food_spliced // []
3、元素的遍历
由于数组也是对象的一种,所以我们可以使用遍历对象的方式来遍历数组,常用的有 for-in 和 for-of 循环
> var fruit = ['apple', 'banana', 'cherry'] > fruit[5] = 'filbert' > // for-in 循环 > for (let index in fruit) { console.log(fruit[index]) } // apple // banana // cherry // filbert > // for-of 循环 > for (let item of fruit) { console.log(item) } // apple // banana // cherry // undefined // undefined // filbert
4、数组的 length 属性
JavaScript 数组 length 属性的值不一定等于数组中元素的个数,它实际上等于数组中最大的合法索引加 1
在这里有一条规则,那就是 在数组中肯定找不到一个元素的索引值大于或等于它的长度
为了维持这个规则,length 属性有许多有趣的行为
- 若为一个数组元素赋值,且它的索引 i 大于或等于现有数组长度,则 length 属性的值将设置为 i + 1
> var alphabet = ['a', 'b', 'c'] > alphabet.length // 3 > alphabet[4] = 'e' > alphabet.length // 5
- 若为一个数组元素赋值,且它的索引不是一个合法的数组索引,则 length 属性的值将不会发生改变
> var alphabet = ['a', 'b', 'c'] > alphabet.length // 3 > alphabet[-1] = 'z' > alphabet.length // 3
- 若设置 length 属性为一个小于当前长度的非负整数 n 时,数组中那些索引值大于或等于 n 的元素将被删除
> var alphabet = ['a', 'b', 'c'] > alphabet.length // 3 > alphabet.length = 1 > alphabet // ['a']
5、数组的方法
JavaScript 提供了一套作用于数组的方法,这些方法是储存在 Array.prototype 的函数
(1)常规的数组方法
- join:将数组中的所有元素都转化为字符串并拼接在一起,返回拼接后的字符串
- 可以通过一个可选的参数(字符串类型)指定分隔符,默认使用逗号
> var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] > var numbers_join = numbers.join() > numbers_join // '0,1,2,3,4,5,6,7,8,9'
- reverse:将数组中的元素倒序排列,返回排序后的数组
> var numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] > var numbers_reverse = numbers.reverse() > numbers_reverse // [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]
- sort:将数组中的元素按照一定的规则排序,返回排序后的数组
- 可以通过一个可选的参数(函数类型)指定元素之间的比较规则,默认为字典序
> var numbers = [111, 3, 22] > ns1 = numbers.sort() // 转化为字符串,按照字典序排列 > ns2 = numbers.sort(function(a, b) { return a < b }) // 按照数值从大到小排列 > ns3 = numbers.sort(function(a, b) { return a > b }) // 按照数值从小到大排列 > ns1 // [ 111, 22, 3 ] > ns2 // [ 111, 22, 3 ] > ns3 // [ 3, 22, 111 ]
- concat:拼接两个数组的元素,返回拼接之后的数组
> var a = [1, 2, 3] > var b = [4, 5, [7], [8, 9]] > var c = a.concat(b) > var d = b.concat(a) > c // [1, 2, 3, 4, 5, [7], [8, 9]] > d // [4, 5, [7], [8, 9], 1, 2, 3]
- slice:返回指定范围的数组片段
- 两个参数分别指定起止位置,省略第二个参数默认为数组最后,同时省略第一个参数默认为数组最前
> var a = [1, 2, 3, 4, 5] > var b = a.slice(1, 4) > var c = a.slice(2) // 省略第二个参数 > var d = a.slice() // 同时省略第二个和第一个参数 > b // [ 2, 3, 4 ] > c // [ 3, 4, 5 ] > d // [ 1, 2, 3, 4, 5 ]
- includes:检查数组中是否包含特定的元素,有的话返回 true,没有的话返回 false
> var numbers = [1, 2, 3, 4, 5] > numbers.includes(0) // false > numbers.includes(1) // true
- indexOf:检查数组中是否包含特定的元素,有的话返回对应的索引,没有的话返回 -1
> var numbers = [1, 2, 3, 4, 5] > numbers.indexOf(0) // -1 > numbers.indexOf(1) // 0
(2)ECMAScript5 中的数组方法
ECMAScript5 中新定义的数组方法具有一些类似的特性,这里先做介绍
这些方法大都接收一个函数作为参数,并且对数组中的每个元素都调用一次该函数
该函数接收三个参数,分别是数组元素、元素索引和数组本身
- forEach
> var numbers = [1, 3, 5, 7, 9] > // 计算数组中所有元素的总和 > var sum = 0 > numbers.forEach(function(value){ sum += value }) > sum // 25 > // 将数组中的每个元素加 1 > numbers.forEach(function(value, index, array){ array[index] = value + 1 }) > numbers // [2, 4, 6, 8, 10]
- map
> var numbers = [1, 2, 3, 4, 5] > // 将数组中的每个元素乘 2,并返回一个新的数组 > var numbers_doubled = numbers.map(function(number){ return (number * 2) }) > numbers_doubled // [2, 4, 6, 8, 10]
- filter
> var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] > // 留下偶数元素 > var even_numbers = numbers.filter(function(number){ return (number % 2 === 0) }) > even_numbers // [2, 4, 6, 8, 10]
- every / some
> function isPrime(val) { if (val < 2) { return false } else if (val === 2) { return true } else { // val > 2 if (val % 2 === 0) { return false } else { // val % 2 !== 0 for (let i = 3; i * i <= val; i += 2) { if (val % i === 0) { return false } } return true } } } > // 判断数组中的每个元素是否都是素数 > [2, 3, 5, 7].every(function(value){ return isPrime(value) }) // true > [2, 3, 5, 7, 9].every(function(value){ return isPrime(value) }) // false > // 判断数组中是否存在一个元素是素数 > [4, 6, 8, 9].some(function(value){ return isPrime(value) }) // false > [2, 4, 6, 8, 9].some(function(value){ return isPrime(value) }) // true
这里给大家出一道题目,请写出下面程序的执行结果
['1', '3', '10'].map(parseInt)
输出的结果是 [1, NaN, 2]
,大家答对了吗
解决这道题目有两个关键的知识点,一个是 map
函数的原理,一个是 parseInt
函数的原理
map
函数的原理,就是将在参数中指定的函数作用于数组的每一个元素,然后把这些结果组成一个数组返回
它接收的第一个参数是数组的当前元素,第二个参数是该元素的索引,第三个参数是数组本身
实际上,这个程序会返回这样的结果 [parseInt('1', 0), parseInt('3', 1), parseInt('10', 2)]
好,下面我们再来看看 parseInt
函数的作用,它的作用其实就是将一个字符串转换成十进制数字
它接收的第一个参数是被解析的字符串,第二个参数表示要使用的基数,这个值可以是 0 或在 2 ~ 36 之间
parseInt('1', 0)
:基数为 0,默认用十进制解析字符串,将十进制的数字 1
转换为十进制数字还是 1
parseInt('3', 1)
:基数既不为 0,也不在范围 2 ~ 36 之间,所以返回 NaN
parseInt('10', 2)
:基数为 2,因此用二进制解析字符串,将二进制的数字 10
转换为十进制数字是 2
所以最终的结果就是 [1, NaN, 2]