数组 Array
本质上,数组属于一种特殊的对象。typeof
运算符会返回数组的类型是 object
- 数组的特殊性体现在,它的键名是按次序排列的一组整数(0,1,2...)
// Object.keys方法返回数组的所有键名。 var arr = ['a', 'b', 'c']; Object.keys(arr); // ["0", "1", "2"] // 可以看到数组的键名就是整数0、1、2。
- 数组的键名其实也是字符串。之所以可以用数值读取,是因为非字符串的键名会被转为字符串
- JavaScript 使用一个32位整数,保存数组的元素个数。这意味着,数组成员最多只有 4294967295 个(232 - 1)个,也就是说
length
属性的最大值就是 4294967295
与普通对象的区别是 "数组使用数字作为索引 [index] 来操作属性"。index 从 0 开始。
数组的存储性能相对较好。
var arr = ['唐僧','孙悟空','猪八戒','沙和尚','白龙马'];
1. 创建数组对象
-
var arr = new Array(); // typeof arr 返回 object
2. 给数组添加对象
-
arr[0] = 1; // console.log(arr) 打印 "1" arr[1] = 11; // console.log(arr) 打印 "1,11" arr[2] = 22; // console.log(arr) 打印 "1,11,22"
3. 读取数组对象
-
console.log(arr[0]); // 1 console.log(arr[1]); // 11 console.log(arr[2]); // 22 console.log(arr[3]); // undefined 不会报错
4. 获取数组长度
- arr.length 总是返回 "最大索引值+1" ,所以尽量避免创建不连续数组
var arr = [1,2,3]; console.log(arr.length); // 返回 3 ,因为有 3 个元素 arr[0],arr[1],arr[2] arr.[100] = 100; // 此时数组有4个元素 arr[0],arr[1],arr[2],arr[100] console.log(arr.length); // 但是会返回 101 , 实际只有 4 个元素, 这四个元素以外的其他 97 个元素值都为 undefined, 且用 in 判断时返回 false
- 清空数组的一个有效方法,就是将 arr.length属性设为 0
var arr = [ 'a', 'b', 'c' ]; arr.length = 0; arr // []
- b
- b
-
5. 在数组最后添加元素
-
// 根据上一个特性,可以在数组最后追加一个元素 arr[arr.length] = 666;
1. 使用字面量来创建数组
-
var arr = []; // 和 new Array() 效果相同
2. 在创建时指定元素
-
var arr = [0,1,2,3,4]; // 等同于 var arr = new Array(0,1,2,3,4);
3. 创建一个只有一个元素的数组
-
var arr = [10]; // 此时数组只有1个元素,arr[0] = 10; var arr = new Array(10); // 此时创建了一个长度为10的数组!!! // 每个元素的值都为 undefined
4. 数组中元素可以放任意数据类型的数据
-
var arr = ["Hello","How",1,2,3,null,undefined,true,false]; // 数组元素是对象 var sun = {name:"孙悟空"}; arr[arr.length] = sun; console.log(arr[arr.length-1].name); // 将会打印 "孙悟空" // 数组元素是函数 var arr = [ function(){console.log("Hello!"}, function(){console.log("Hi!")} ]; // 数组 里面放 数组 var newArr = [["01","02","03"],[0,1,2],["11","22","33"]]; // 混合着放 var arr = [ {a: 1}, // 对象 [1, 2, 3], // 数组 function() {return true;} // 函数 ]; arr[0] // Object {a: 1} arr[1] // [1, 2, 3] arr[2] // function (){return true;}
5. 遍历数组
for...in
循环不仅可以遍历对象,也可以遍历数组,毕竟数组只是一种特殊对象var a = [1, 2, 3]; for (var i in a) { console.log(a[i]); // 1 2 3 }
- 但是,
for...in
不仅会遍历数组所有的数字键,还会遍历非数字键。所以,不推荐使用for...in
遍历数组。var a = [1, 2, 3]; a.foo = true; for (var key in a) { console.log(key); // 0 1 2 foo console.log(key); // 1 2 3 true }
- 数组的遍历可以考虑使用
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]); }
- 数组的 arr.forEach() 方法,也可以用来遍历数组
var colors = ['red', 'green', 'blue']; colors.forEach(function (color) { console.log(color); // red green blue });
- 数组的空位 hole 不影响
length
属性- 当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)
- 如果最后一个元素后面有逗号,并不会产生空位。也就是说,有没有这个逗号,结果都是一样的。
var a = ['a' , , 'c']; a.length // 3
// 数组的空位是可以读取的,返回undefined
a[1] // undefined
a[0] // 'a'
a[2] // 'c'var a = [1, 2, 3,];
a.length // 3
- delete 命令删除一个数组成员,会形成空位,并且不会影响
length
属性var a = [1, 2, 3]; delete a[1]; a[1] // 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']
- 如果是空位,使用数组的
6. 类似数组对象
- 如果一个对象的所有键名都是正整数或零,并且有
length
属性,那么这个对象就很像数组,语法上称为“类似数组的对象”(array-like object)。var obj = { 0: 'a', 1: 'b', 2: 'c', length: 3 }; obj[0] // 'a' obj[1] // 'b' obj.length // 3 obj.push('d') // TypeError: obj.push is not a function // 因为它们不具备数组特有的方法。 // 对象obj没有数组的push方法,使用该方法就会报错。
- 有一个问题,这种
length
属性不是动态值,不会随着成员的变化而变化 - 典型的“类似数组的对象”有 函数的
arguments
对象,以及大多数 DOM 元素集,还有字符串。 - 数组的
slice
方法可以将 “类似数组的对象” 变成真正的数组-
var arr = Array.prototype.slice.call(arrayLike);
-
- 通过
call()
,可以把forEach()
嫁接到arrayLike
上面调用- 如:在
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 = Array.prototype.slice.call('abc'); arr.forEach(function (chr) { console.log(chr); // a b c });
7. 数组的静态方法
- Array.isArray() 判断变量是否是数组
var arr = [1, 2, 3]; typeof arr // "object" Array.isArray(arr) // true
8. 数组的实例方法
- .valueOf() 数组的
valueOf
方法 返回数组本身。。。不同对象的valueOf
方法不尽一致 - .toString() 数组的
toString
方法 返回数组的字符串形式 - .push() 用于在数组的末端添加一个或多个元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组
-
var arr = []; arr.push(1); // 1 arr.push('a'); // 2 arr.push(true, {}); // 4 // arr = [1, 'a', true, {}]
-
- .pop() 用于删除数组的最后一个元素,并返回该元素。注意,该方法会改变原数组
-
var arr = ['a', 'b', 'c']; arr.pop() // 'c' // arr = ['a', 'b']
对空数组使用
pop
方法,不会报错,而是返回undefined
push
和pop
结合使用,就构成了“后进先出”的栈结构(stack)
-
- .shift() 用于删除数组的第一个元素,并返回该元素。注意,该方法会改变原数组
shift
方法可以遍历并清空一个数组。var list = [1, 2, 3, 4, 5, 6]; var item; while (item = list.shift()) { console.log(item); } list // []
push
和shift
结合使用,就构成了“先进先出”的队列结构(queue)
- .unshift() 用于在数组的第一个位置添加元素,并返回添加新元素后的数组长度。注意,该方法会改变原数组
unshift
方法可以接受多个参数,这些参数都会添加到目标数组头部
- .join() 以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。
-
var a = [1, 2, 3, 4]; a.join(' '); // '1 2 3 4' a.join(' | '); // "1 | 2 | 3 | 4" a.join(); // "1,2,3,4"
[undefined, null].join('#') // '#' ['a',, 'b'].join('-') // 'a--b'
如果数组成员是
undefined
或null
或空位,会被转成空字符串 - 通过
call
方法,这个方法也可以用于字符串或类似数组的对象Array.prototype.join.call('hello', '-') // "h-e-l-l-o" var obj = { 0: 'a', 1: 'b', length: 2 }; Array.prototype.join.call(obj, '-') // 'a-b'
-
- .concat() 用于多个数组的合并。
- 它将新数组的成员,添加到原数组成员的后部,然后返回一个新数组,原数组不变
-
['hello'].concat(['world']); // ["hello", "world"] ['hello'].concat(['world'], ['!']); // ["hello", "world", "!"] [].concat({a: 1}, {b: 2}); // [{ a: 1 }, { b: 2 }] [2].concat({a: 1}); // [2, {a: 1}]
- 如果数组成员包括对象,
concat
方法返回当前数组的一个浅拷贝。所谓“浅拷贝”,指的是新数组拷贝的是对象的引用- 改变原对象以后,新数组跟着改变
- .reverse() 颠倒排列数组元素,返回改变后的数组。注意,该方法将改变原数组
- .slice(start, end) 用于提取目标数组的一部分,返回一个新数组,原数组不变
- 它的第一个参数为起始位置(从0开始),
- 第二个参数为终止位置(但该位置的元素本身不包括在内)。如果省略第二个参数,则一直返回到原数组的最后一个成员
- 如果
slice
方法的参数是负数,则表示倒数计算的位置 - 如果第一个参数大于等于数组长度,或者第二个参数小于第一个参数,则返回空数组。
-
var a = ['a', 'b', 'c']; a.slice(-2); // ["b", "c"] a.slice(-2, -1); // ["b"] var a = ['a', 'b', 'c']; a.slice(4); // [] a.slice(2, 1); // []
- Array.prototype.slice().call()方法的一个重要应用,是 将类似数组的对象转为真正的数组
Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 }) // ['a', 'b'] Array.prototype.slice.call(document.querySelectorAll("div")); Array.prototype.slice.call(arguments);
- .splice(start, count, addElement1, addElement2, ...)
- 用于删除原数组的一部分成员,并可以在删除的位置添加新的数组成员,返回值是被删除的元素。注意,该方法会改变原数组
- 第一个参数是删除的起始位置(从0开始),起始位置如果是负数,就表示从倒数位置开始删除。
- 第二个参数是被删除的元素个数,
- 如果后面还有更多的参数,则表示这些就是要被插入数组的新元素。
- 如果只提供第一个参数,从起始位置开始,删除后面所有元素组成一个数组返回
- .sort() 对数组成员进行排序,默认是按照字典顺序排序。排序后,原数组将被改变
数值会被先转成字符串,再按照字典顺序进行比较所以
101
排在11
的前面- 如果想让
sort
方法按照自定义方式排序,可以传入一个函数作为参数- 函数本身接受两个参数,表示进行比较的两个数组成员。如果该函数的返回值大于
0
,表示第一个成员排在第二个成员后面;其他情况下,都是第一个元素排在第二个元素前面。[10111, 1101, 111].sort(function (a, b) { return a - b; }); // [111, 1101, 10111]
[ { name: "张三", age: 30 }, { name: "李四", age: 24 }, { name: "王五", age: 28 } ].sort(function (o1, o2) { return o1.age - o2.age; }); // [ // { name: "李四", age: 24 }, // { name: "王五", age: 28 }, // { name: "张三", age: 30 } // ]
- 函数本身接受两个参数,表示进行比较的两个数组成员。如果该函数的返回值大于
- .map() 方法
- 将数组的所有成员依次传入参数函数,
- 然后把每一次的执行结果组成一个新数组返回。。。原数组没有变化
-
var numbers = [1, 2, 3]; numbers.map(function (n) { return n + 1; }); // [2, 3, 4] numbers // [1, 2, 3]
-
- 该函数调用时,
map
方法向它传入三个参数:当前成员 elem、当前位置 index、数组本身 arr-
[1, 2, 3].map(function(elem, index, arr) { return elem * index; }); // [0, 2, 6]
-
map
方法还可以接受第二个参数,用来绑定回调函数内部的this
变量-
var arr = ['a', 'b', 'c']; [1, 2].map(function (e) { return this[e]; }, arr) // ['b', 'c']
-
- 如果数组有空位,
map
方法的回调函数在这个位置不会执行,会跳过数组的空位 (不会跳过不会跳过undefined
和null
)-
var f = function (n) { return 'a' }; [1, undefined, 2].map(f); // ["a", "a", "a"] [1, null, 2].map(f); // ["a", "a", "a"] [1, , 2].map(f); // ["a", , "a"]
-
- .forEach()
- 与
map
方法很相似,也是对数组的所有成员依次执行参数函数 - 但是,
forEach
方法不返回值,只用来操作数据。 - 这就是说,如果数组遍历的目的是为了得到返回值,那么使用
map
方法,否则使用forEach
方法 - 参数是一个函数,该函数同样接受三个参数:当前成员 elem、当前位置 index、数组本身 arr
forEach
方法也可以接受第二个参数,绑定参数函数的 this 变量forEach
方法也会跳过数组的空位 , 不会跳过undefined
和null
-
var out = []; [1, 2, 3].forEach(function(elem) { this.push(elem * elem); }, out); out // [1, 4, 9]
- 注意 :
forEach
方法无法中断执行,总是会将所有成员遍历完。- 如果希望符合某种条件时,就中断遍历,要使用
for
循环
- 与
- .filter() 用于过滤数组成员,满足条件的成员组成一个新数组返回
- 参数是一个函数,所有数组成员依次执行该函数,返回结果为
true
的成员组成一个新数组返回。该方法不会改变原数组。-
[1, 2, 3, 4, 5].filter(function (elem) { return (elem > 3); }); // [4, 5] 将大于3的数组成员,作为一个新数组返回 var arr = [0, 1, 'a', false]; arr.filter(Boolean); // [1, "a"] 获取所有布尔值为true的成员
-
- 函数可以接受三个参数:当前成员 elem、当前位置 index、数组本身 arr
-
[1, 2, 3, 4, 5].filter(function (elem, index, arr) { return index % 2 === 0; }); // [1, 3, 5]
-
filter
方法还可以接受第二个参数,用来绑定参数函数内部的this
变量-
var obj = { MAX: 3 }; var myFilter = function (item) { if (item > this.MAX) return true; }; var arr = [2, 8, 3, 4, 1, 3, 2, 9]; arr.filter(myFilter, obj); // [8, 4, 9]
-
- 参数是一个函数,所有数组成员依次执行该函数,返回结果为
- .some() 和 every()
-
- 这两个方法类似“断言”(assert),返回一个布尔值,表示判断数组成员是否符合某种条件
- 接受一个函数作为参数,所有数组成员依次执行该函数。然后返回一个布尔值
- 该函数接受三个参数:当前成员 elem、当前位置 index、数组本身 arr
some
方法是只要一个成员的返回值是true
,则整个some
方法的返回值就是true
,否则返回false
-
var arr = [1, 2, 3, 4, 5]; arr.some(function (elem, index, arr) { return elem >= 3; }); // true
-
every
方法是所有成员的返回值都是true
,整个every
方法才返回true
,否则返回false
-
var arr = [1, 2, 3, 4, 5]; arr.every(function (elem, index, arr) { return elem >= 3; }); // false
-
- 注意,对于空数组,
some
方法返回false
,every
方法返回true
,回调函数都不会执行
-
- .reduce() 和 .reduceRight()
- 依次处理数组的每个成员,最终累计为一个值。
- 它们的差别是 :
reduce
是从左到右处理(从第一个成员到最后一个成员)reduceRight
则是从右到左(从最后一个成员到第一个成员),其他完全一样
-
// 求出数组所有成员的和
[1, 2, 3, 4, 5].reduce(function (a, b) { console.log(a, b); return a + b; }); // 1 2 第一次执行,a
是数组的第一个成员1
,b
是数组的第二个成员2
// 3 3 第二次执行,a
为上一轮的返回值3
,b
为第三个成员3
// 6 4 第三次执行,a
为上一轮的返回值6
,b
为第四个成员4
// 10 5 第四次执行,a
为上一轮返回值10
,b
为第五个成员5
// 最后结果:15 reduce()
和reduceRight()
的第一个参数都是一个函数。该函数接受以下四个参数- 累积变量,默认为数组的第一个成员
- 当前变量,默认为数组的第二个成员
- 当前位置(默认从0开始)
- 原数组
- 只有前两个是必须的,后两个则是可选的
- 如果要对累积变量指定初值,可以把它放在
reduce
方法和reduceRight
方法的第二个参数-
[1, 2, 3, 4, 5].reduce(function (a, b) { return a + b; }, 10); // 25 // 指定参数a的初值为10,所以数组从10开始累加,最终结果为25。 // 注意,这时b是从数组的第一个成员开始遍历
-
- 由于空数组取不到初始值,
reduce
方法会报错。这时,加上第二个参数,就能保证总是会返回一个值 -
function substract(prev, cur) { return prev - cur; } // 比较 reduce() reduceRight() [3, 2, 1].reduce(substract) // 0 [3, 2, 1].reduceRight(substract) // -4
- 还可以用来做一些遍历相关的操作。比如,找出字符长度最长的数组成员
-
function findLongest(entries) { return entries.reduce(function (longest, entry) { return entry.length > longest.length ? entry : longest; }, ''); } findLongest(['aaa', 'bb', 'c']); // "aaa"
-
- .indexOf() 返回给定元素在数组中第一次出现的位置,如果没有出现则返回
-1
- 还可以接受第二个参数,表示搜索的开始位置
var a = ['a', 'b', 'c']; a.indexOf('b'); // 1 a.indexOf('y'); // -1 ['a', 'b', 'c'].indexOf('a', 1); // -1 结果为-1,表示没有搜索到
- 还可以接受第二个参数,表示搜索的开始位置
- .lastIndexOf() 返回给定元素在数组中最后一次出现的位置,如果没有出现则返回
-1
-
var a = [2, 5, 9, 2]; a.lastIndexOf(2); // 3
a.lastIndexOf(7); // -1[NaN].indexOf(NaN) // -1
[NaN].lastIndexOf(NaN) // -1
- 不能用来搜索
NaN
的位置,即它们无法确定数组成员是否包含NaN
-
- 有不少返回的还是数组,所以可以链式使用
-
var users = [ {name: 'tom', email: 'tom@example.com'}, {name: 'peter', email: 'peter@example.com'} ]; users .map(function (user) { return user.email; }) .filter(function (email) { return /^t/.test(email); }) .forEach(function (email) { console.log(email); }); // 先产生一个所有 Email 地址组成的数组,然后再过滤出以t开头的 Email 地址,最后将它打印出来
-