在JavaScript中,数组可以使用Array构造函数来创建,或使用[]快速创建,这也是首选的方法。数组是继承自Object的原型,并且他对typeof没有特殊的返回值,他只返回‘object‘。
运行[] instanceof Array他会返回ture。虽然结果是这样,但也有复杂的类数组对象,如字符串或arguments对象,但arguments对象并非是Array的实例,但他却拥有length属性,而且他的值是可以被索引的,因此他可以像一个数组那样被遍历。
这本文中,我将介绍数组原型的一些方法,并探讨每一种方法的用途:
使用.forEach 来做遍历
使用.some and .every 来断言
使用 .join and .concat 来合并
使用 .pop, .push, .shift, and .unshift 来操作栈和队列
使用 .map 映射模型
使用 .filter 来做查询
使用 .sort 来做排序
使用 .reduce, .reduceRight 来计算
使用 .slice 来复制
.splice 的用途
使用 .indexOf 来查找
in 操作符介绍
.reverse 的用途
使用 .forEach来遍历
======
这是原生Javascript数组最简单的方法之一,但他不支持IE6~8。
forEach 会在遍历数组中的每个元素时就执行一次回调函数,并传递三个参数。
value 当前的数组元素
index 当前元素在数组中的位置
array 对数组的引用
此外,我们还可以传递一个可选的参数,作为每个函数调用时的上下文(this),看下面例子:
[‘_‘, ‘t‘, ‘a‘, ‘n‘, ‘i‘, ‘f‘, ‘]‘].forEach(function (value, index, array) { this.push(String.fromCharCode(value.charCodeAt() + index + 2)) }, out = []) out.join(‘‘) // <- ‘awesome‘
这里使用了.join,这是我们还没有提及到的,但往后我们会再作介绍,在这个例子中,他可以连接数组中的不同元素,可以达到类似字符串拼接的效果。out[0] + ‘‘ + out[1] + ‘‘ + out[2] + ‘‘ + out[n].
我们不能中止forEach循环和抛出异常,在这些情景下,我们可以选择使用其他可用的方法。
使用 .some和.every来断言
=====
如果你曾使用过.Net的 enumerables,也许你熟悉.Any(x => x.IsAwesome) and .All(x => x.IsAwesome).
这些方法和.forEach非常相似,他们同样地给回调函数传递了value, index 和array, 你也同样的可以给他传入二个可选参数来作为回调函数的上下文。MDN是这样来描述.some的:
".some方法可以在遍历数组中的每个元素时执行回调函数,直到回调函数返回true为止,如果这个元素被发现,.some立即返回true。否则.some会返回false. 回调函数只对数组非空元素的索引调用,并不会对已经被删除或从未被指派的值调用"
max = -Infinity satisfied = [10, 12, 10, 8, 5, 23].some(function (value, index, array) { if (value > max) max = value return value < 10 }) console.log(max) // <- 12 satisfied // <- true
回调函数会在满足条件value < 10时停止执行。.every也是同样的道理,但他的短路是发生在回调函数返回false时。
使用 .join and .concat 来合并
=====
.join方法通常会和.concat方法混淆。.join是创建一个通过分隔符来链接数组中各元素的字符串,如果不提供分隔符,他就会默认以逗号作用分隔符。.concat通过他的源数组创建一个新的数组。
.concat 可以传入多个参数: array.concat(val, val2, val3, valn)
.concat 是可以返回一个新的数组
array.concat() 如果不传入参数则将会返回一个新的浅拷贝数组。
浅拷贝意味着副本可以保存着源数组的对象引用。例如:
var a = { foo: ‘bar‘ } var b = [1, 2, 3, a] var c = b.concat() console.log(b === c) // <- false b[3] === a && c[3] === a // <- true
使用 .pop, .push, .shift, and .unshift 来操作栈和队列
===
现在大家都知道可以通过.push方法来往数组中添加元素,但你是否知道.push可以传入多个参数,一次性把多个参数添加到数组后面。如:[].push(‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘z‘)
.pop方法和.push方法刚好相反。他将会返回数组中的最后一个元素,并且同时从数组中删除这个元素。如果数组为空,则会返回void . (undefined)。使用.push和.pop,我可以非常容易地创建一个LIFO(last in first out) 的栈。
function Stack () { this._stack = [] } Stack.prototype.next = function () { return this._stack.pop() } Stack.prototype.add = function () { return this._stack.push.apply(this._stack, arguments) } stack = new Stack() stack.add(1,2,3) stack.next() // <- 3
反之,我可以通过使用.unshift 和.shift来创建一个FIFO(fist in first out) 的队列。
function Queue () { this._queue = [] } Queue.prototype.next = function () { return this._queue.shift() } Queue.prototype.add = function () { return this._queue.unshift.apply(this._queue, arguments) } queue = new Queue() queue.add(1,2,3) queue.next() // <- 1
使用.shift (或.pop) 可以容易地遍历数组。
list = [1,2,3,4,5,6,7,8,9,10] while (item = list.shift()) { console.log(item) } list // <- []
使用 .map 映射模型
=========
.map在遍历数组中的每个元素时执行一次回调函数,并且会返回一个新的数组。回调函数只会对数组的元素索引执行,并不会对已删除或没元素的索引而执行。
Array.prototype.map方法和.forEach,.some和.every有着相似的地方:.map(fn(value, index, array), thisArgument)。
values = [void 0, null, false, ‘‘] values[7] = void 0 result = values.map(function(value, index, array){ console.log(value) return value }) // <- [undefined, null, false, ‘‘, undefined × 3, undefined]
undefined × 3 的意思是.map不会在一个删除或者未定义的数组元素上执行,但他们会继续保留在结果数组上。映射是对数组的转化是非常有用的。看下面示例:
// casting [1, ‘2‘, ‘30‘, ‘9‘].map(function (value) { return parseInt(value, 10) }) // 1, 2, 30, 9 [97, 119, 101, 115, 111, 109, 101].map(String.fromCharCode).join(‘‘) // <- ‘awesome‘ // a commonly used pattern is mapping to new objects items.map(function (item) { return { id: item.id, name: computeName(item) } })
使用 .filter 来做查询
======
.filter在遍历数组中的每个元素时执行一次回调函数,并且回调函数返回的true时,他将会保存这当前元素,最后返回一个新数组。回调函数只会对数组的元素索引执行,并不会对已删除或没元素的索引而执行。不传递到回调函数的元素将会被简单的忽略,并且不会在新的数组中出现。
[void 0, null, false, ‘‘, 1].filter(function (value) { return value }) // <- [1] [void 0, null, false, ‘‘, 1].filter(function (value) { return !value }) // <- [void 0, null, false, ‘‘]
使用.sort 来做排序
========
如果不提供回调函数,元素将会通过转换为字符后并且按照在字典中的顺序来排序。例如在字典中"80"在"9"的前面,但如果按数字来排序,则9在80之前。
就像大多数的排序函数一样,Array.prototype.sort(fn(a,b))可以比较两个元素。并且会在以下三个情况中返回值。
如果 a 应该出现在 b 之前,则返回值小于0。
如果a 和b 相等,则返回0。
如果 a 应该出现在 b 之后,刚返回值大于0。
[9,80,3,10,5,6].sort() // <- [10, 3, 5, 6, 80, 9] [9,80,3,10,5,6].sort(function (a, b) { return a - b }) // <- [3, 5, 6, 9, 10, 80]
使用 .reduce, .reduceRight 来计算
========
这两个方法都有着同样的特性:
.reduce(callback(previousValue, currentValue, index, array), initialValue).
在每一个回调函数执行时,previousValue将会被返回。初始化时initialValue将会被传入回调函数,currentValue包含着当前的元素,index表示该元素的在数组中的位置。array为数组的引用。
一个经典的.reduce例子就是加法函数。
Array.prototype.sum = function () { return this.reduce(function (partial, value) { console.log(partial, ",",value) return partial + value }, 0) }; [3,4,5,6,10].sum() // <- 28
如果说我们要合并一些字符串,我们可能会用到.join方法来达到目的。但是在下面这个例子中,.join方法就可能达不到我们的要求了,除非这些对象都有valueOf或者toString属性。但我们可以使用.reduce方法来轻松实现合并各对象为字符串。
function concat (input) { return input.reduce(function (partial, value) { if (partial) { partial += ‘, ‘ } return partial + value.name }, ‘‘) } concat([ { name: ‘George‘ }, { name: ‘Sam‘ }, { name: ‘Pear‘ } ])
注:reduce和reduceRight的区别是,reduce 是从数组左到右开始遍历,而reduceRight是从数组右到左开始遍历。
使用 .slice 来复制
======
Array.prototype.slice can be used to convert array-like objects into real arrays.
和.concat相似,可以通过不传递参数给.slice方法来复制源数组。.slice方法可以传入两个参数,一个是开始位置,另一个是结束位置。Array.prototype.slice也可以把类数组转为数组。
Array.prototype.slice.call({ 0: ‘a‘, 1: ‘b‘, length: 2 }) // <- [‘a‘, ‘b‘]
但是用.concat就达不到这样的目的了,因为他会把类数组放到一个真正的数组中。
Array.prototype.concat.call({ 0: ‘a‘, 1: ‘b‘, length: 2 }) // <- [{ 0: ‘a‘, 1: ‘b‘, length: 2 }]
除此之外,我们还可以把类数组转换为真正的数组之后,并且去除数组前面几个元素。
function format (text, bold) { if (bold) { text = ‘<b>‘ + text + ‘</b>‘ } var values = Array.prototype.slice.call(arguments, 2) values.forEach(function (value) { text = text.replace(‘%s‘, value) }) return text } format(‘some%sthing%s %s‘, true, ‘some‘, ‘other‘, ‘things‘) // <- <b>somesomethingother things</b>
.splice 的用途
====
.splice也是一个常用的数组方法。你可以通过.splice来删除元素,插入新元素,并且可以调用一次.splice在同样的位置达到删除,插入元素的目标不。要注意的是,这个方法是会改变源数组。
var source = [1,2,3,8,8,8,8,8,9,10,11,12,13] var spliced = source.splice(3, 4, 4, 5, 6, 7) console.log(source) // <- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13] spliced // <- [8, 8, 8, 8]
如果你有留意,他会返回已删除的元素。
var source = [1,2,3,8,8,8,8,8,9,10,11,12,13] var spliced = source.splice(9) spliced.forEach(function (value) { console.log(‘removed‘, value) }) // <- removed 10 // <- removed 11 // <- removed 12 // <- removed 13 console.log(source) // <- [1, 2, 3, 8, 8, 8, 8, 8, 9]
使用 .indexOf 来查找
=========
通过使用.indexOf方法,我们可以找到元素在数组中的位置。如果找不到,将会返回-1。如果要查找,通常我会这样写比较a === ‘a‘ || a === ‘b‘ || a === ‘c‘,但在这个场景,你可以[‘a‘, ‘b‘, ‘c‘].indexOf(a) !== -1。
要注意的是,如果是查找数组中的对象,那么你要提供相同的对象引用。第二个参数是表示从数组中的哪个位置开始搜索。
var a = { foo: ‘bar‘ } var b = [a, 2] console.log(b.indexOf(1)) // <- -1 console.log(b.indexOf({ foo: ‘bar‘ })) // <- -1 console.log(b.indexOf(a)) // <- 0 console.log(b.indexOf(a, 1)) // <- -1 b.indexOf(2, 1) // <- 1
如果你希望反顺序查找,你可以使用.lastIndexOf。
in 操作符介绍
========
.indexOf 和 in操作符非常容易混淆。
var a = [1, 2, 5] 1 in a // <- true, but because of the 2! 5 in a // <- false
问题就在这里,in操作符是用于检查一个对象的键,而不是查找一个元素在数组中的位置。当然这比.indexOf快。
var a = [3, 7, 6] 1 in a === !!a[1] // <- true
in操作符相乎是把传递进来的值转换为布尔值。!!表达式是可以把值隐式转换为布尔值。
关于.reverse
=========
这个方法是可以把数组中的元素位置反转。
var a = [1, 1, 7, 8] a.reverse() // [8, 7, 1, 1]
这里并非返回一个副本,而是直接修改了数组的本身。