1 定义
创建数组
// 标准写法
let arr = new Array(1, 2, 3,)
// 简写
let arr = [1, 2, 3]
//创建长度为3的数组
let arr = new Array(3)
任何类型的数据,都可以放入数组。
2 数组的本质
本质上,数组属于一种特殊的对象。JavaScript其实没有真正的数组(只是用对象模拟数组)。
典型的数组:
- 元素的数据类型相同
- 使用连续的内存存储
- 通过数字下表获取元素
JS的数组:
- 元素的数据类型可以不同
- 内存不一定是连续的(对象是随机存储的)
- 不能通过数字下表, 而是通过字符串下标
typeof([]) // "object"
数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2…)。
Object.keys
方法返回数组的所有键名
JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串。
注意,这点在赋值时也成立。一个值总是先转成字符串,再作为键名进行赋值。
var a = [];
a[1.00] = 6;
a[1] // 6
对象有两种读取成员的方法:点结构(object.key
)和方括号结构(object[key]
)。但是,对于数值的键名,不能使用点结构。
var arr = [1, 2, 3];
arr.0 // SyntaxError
3 length属性
JavaScript 使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有 4294967295 个(
2
32
2^{32}
232 - 1)个,也就是说length
属性的最大值就是 4294967295。
只要是数组,就一定有length
属性。该属性是一个动态的值,等于键名中的最大整数加上1。
var arr = ['a', 'b'];
arr.length // 2
arr[2] = 'c';
arr.length // 3
arr[9] = 'd';
arr.length // 10
arr[1000] = 'e';
arr.length // 1001
length
属性是可写的。如果人为设置一个小于当前成员个数的值,该数组的成员数量会自动减少到length
设置的值。
var arr = [ 'a', 'b', 'c' ];
arr.length // 3
arr.length = 2;
arr // ["a", "b"]
清空数组的一个有效方法,就是将
length
属性设为0。
var arr = [ 'a', 'b', 'c' ];
arr.length = 0;
arr // []
如果人为设置length大于当前元素个数,则数组的成员数量会增加到这个值,新增的位置都是空位empty
。
var a = ['a'];
a.length = 3;
a // ["a", empty × 2]
a[1] // undefined
如果人为设置length
为不合法的值,JavaScript 会报错。
// 设置负值
[].length = -1
// RangeError: Invalid array length
// 数组元素个数大于等于2的32次方
[].length = Math.pow(2, 32)
// RangeError: Invalid array length
// 设置字符串
[].length = 'abc'
// RangeError: Invalid array length
值得注意的是,由于数组本质上是一种对象,所以可以为数组添加属性,但是这不影响
length
属性的值。
var a = [];
a['p'] = 'abc';
a.length // 0
a[2.1] = 'abc';
a.length // 0
上面代码将数组的键分别设为字符串和小数,结果都不影响length
属性。因为,length
属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以length
属性保持为0。
如果数组的键名是添加超出范围的数值,该键名会自动转为字符串。
var arr = [];
arr[-1] = 'a';
arr[Math.pow(2, 32)] = 'b';
arr.length // 0
arr[-1] // "a"
arr[4294967296] // "b"
4 in 运算符
检查某个键名是否存在的运算符in
,适用于对象,也适用于数组。
var arr = [ 'a', 'b', 'c' ];
2 in arr // true
'2' in arr // true
4 in arr // false
上面代码表明,数组存在键名为2
的键。由于键名都是字符串,所以数值2
会自动转成字符串。
5 for…in循环和数组的遍历
for...in
循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象。
var a = [1, 2, 3];
for (var i in a) {
console.log(a[i]);
}
// 1
// 2
// 3
但是,for...in
不仅会遍历数组所有的数字键,还会遍历非数字键。
所以,不推荐使用
for...in
遍历数组。
数组的遍历可以考虑使用for
循环或while
循环。
var a = [1, 2, 3];
// for循环
for(var i = 0; i < a.length; i++) {
console.log(a[i]);
}
// while循环
var i = 0;
while (i < a.length) {
console.log(a[i]);
i++;
}
var l = a.length;
while (l--) {
console.log(a[l]);
}
@@@
数组的forEach
方法,也可以用来遍历数组,详见《标准库》的 Array 对象一章。
@@需要修改@
var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
console.log(color);
});
// red
// green
// blue
自己实现foreach
function forEach(array, fn){
for(let i = 0; i<array.length; i++){
fn(array[i], i, array)
}
}
forEach([1, 2, 3], function(value, key){
console.log(key, value)
})
6 数组的空位
当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。
var a = [1, , 1];
a.length // 3
需要注意的是,如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。
var a = [1, 2, 3,];
a.length // 3
a // [1, 2, 3]
数组的空位是可以读取的,返回undefined。
var a = [, , ,];
a // [empty × 3]
a[1] // undefined
var b = []
b.length = 3
b // [empty × 3]
b[1] // undefined
使用delete
命令删除一个数组成员,会形成空位,并且不会影响length
属性。也就是说,length
属性不过滤空位。所以,使用length
属性进行数组遍历,一定要非常小心。
var a = [1, 2, 3];
delete a[2];
a[2] // undefined
a.length // 3
数组的某个位置是空位,与某个位置是undefined
,是不一样的。如果是空位,使用数组的forEach
方法、for...in
结构、以及Object.keys
方法进行遍历,空位都会被跳过。
var a = [, , ,];
a.forEach(function (x, i) {
console.log(i + '. ' + x);
})
// 不产生任何输出
for (var i in a) {
console.log(i);
}
// 不产生任何输出
Object.keys(a)
// []
如果某个位置是undefined,遍历的时候就不会被跳过。
var a = [undefined, undefined, undefined];
a.forEach(function (x, i) {
console.log(i + '. ' + x);
});
// 0. undefined
// 1. undefined
// 2. undefined
for (var i in a) {
console.log(i);
}
// 0
// 1
// 2
Object.keys(a)
// ['0', '1', '2']
这就是说,空位就是数组没有这个元素,所以不会被遍历到,而undefined
则表示数组有这个元素,值是undefined
,所以遍历不会跳过。
7 类数组
如果一个对象的所有键名都是正整数或零,并且有length
属性,那么这个对象就很像数组,语法上称为“类似数组的对象”(array-like object),简称类数组。
“类似数组的对象”的根本特征,就是具有length
属性。只要有length
属性,就可以认为这个对象类似于数组。
但是有一个问题,这种length
属性不是动态值,不会随着成员的变化而变化,而且没有数组的共有属性,例如push
和pop
等。
var obj = {
length: 0
};
obj[3] = 'd';
obj.length // 0
上面代码为对象obj
添加了一个数字键,但是length
属性没变。这就说明了obj
不是数组。
典型的类数组有:
- 函数的
arguments
对象; - 大多数
DOM
元素集; - 字符串。
// arguments对象
function args() { return arguments }
var arrayLike = args('a', 'b');
arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false
// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false
// 字符串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false
数组的slice
方法可以将类数组变成真正的数组。
var arr = Array.prototype.slice.call(arrayLike)
//等价于
var arr = [].slice.call(arrayLike)
from
方法也可以将类数组变成真正的数组。
Array.from('123') // ["1", "2", "3"]
Array.from({0:'a', 1:'b', length:2}) // ["a", "b"]
对于字符串可以使用split
方法
'1, 2, 3'.split(',') // ["1", "2", "3"]
'123'.split('') // ["1", "2", "3"]
@@@
另外,你可以使用 bind 。
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
function list() {
return slice(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
@@不懂@
除了转为真正的数组,类数组还有一个办法可以使用数组的方法,就是通过call()
把数组的方法放到对象上面。
function print(value, index) {
console.log(index + ' : ' + value);
}
Array.prototype.forEach.call(arrayLike, print);
疑问: 类数组的原型链上会有数组的方法吗?
答:没有
下面的例子就是通过这种方法,在arguments对象上面调用forEach方法。字符串也是类似数组的对象,所以也可以.
// forEach 方法
function logArgs() {
Array.prototype.forEach.call(arguments, function (elem, i) {
console.log(i + '. ' + elem);
});
}
// 等同于 for 循环
function logArgs() {
for (var i = 0; i < arguments.length; i++) {
console.log(i + '. ' + arguments[i]);
}
}
注意,这种方法比直接使用数组原生的
forEach
要慢,所以最好还是先将类数组转为真正的数组,然后再直接调用数组的forEach
方法。
var arr = 'abc';
function logArgs(arr) {
Array.prototype.forEach.call(arr, function (elem, i) {
console.log(i + '. ' + elem);
});
}
logArgs(arr)
// 直接先转换为数组后foreach
var arr = Array.prototype.slice.call('abc');
arr.forEach(function (chr) {
console.log(chr);
});
// a
// b
// c
其他操作
from方法
console.log(Array.from([1, 2, 3], x => x + x)); //[2, 4, 6]
Array.from({length: 5}, (v, i) => i); // [0, 1, 2, 3, 4]
@@@
数组去重并合并
function combine(){
let arr = [].concat.apply([], arguments); //没有去重复的新数组
return Array.from(new Set(arr));
}
var m = [1, 2, 2], n = [2,3,3];
console.log(combine(m,n)); // [1, 2, 3]
@@需要仔细看看@
- 合并两个数组
concat
返回一个新数组不会影响原来的两个数组。
let arr11 = [1]
let arr22 = [2]
arr11.concat(arr22) // [1, 2]
arr11 // [1]
arr22 // [2]
- 截取数组
slice
返回一个新数组不会影响原数组
let arr = [1, 2, 3]
arr.slice(1) // [2, 3]
arr // [1]
//复制一个数组
arr.slice(0) // [1, 2, 3]
JS只提供浅拷贝
删除数组
delete
和直接修改length
不是删除数组的方法。
删数组元素:
- 删除头部的元素
arr.shift()
, arr被修改, 并返回被删除的元素 - 删除尾部的元素
arr.pop()
, arr被修改, 并返回被删除的元素 - 删除中间的元素
//删除index的一个元素, 1表示删除长度
arr.splice(index, 1)
//并在删除位置添加'x'
arr.splice(index, 1, 'x')
//并在删除位置添加'x', 'y'
arr.splice(index, 1, 'x', 'y')
查看单个属性:
- 索引越界
arr[arr.length] === undefined
arr[-1] === undefined
-
查看某个元素是否在数组里
arr.indexOf(item)
, 存在返回索引, 否则返回-1 -
使用条件查找元素
arr.find(item=> item%2 === 0)
, 找到第一个偶数, 否则返回undefined -
使用条件查找元素的索引
arr.findIndex(item=> item%2 === 0)
, 找到第一个偶数索引, 否则返回-1 -
在尾部加元素
push()
方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
//修改arr, 返回新长度
arr.push(newItem)
//修改arr, 返回新长度
arr.push(item1,item2)
- 在头部加元素
unshift()
方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
//修改arr, 返回新长度
arr.unshift(newItem)
//修改arr, 返回新长度
arr.unshift(item1,item2)
- 在中间添加元素
splice()
方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
//在index处插入'x'
arr.splice(index, 0, 'x')
arr.splice(index, 0, 'x', 'y')
- 反转顺序
arr.reverse()
, 修改原数组 - 自定义顺序
arr.sort()
默认是从小到大排序,不对。
var array2 = [1, 30, 4, 21, 100000]
array2.sort()
// [1, 100000, 21, 30, 4]
如果要求从大到小排序呢?
可以使用sort和reverse来实现
但是我就是要用sort来实现呢
原始代码:
//返回1:左边大右边小,返回-1:左边小右边大
arr.sort(function(a, b){
if(a < b){
return -1
}else if(a === b){
return 0
}else{
return 1
}
})
简写代码:
arr.sort((a, b)=> a-b)
,因为只需要确定返回值是正数,负数和0,即可。
指定对象属性排序
var arr = [
{
'name': 'a1',
'score': 91
},
{
'name': 'a2',
'score': 71
},
{
'name': 'a3',
'score': 81
}
]
arr.sort((a, b) => a.sorce - b.sorce)
数组变换
- map
对数组的每一项进行操作(n变n)
let arr = [1, 2, 3]
arr.map(item => item * item)
// [1, 4, 9]
- filter
对数组的元素进行过滤(n变少)
let arr = [1, 2, 3]
arr.filter(item => item % 2 === 0)
// [2]
let arr = [1, 2, 3]
arr.map(item => item % 2 === 0)
// [false, true, false]
- reduce
对数组整合成一个(n边1)
let arr = [1, 2, 3]
arr.reduce((sum, item) => sum+item, 0)
// 6
reduce实现平方(不建议)
let arr = [1, 2, 3]
arr.reduce((result, item) => result.concat(item*item), [])
// [1, 4, 9]
通过reduce来实现过滤出偶数
法一:
let arr = [1, 2, 3]
arr.reduce((result, item) =>{
if(item % 2 === 0){
return result.concat(item)
}else{
return result
}
}, [])
// [2]
法二:
let arr = [1, 2, 3]
arr.reduce((result, item) => item % 2 === 0 ? result.concat(item) : result, [])
// [2]
法三:通过拼接一个空的[]
let arr = [1, 2, 3]
arr.reduce((result, item) => result.concat(item % 2 ===0 ? item : []), [])
// 2
把数字变为星期
let arr = [0, 1, 3, 5, 3]
arr.map(i => ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'][i])
// ["星期日", "星期一", "星期三", "星期五", "星期三"]