数组
数组是值的有序集合
,数组中每个值称为元素
,元素在数组中的位置称为索引
。JavaScript中的数组是一种特殊的对象:
类属性
class attribute
为Array
新元素添加到数组后,自动更新自身的
length
属性从
Array.prototype
对象中继承方法设置
length
属性值小于元素个数时,会截断数组
数组的特点
JavaScript中,数组的元素可以是任意JavaScript值,并且同一个数组中不同元素值的类型可以不同
数组的索引是基于0的32位数值,最大的索引为
(2^32 - 1) - 1
数组是动态的,可以根据需要增长或缩短
数组的操作方法大部分定义在
Array.prototype
原型对象上,使所有的数组对象都可以使用数组的实现经过了优化,用索引访问元素的效率高于直接访问常规对象的属性
数组可能是稀疏或者稠密的。
1 数组的创建与访问
1.1 创建数组
有两种方法可以创建数组:数组字面量[1, 2, 3]
和new Array(1, 2, 3)
。推荐使用数组字面量创建数组,简洁易懂。
对象字面量创建的数组,其元素可以是表达式、对象或其他数组。如果省略数组中字面量的值,数组会变为稀疏数组。
var empty = []; // <==> var empty = new Array();数组字面量的写法更简洁
var primes = [2, 3, 5, 7, 11];
var misc = [1.1, true, 'a']; //数组中元素可以是任意类型值
var base = 1024;
var table = [base, base + 1, base + 2, base + 3]; // ==> [ 1024, 1025, 1026, 1027 ]
var b = [[1, {a: "1", b: 2}], [true, {x: 2, y:3}]];
通过Array()
构造函数也可以创建数组,但是使用比较繁琐,根据传入参数个数的不同,分为三种情况:
不传入参数:
var a = new Array()
;a
数组与[]
相同传入一个非负整数:
var a = new Array(10)
;创建一个存放10个数组元素的空间,但是此时没有定义索引属性和存储值,此时的数组是稀疏的传入2个或多个数组元素或者一个非数值元素:
var c = new Array('1', 2, true, "text");
,创建的数组['1', 2, true, "text"]
稀疏数组与稠密数组
根据数组是否有不连续的数组,将其分为稀疏数组和稠密数组
稀疏数组的length
属性大于元素的个数,在实现上比稠密数组更慢,消耗内存空间更大,查找元素的效率与常规对象相似。
要避免使用稀疏数组,其查找效率很低。稀疏数组空缺的部分没有值和索引,因为不能用for-in
循环遍历出空缺的部分,for-in
循环可以遍历对象的可枚举属性(自身和继承)
var undefs = [, , ]; //数组字面量允许逗号结尾,是稀疏数组,没有元素,使用for-in循环不会输出任何内容
var count = [1, , 3]; //稀疏数组,使用for-in循环只输出[1, 3]
var arr = new Array(10); //稀疏数组,没有赋值和索引
a = new Array(5); //数组没有元素,a.length为5,稀疏
a[1000] = 0; //a.length为1001,索引大于元素个数,稀疏
a = [, , , ]; //是稀疏数组,0 in a; => false
a = [1, , 3]; //稀疏数组,长度为3
a = [,]; //数组没有元素,长度是1
总结:数组创建尽量使用字面量方法,如果数组的索引不连续,则数组是稀疏数组,查询效率低,内存消耗高
1.2 数组的访问
数组是特殊的对象,可以利用对象属性的访问方式.
和[]
,因为数组元素的索引是非负数的字符串,所以需要使用[]
访问,而数组的属性(如length
则可以用.
访问)。
利用[]
可以读写数组的元素:
var a = ["Hello"];
var value = a[0]; //读取第一个元素
a[1] = 3.14; //写入第二个元素
数组是特殊的对象:
所有的数组都是对象,但对象不一定是数组;
数组对象的属性名在
0~2^32-2
之间的是索引,可以为数组创建任意属性,只有当属性是索引时,才更新其length
属性使用负整数、与整数等的浮点数作为属性时,会将其转化为索引
a[-1.23] = true; //创建一个名为"-1.23"的属性
a[-3] = false; //创建一个名为"-3"的属性
a["1000"] = 0; //数组的第1001个元素为值为0
a[2.000]; //与a[2]相同
注:JS中没有越界的概念,因为是将数组作为对象来处理,对于不存在的下标,将其当做普通属性添加到数组对象上。
总结:使用[]
访问数组元素,使用.
访问数组属性,如.length
。
1.3 数组的长度
对于稠密数组,其length
属性代表元素的个数,索引从0
开始,最大值是length - 1
。如果是稀疏数组,其length
属性大于元素的个数。数组的长度保证大于每个元素的索引值
[].length; //==>0,数组没有元素
[1, 2, 3].length; //==>3,最大索引为2,length为3
为数组元素赋值,如果其索引
i
大于等于现有数组长度,length
的属性值将设置为i+1
如果设置的
length
值n
小于数组长度时,会将索引大于n
的元素全部删除。-
在ES5中,可以通过
Object.defineProperty()
设置数组的length
属性的描述符descripter
为{writable: false}
,使其变为只读属性,设置之后不能添加或者删除数组元素a = [1, 2, 3, 4, 5]; a.length = 3; //数组变为[1, 2, 3] a.length = 0; //删除所有数组元素 a = [1, 2, 3]; //注意设置为只读后,不能在为数组添加元素,除非修改writable为true Object.defineProperty(a, 'length', {writable: false}); a.length = 2; //a.length不会改变 a.length; //==>3
2 数组的操作
在Array.prototype
对象中定义许多关于数组操作的方法。
2.1 数组元素的添加与删除
push()
和unshift()
方法
push()
方法:在数组末尾插入一个或多个元素,与pop()
方法相对应,返回插入元素后数组的新长度unshift()
方法:在数组的首部插入一个或多个元素,与shift()
方法相对应,返回插入元素后的数组新长度。注意一次插入多个元素和多次插入一个元素的效果是不同的
pop()
和shift()
方法
pop()
方法:在数组末尾删除一个元素,返回删除的元素;-
shift()
方法:在数组首部删除一个元素,返回删除的元素;var a = []; a.push('one'); // ==> ['one'],在末尾添加一个元素 a.push('two', 'zero'); // ==> ['one', 'two', 'zero'],在末尾添加两个个元素 a.pop(); // ==>"zero",a变为['one', 'two'] a.unshift('four'); // ==> a变为['four', 'one', 'two'] a.shift(); // ==> 删除"four"
注:会修改调用该方法的数组
splice()
方法
splice(start, num, insert1, insert2,...)
方法是插入、删除和替换数组元素的通用方法。参数
start
:插入或删除的起始位置参数
num
:需要删除元素的个数,如果不指定会将起始位置后的所有元素删除-
insert
、insert2
...:需要插入的元素var a = [1, 2, 3, 4, 5, 6, 7]; a.splice(5); //==>[6, 7],a变为[1, 2, 3, 4, 5] a.splice(2, 2); //==>[3, 4],a变为[1, 2, 5] a.splice(-1, 1); //==>[5],a变为[1, 2] a.splice(1, 0, 'a', false, 0); // ==>[],没有删除元素,a变为[1, "a", false, 0, 2] a.splice(2, 2, ['x', 'y']); // ==>[false, 0],a变为[1, "a", ['x', 'y'], 2]
注:会修改调用该方法的数组
2.2 数组的遍历
使用for
循环是遍历数组的常见方法,可以配合部分if
语句过滤部分元素。
for(var i=0, len=arr.length; i<len; i++) {
if(!a[i]) {continue;} //过滤掉null、undefined和不存在的元素
if(a[i] === undefined) {continue;} //过滤掉undefined和不存在的元素
if(!(i in arr)) {continue;} //in操作符可以检测对象是否含有某个属性,数组没有索引时,返回false,可以过滤掉不存在的元素
}
对于稀疏数组,可以使用for-in
循环历来过滤掉不存在的索引。for-in
循环会访问继承的可枚举属性,利用hasOwnProperty()
方法过滤掉非自身属性。
for(var i in p) {
if(!p.hasOwnProperty(i)) {continue;} //跳过继承的属性
}
forEach()
方法
由于for-in
循环本身是未遍历对象而设计,如果数组有其余可枚举属性,需要专门过滤。ES5定义forEach()
方法来遍历数组。
forEach(fn)
方法接收一个函数作为参数,将每个元素分别调用该函数。forEach()
可以给fn
传递三个参数,fn(value, index, array)
。
value
:数组元素index
:数组元素对应的索引-
array
:数组本身var a = [1, 2, 3, 4, 5]; //计算数组元素的和 var sum = 0; a.forEach(function(value) { //只传递数组元素一个参数 sum += value; }); console.log(sum); // ==> 15 //将数组每个元素加2 a.forEach(function (value, index, array) { array[index] = value + 2; }); console.log(a); // ==>[3, 4, 5, 6, 7]
forEach()
方法不能在所有元素都传递给fn
函数调用前终止遍历(不能使用break
跳出循环),如果要提前终止循环,需要将forEach()
方法放在try
块中,如果forEach()
调用的函数fn
能抛出foreach.break
异常,遍历提前终止。
function foreach(value, index, array) {
try {
a.forEach(index, array);
} catch(e) {
if(e === foreach.break) {
return;
} else {
throw e;
}
}
}
2.3 多维数组
JS并不支持真正的多维数组,可以利用数组的数组来进行模拟,使用[][]
访问即可。
var table = new Array(10); //表格10行
var len=table.length;
for(let i=0; i<len; i++) {
table[i] = new Array(10); //10列
}
//初始化表格
for(let row=0; row<len; row++) {
for(let col=0; col<table[row].length; col++) {
table[row][col] = row * col;
}
}
var product = table[5][7]; // ==> 35
3 数组的方法
在Array.prototype
中定义了与数组操作相关的方法,主要分为ES3和ES5两个部分
3.1 ES3中的数组操作方法
Array.prototype.join()
:不修改原字符串
Array.prototype.join(seperator)
方法将数组中所有元素先转化为字符串,再利用参数seperator
传入的分隔符将元素转换成的字符串拼接起来,最后返回该字符串。默认使用,
分隔。是String.prototype.split()
方法的逆向操作
var a = [1, 2, 3];
a.join(); // ==> "1,2,3"
a.join(" "); // ==> "1 2 3"
a.join(""); // ==> "123"
"1,2,3".split(",").join(); //==> "1,2,3"
Array.prototype.reverse()
:会修改原数组
将数组中的元素顺序颠倒,返回逆序后的数组
var a = [1, 2, 3];
a.reverse.join(); //"3,2,1",并且a变为[3, 2, 1],所以注意使用副本来操作
Array.prototype.sort()
:会修改原数组
将数组中元素按照指定的顺序后返回,默认按照字母表的顺序怕排序。undefined
值被排到最后,要使用其他方式排序,需要传入一个比较函数fn(a, b)
,依据两个参数在排序好的数组中的先后顺序:
如果第一个参数在第二个参数之前,函数返回值小于
0
如果第一个参数在第二个参数值后,函数返回值大于
0
-
如果两个参数相等,函数返回值等于
0
var arr = ['banana', 'cherry', 'apple']; arr.sort(); arr.join(","); //==> "apple,banana,cherry",默认按照字母表顺序 var a = [1, 2, 3, 4]; a.sort(function(a, b) { return a - b; //如果目标是升序,a在b的前面,函数返回值小于0即可 }); a.sort(function(a, b) { return b - a ; //如果目标是降序,a在b的后面,函数返回值大于0即可 }); //不区分大小写升序排列 var arr = ['ant', 'Dog', 'Bug', 'cat']; arr.sort(function(first, second) { first = first.toLowerCase(); //将字符串全部转化为小写 second = second.toLowerCase(); if(first < second) { //如果正确顺序是first在second前,则返回-1 return -1; } else if (first > second) { return 1; } else { return 0; } });
Array.prototype.concat()
:不修改原数组
用于拼接原始数组与传入的参数,组成一个新的数组并返回。如果传入的参数是数组,会连接最外层的数组元素
var a = [1, 2, 3];
a.concat(4, 5); // ==> [1, 2, 3, 4, 5]
a.concat([4, 5]); // ==> [1, 2, 3, 4, 5]
a.concat(4, [5, [6, 7]]); //==> [1, 2, 3, 4, 5, [6, 7]]
// 将数组[5, [6, 7]]最外层拆开,但是保留内部
Array.prototype.slice()
:不改变原数组
返回指定数组的一个片段或数组:
通过两个参数指定切割的起始位置与结束位置(不包含第二个位置的元素)
如果只有一个参数,返回从开始位置到数组结束的所有元素
-
参数接收负数,
-1
表示最后一个元素,-2
表示倒数第二个元素a = [1, 2, 3, 4, 5]; a.slice(0, 3); // ==> [1, 2, 3] a.slice(3); // ==> [4, 5] a.slice(1, -1); // ==> [2, 3, 4]
toString()
和toLocalString()
先将数组的每个元素转化为字符串,再利用逗号将其拼接为一个字符串,其中toLocaleString()
方法区别在于转换Date
和Number
类型的数字、日期和时间时,根据时区、数字来转化。
转换结果与不传入参数的
join()
方法类似
[1, 2, 3].toString(); // ==> "1,2,3"
['a', [1, 'b']].toString(); // ==> "a,1,b"
3.2 ES5中操作数组的方法
ES5中定义了9个方法来遍历forEache()
、映射map()
、过滤filter()
、检测every()
some()
、简化reduce()
reduceRight()
和搜索indexOf()
lastIndexOf()
数组。九种方法不会修改调用它的数组,但是传入的函数会。
九种方法基本都属于过程抽象,第一个参数基本都是一个函数,对数组的每个元素调用该函数
Array.prototype.map()
map(fn)
只有一个参数,map()
方法传递三个参数给fn(value, index, array)
,根据需要选择。其调用方式与forEach()
相似,但是传递给map(fn)
的函数fn
有返回值。
map()
返回的是新数组,不修改原数组-
如果是稀疏数组,返回稀疏素组;具有相同的长度与缺失元素
var a = [1, 2, 3]; a.map(function(value) { return value * value; //map()传入的函数必须要有返回值,将返回值作为返回数组的元素的值 }); //==> [1, 4, 9],原数组a还是[1, 2, 3]不变
Array.prototype.filter()
filter()
方法返回的数组是调用数组的一个子集。通过传递的函数进行逻辑判断,如果返回值是true
,当前元素被添加到返回的子集中;如果返回false
,则过滤掉该元素;
filter()
会跳过稀疏数组中缺失的元素,返回的总是稠密数组。arr.filter(function() {return true;});
会过滤掉所有缺失的元素;-
arr.filter(funtion(x) {return (x !== undefined && x !=null)})
:过滤掉undefined
和null
var a = [5, 4, 3, 2, 1]; a.filter(function(value) { return value < 3; }); // ==> [2, 1] a.filter(function(value, index) { return index % 2 === 0; //过滤掉索引为奇数的元素 }); // ==> [5, 3, 1],不会改变原数组
Array.prototype.every()
和Array.prototype.some()
every()
和some()
用于对数组进行逻辑判断,返回true
或`false;每个元素应用传入的函数进行判断:
every()
方法:当且仅当数组中所有元素调用函数的判断结果为true
时,才返回true
some()
方法:至少有一个元素调用函数的判断结果为true
时,返回true
every()
和some()
在确定返回true
或false
后会停止遍历数组。类似&&
和||
的短路特性-
在空数组上调用,根据惯例,
every()
返回true
,some()
返回false
var a = [5, 4, 3, 2, 1]; a.every(function(value) { return value < 10; }); // ==>true,所有的值都小于10 a.every(function(value) { return value % 2 === 0; }); // ==> false,不是所有元素都是偶数 a.some(function(value) { return value % 2 === 0; }); // ==> true,数组a中有偶数 a.some(isNaN); // ==> false,a中所有元素都不是非数值元素
Array.prototype.reduce()
和Array.prototype.reduceRight()
reduce()
和reduceRight()
:使用指定函数将数组中的元素进行组合,生成单个值。在函数式编程中常用,称为注入和折叠。ruduceRight()
的工作原理与reduce()
相同,只是从右侧开始索引
reduce(fn, init)
有两个参数:
fn
是执行简化操作的函数;init
是传递给简化函数的初始值,如果不指定,默认为数组的第一个元素
reduce()
传递给化简函数fn(init, value, index array)
四个参数:
init
:第一次调用函数时,init
是reduce()
传入的初始值,如果reduce()
没有指定第二个参数,默认使用数组的第一个元素;以后的调用中,其值等于化简函数的返回值-
value
、index
、array
:分别是数组元素、元素索引和数组本身。var a = [1, 2, 3, 4, 5]; a.reduce(function(init, value) { return init + value; }, 0); //初始值init是0,返回累加的结果==> 15 //初始值为1,如果不指定,默认为数组的第一个元素 a.reduce(function(init, value) {return init * value;}, 1); // ==>120 a.reduce(function(init, value) {return (init > value)? init: value;}); var a = [2, 3, 4]; //计算2^(3^4),乘方操作的优先顺序是从右到左。 a.reduceRight(function(init, value) {return Math.pow(value, init);});
a.reduce(function(init, value) {return init + value;}, 0);
的执行过程:
化简函数第一次调用时,
reduce()
方法将0
传递给函数的init
参数,将数组的第一个元素传递给value
执行化简函数,将返回值赋予
init
,再次继续下次调用
Array.prototype.indexOf()
和Array.prototype.lastIndexOf()
搜索整个数组中指定值,返回第一个匹配值的索引,如果没有找到返回-1
。indexOf()
从头到尾查找、lastIndexOf()
从尾到头查找。
indexOf()
与lastIndexOf()
的参数没有函数,第一个参数是查找的值;第二个参数可选,指定开始查找的索引位置
//查找数组中所有出现的value,返回一个包含匹配值索引的数组
function findall(arr, value) {
var results = [];
var len = a.length;
var index = 0; //开始查找的索引
while(index < len) { //arr.indexOf(arr, index)中第二个参数是为了过滤掉之前已经查找过的数组元素
index = arr.indexOf(value, index); //避免重复的同时,加快了搜索效率
if(index === -1) {break;} //如果未找到,便退出;因为forEach()退出循环不方便,所以使用while
results.push(index);
index += 1;
}
}
4 类数组对象
4.1 数组类型
判断一个未知的对象是否为数组,可以使用ES5提供的Array.isArray()
方法,定义在Array()
构造器函数上,没有定义在Array.prototype
原型上。
Array.isArray([]); // ==>true
Array.isArray({}); // ==>false
因为typeof
和instanceof
在判断Array
类型时比较繁复,所以使用同一的Array.isArray()
来判断。isArray()
方法的可能实现:
//Function.isArray || function(o)用来判断程序中是否有同名的isArray函数,确保不会重写原有函数。
var isArray = Function.isArray || function(o) {
return (typeof o === 'object') && (Object.prototype.toString.call(o) === '[object Array]');
};
4.2 类数组对象
JS中数组的部分特性是其余对象没有的:
新元素添加到数组后,自动更新
length
属性设置
length
为较小值时,截断数组从
Array.prototype
中继承属性类属性为
Array
这4点并不是数组的本质。但是可以将一个用于length
属性(值为数值),其余属性为非负整数(索引)的对象看做类数组。由于数组的方法通用性很强,可以使用数组的方法来操作类数组对象,只是类数组对象并未继承Array.prototype
对象,所以需要Function.call()
来间接调用。
var a = {'0': 'a', '1': 'b', '2': 'c', 'length': 3}; //类数组对象
Array.prototype.join.call(a, ','); // ==> 'a,b,c'
Array.prototype.slice.call(a, 0); // ==> ['a', 'b', 'c']真正数组的副本
Array.prototype.map.call(a, function(value) {
return value.toUpperCase();
}); // ==> ['A', 'B', 'C']
函数的参数arguments
对象和document.getElementsByTagName()
获取的对象都是类数组对象。检测一个对象是否是类数组对象:
//判断o是否为类数组对象
//字符串和函数有length属性,使用typeof将其排除
//在DOM中,文本节点有length属性,要额外调用o.nodeType !== 3排除
function isArrayLike(o) {
if(o && typeof o === 'object' && //非null和undefined,并且是对象
isFinite(o.length) &&
o.length >= 0 &&
o.length == Math.floor(o.length) && //o.length是整数
o.length < Math.pow(2, 32)) { //数组索引的上限是2^32-1
retun true;
} else {
return false;
}
}
4.3 作为数组的字符串
在ES5中,字符串的行为类似于只读的数组,可以使用charAt()
访问单个字符,也可以通过[]
访问单个字符。
var s = 'test';
s.charAt(0); // ==> 't'
s[1]; // ==> 'e'
使用[]
的索引形式访问字符串,可以简化charAt()
的调用。因为字符串用于非负的整数length
属性,每个字符的索引是非负整数,可以将字符串看做类数组对象,使用数组的方法来操作字符串。
调用的方法不能改变原字符串,
splice()
、sort()
、reverse()
、pop()
、push()
、shift()
与unshift()
方法不能使用,使用数组方法修改字符串会导致错误,但是没有错误提示
var s = "javascript";
Array.prototype.join.call(s, " "); // ==>"j a v a s c r i p t"
总结
使用对象字面量方式创建数组
数组元素的读写都可以通过
[]
对于稀疏数组,其
length
属性大于元素的个数ES5之后,单纯的数组遍历可以使用
forEach()
,对于需要判断跳出循环遍历的,可以使用for
循环替代、或使用try-catch
数组的操作方法都定义在
Array.prototype
原型对象上,可以对数组进行:forEach()
、map()
、every()
和some()
、filter()
、reduce()
和reduceRinght()
、indexOf()
和lastIndexOf()
、splice()
、sort()
、reverse()
类数组对象指有非负整数的
length
值,并且索引为非负整数的对象,类数组对象可以使用数组的方法来操作,使用Function.call()
来间接调用即可