严格来说,在 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 中新定义的数组方法具有一些类似的特性,这里先做介绍
这些方法大都接收一个函数作为参数,并且对数组中的每个元素都调用一次该函数
该函数接收三个参数,分别是数组元素、元素索引和数组本身
(1)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]
(2)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]
(3)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]
(4)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]
【 阅读更多 JavaScript 系列文章,请看 JavaScript学习笔记 】