JavaScript原生数组

说明

数组是类似列表的对象,在原型中提供了一些遍历以及改变其中对象的方法。JavaScript数组的长度及其中元素的类型都是不固定的。因为数组的长度可以随时增长或缩减,所以JavaScript 数组不保证是密集的。通常情况下,这是一些方便的特性;如果这些特性不适用于你的特定使用场景,你可以考虑使用固定类型数组。在JavaScript中数组可以使用数组构造函Array被创建,为了方便一般用中括号[]创建,Arrays继承Object的原型.typeof检测数组的类型会返回object,可是使用[]instanceof Array,会返回true,也就是说在JavaScript中存在类似数组的Array-like的对象objects,例如:strings,arguments对象,arguments对象不是数组的实例,但是它有length属性,它的值和数组的下标属性之间是有关系的并且length是可变的(例如, push, splice, 等) 会改变Arraylength属性。因此它可以像任何数组一样去遍历.这篇文章主要介绍一些Array.prototype中常用的方法,尽可能揭开每一种方法的真面纱。

JavaScript-Array
你可以在你的浏览器控制台里面尝试!

目录

  • .forEach循环
  • .some.every检测
  • .join.concat细微差别
  • .pop,.push,.shift,和.unshift模拟堆栈
  • .map映射
  • .filter过滤测试
  • .sort排序
  • .reduce,.reduceRight累加器
  • .slice复制(copy)
  • .splice修改(update)
  • .indexOf查找
  • .reverse逆序

    forEeah循环

    array.forEach(callback[,thisArg])forEach 方法按升序为数组中含有效值的每一项执行一次callback 函数,那些已删除(使用delete方法等情况)或者从未赋值的项将被跳过(但不包括哪些值为 undefined 的项),这个给定的回调函数可以传入三个参数
  • callback 给定执行的回调函数
    • value 数组当前项的值
    • index 当前的索引(或下标)
    • array 数组本身
  • thisArg 可选参数。用来当作callback 函数内this的值的对象。
1
2
3
4
5
6
['_', 't', 'a', 'n', 'i', 'f', ']'].forEach(function (value, index, array) {
this.push(String.fromCharCode(value.charCodeAt() + index + 2))
}, out = [])

out.join('')
//<-'awesome'

some和every

array.some(callback[,thisArg])some 为数组中的每一个元素执行一次 callback 函数,直到找到一个使得 callback 返回一个“真值”(即可转换为布尔值 true 的值)。如果找到了这样一个值,some 将会立即返回 true。否则,some 返回 false。callback 只会在那些”有值“的索引上被调用,不会在那些被删除或从来未被赋值的索引上调用。callback 被调用时传入三个参数:元素的值,元素的索引,被遍历的数组。some调用不改变数组。some 遍历的元素的范围在第一次调用 callback. 时就已经确定了。在调用 some 后被添加到数组中的值不会被 callback 访问到。如果数组中存在且还未被访问到的元素被 callback 改变了,则其传递给 callback 的值是 some 访问到它那一刻的值。

1
2
3
4
5
6
7
8
9
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

array.every(callback[,thisArg]) every 方法为数组中的每个元素执行一次 callback 函数,直到它找到一个使 callback 返回 false(表示可转换为布尔值 false 的值)的元素。如果发现了一个这样的元素,every 方法将会立即返回 false。否则,callback 为每一个元素返回 true,every 就会返回 true。callback 只会为那些已经被赋值的索引调用。不会为那些被删除或从来没被赋值的索引调用。every也不会改变数组,遍历元素的范围和值与some基本一样。

1
2
3
4
5
6
7
function isBigEnough(element, index, array) {
return (element >= 10);
}
var passed = [12, 5, 8, 130, 44].every(isBigEnough);
// passed is false
passed = [12, 54, 18, 130, 44].every(isBigEnough);
// passed is true

总结下:some其实就相当与检测数组中是否存在使callback返回true的值.而every是检测数组中任意值都能使callback返回true.这里的true指的是可以转换为布尔值的元素.

join和concat

str = arr.join([separator = ',']) join方法将所有的数组元素被转换成字符串,再用一个分隔符将这些字符串连接起来。如果元素是undefined 或者null,则会转化成空字符串。

  • separator 可选,用于指定连接每个数组元素的分隔符。分隔符会被转成字符串类型;如果省略的话,默认为一个逗号。如果 seprator 是一个空字符串,那么数组中的所有元素将被直接连接。
    1
    2
    3
    4
    5
    var a = ['Wind', 'Rain', 'Fire'];
    var myVar1 = a.join(); // myVar1的值变为"Wind,Rain,Fire"
    var myVar2 = a.join(', '); // myVar2的值变为"Wind, Rain, Fire"
    var myVar3 = a.join(' + '); // myVar3的值变为"Wind + Rain + Fire"
    var myVar4 = a.join(''); // myVar4的值变为"WindRainFire"

array.concat(value1, value2, ..., valueN)concat 方法将创建一个新的数组,然后将调用它的对象(this 指向的对象)中的元素以及所有参数中的数组类型的参数中的元素以及非数组类型的参数本身按照顺序放入这个新数组,并返回该数组.
concat 方法并不修改调用它的对象(this 指向的对象) 和参数中的各个数组本身的值,而是将他们的每个元素拷贝一份放在组合成的新数组中.array.concat()如果没有参数传入,就会返回array的一个浅复制.对新数组的任何操作都不会对原数组产生影响,反之亦然.原数组中的元素有两种被拷贝的方式:

  • 对象引用(非对象直接量):concat 方法会复制对象引用放到组合的新数组里,原数组和新数组中的对象引用都指向同一个实际的对象,所以,当实际的对象被修改时,两个数组也同时会被修改.
  • 字符串和数字(是原始值,而不是包装原始值的 String 和 Number 对象): concat 方法会复制字符串和数字的值放到新数组里.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var alpha = ["a", "b", "c"];
    var numeric = [1, 2, 3];
    // 组成新数组 ["a", "b", "c", 1, 2, 3]; 原数组 alpha 和 numeric 未被修改
    var alphaNumeric = alpha.concat(numeric);
    var num1 = [1, 2, 3];
    var num2 = [4, 5, 6];
    var num3 = [7, 8, 9];
    // 组成新数组[1, 2, 3, 4, 5, 6, 7, 8, 9]; 原数组 num1, num2, num3 未被修改
    var nums = num1.concat(num2, num3);
    var alpha = ['a', 'b', 'c'];
    // 组成新数组 ["a", "b", "c", 1, 2, 3], 原alpha数组未被修改
    var alphaNumeric = alpha.concat(1, [2, 3]);

pop,push和shift,unshift模拟堆栈

array.pop() pop方法删除一个数组中的最后一个元素,并且把这个删除掉的元素返回给调用者。pop 被有意设计成具有通用性,该方法可以通过 callapply 方法应用于一个类数组(array-like)对象上。

arr.push(element1, ..., elementN) push方法push 方法把值添加到数组中。push 方法有意具有通用性。该方法和 call()apply() 一起使用时,可应用在类似数组的对象上。push 方法根据 length 属性来决定从哪里开始插入给定的值。如果 length 不能被转成一个数值,则插入的元素索引为 0,包括 length 不存在时。当 length不存在时,将会创建它。唯一的原生类数组(array-like)对象是 Strings,尽管如此,它们并不适用该方法,因为字符串是不可改变的。

1
2
3
4
var vegetables = ['parsnip', 'potato'];
var moreVegs = ['celery', 'beetroot'];
Array.prototype.push.apply(vegetables, moreVegs);
console.log(vegetables); // ['parsnip', 'potato', 'celery', 'beetroot']

array.shift() shift 方法移除索引为 0 的元素(即第一个元素),并返回被移除的元素,其他元素的索引值随之减 1。如果 length 属性的值为 0 (长度为 0),则返回 undefined。 shift 方法并不局限于数组:该方法亦可通过 callapply 作用于对象上。对于不包含 length 属性的对象,将添加一个值为 0 的 length 属性。

arr.unshift(element1, ..., elementN) unshift 方法会在调用它的类数组(array-like)对象的开始位置插入给定的参数。unshift 特意被设计成具有通用性;这个方法能够通过 callapply 方法作用于类似数组的对象上。不过对于没有 length 属性(代表从0开始的一系列连续的数字属性的最后一个)的对象,调用该方法可能没有任何意义。

1
2
3
4
5
6
7
var arr = [1, 2];
arr.unshift(0); //result of call is 3, the new array length
//arr is [0, 1, 2]
arr.unshift(-2, -1); // = 5
//arr is [-2, -1, 0, 1, 2]
arr.unshift( [-3] );
//arr is [[-3], -2, -1, 0, 1, 2]

模拟LIFO(last in first out)栈

1
2
3
4
5
6
7
8
9
10
11
12
13
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

模拟FIFO(first in first out)堆

1
2
3
4
5
6
7
8
9
10
11
12
13
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

map

array.map(callback[, thisArg]) map 方法会给原数组中的每个元素都按顺序调用一次 callback 函数。callback 每次执行后的返回值组合起来形成一个新数组。callback 函数只会在有值的索引上被调用;那些从来没被赋过值或者使用 delete 删除的索引则不会被调用。callback 函数会被自动传入三个参数:数组元素,元素索引,原数组本身。

  • callback 给定执行的回调函数
    • value 数组当前项的值
    • index 当前的索引(或下标)
    • array 数组本身
  • thisArg 可选参数。用来当作callback 函数内this的值的对象。
    如果 thisArg 参数有值,则每次 callback 函数被调用的时候,this 都会指向 thisArg 参数上的这个对象。如果省略了 thisArg 参数,或者赋值为 null 或 undefined,则 this 指向全局对象。
    map 不修改调用它的原数组本身(当然可以在 callback 执行时改变原数组)。使用 map 方法处理数组时,数组元素的范围是在 callback 方法第一次调用之前就已经确定了。在 map 方法执行的过程中:原数组中新增加的元素将不会被 callback 访问到;若已经存在的元素被改变或删除了,则它们的传递到 callback 的值是 map 方法遍历到它们的那一时刻的值;而被删除的元素将不会被访问到。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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]
    // 通常使用parseInt时,只需要传递一个参数.但实际上,parseInt可以有两个参数.第二个参数是进制数.可以通过语句"alert(parseInt.length)===2"来验证.
    [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

array.filter(callback[, thisArg]) filter 为数组中的每个元素调用一次 callback 函数,并利用所有使得 callback 返回 true 或 等价于 true 的值 的元素创建一个新数组。callback 只会在已经赋值的索引上被调用,对于那些已经被删除或者从未被赋值的索引不会被调用。那些没有通过 callback 测试的元素会被跳过,不会被包含在新数组中。 callback 被调用时传入三个参数:元素的值,元素的索引,被遍历的数组.如果为 filter 提供一个 thisArg参数,则它会被作为 callback 被调用时的 this 值。否则,callback 的 this 值在非严格模式下将是全局对象,严格模式下为undefined。filter和上面的some,every,forEach,map 具有相同的特性;不会改变原数组。filter遍历的元素范围在第一次调用 callback 之前就已经确定了。在调用 filter 之后被添加到数组中的元素不会被 filter 遍历到。如果已经存在的元素被改变了,则他们传入 callback 的值是 filter 遍历到它们那一刻的值。被删除或从来未被赋值的元素不会被遍历到。

1
2
3
4
5
6
7
8
[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

arr.sort([compareFunction]) sort()方法对数组的元素做原地的排序,并返回这个数组。 sort 可能不是稳定的。默认按照字符串的Unicode码位点(code point)排序。如果没有指明 compareFunction ,那么元素会被转换为字符串并按照万国码位点顺序排序。例如 “Cherry” 会被排列到 “banana” 之前。当对数字进行排序的时候, 9 会出现在 80 之后,因为他们会先被转换为字符串,而 “80” 比 “9” 要靠前。如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。记 a 和 b 是两个将要被比较的元素:

  • 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
  • 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。备注: ECMAScript 标准并不保证这一行为,而且也不是所有浏览器都会遵守(例如 Mozilla 在 2003 年之前的版本);
  • 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
  • compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    var stringArray = ["Blue", "Humpback", "Beluga"];
    var numericStringArray = ["80", "9", "700"];
    var numberArray = [40, 1, 5, 200];
    var mixedNumericArray = ["80", "9", "700", 40, 1, 5, 200];
    function compareNumbers(a, b)
    {
    return a - b;
    }
    console.log('stringArray:', stringArray.join());
    console.log('Sorted:', stringArray.sort());
    console.log('numberArray:', numberArray.join());
    console.log('Sorted without a compare function:', numberArray.sort());
    console.log('Sorted with compareNumbers:', numberArray.sort(compareNumbers));
    console.log('numericStringArray:', numericStringArray.join());
    console.log('Sorted without a compare function:', numericStringArray.sort());
    console.log('Sorted with compareNumbers:', numericStringArray.sort(compareNumbers));
    console.log('mixedNumericArray:', mixedNumericArray.join());
    console.log('Sorted without a compare function:', mixedNumericArray.sort());
    console.log('Sorted with compareNumbers:', mixedNumericArray.sort(compareNumbers));

对非 ASCII 字符排序

1
2
3
4
5
var items = ['réservé', 'premier', 'cliché', 'communiqué', 'café', 'adieu'];
items.sort(function (a, b) {
return a.localeCompare(b);
});
// items is ['adieu', 'café', 'cliché', 'communiqué', 'premier', 'réservé']

使用映射改善排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 需要被排序的数组
var list = ['Delta', 'alpha', 'CHARLIE', 'bravo']
// 对需要排序的数字和位置的临时存储
var mapped = list.map(function(el, i) {
return { index: i, value: el.toLowerCase() };
})
// 按照多个值排序数组
mapped.sort(function(a, b) {
return +(a.value > b.value) || +(a.value === b.value) - 1;
});
// 根据索引得到排序的结果
var result = mapped.map(function(el){
return list[el.index];
});

reduce和reduceRight

array.reduce(callback,[initialValue]) reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值。reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。回调函数第一次执行时,previousValue 和 currentValue 可以是一个值,如果 initialValue 在调用 reduce 时被提供,那么第一个 previousValue 等于 initialValue ,并且currentValue 等于数组中的第一个值;如果initialValue 未被提供,那么previousValue 等于数组中的第一个值,currentValue等于数组中的第二个值。如果数组为空并且没有提供initialValue, 会抛出TypeError 。如果数组仅有一个元素(无论位置如何)并且没有提供initialValue, 或者有提供initialValue但是数组为空,那么此唯一值将被返回并且callback不会被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Array.prototype.sum = function () {
return this.reduce(function (partial, value) {
return partial + value
}, 0)
};
[3,4,5,6,10].sum()
// <- 28

function concat (input) {
return input.reduce(function (partial, value) {
if (partial) {
partial += ', '
}
return partial + value.name
}, '')
}
concat([
{ name: 'George' },
{ name: 'Sam' },
{ name: 'Pear' }
])
// <- 'George, Sam, Pear'

arr.reduceRight(callback[, initialValue]) reduceRight() 方法接受一个函数作为累加器(accumulator),让每个值(从右到左,亦即从尾到头)缩减为一个值。(与 reduce() 的执行方向相反)

1
2
3
4
5
6
7
8
var total = [0, 1, 2, 3].reduceRight(function(a, b) {
return a + b;
});
// total == 6
var flattened = [[0, 1], [2, 3], [4, 5]].reduceRight(function(a, b) {
return a.concat(b);
}, []);
// flattened is [4, 5, 2, 3, 0, 1]

slice

array.slice([begin[,end]]) slice不修改原数组,只会返回一个包含了原数组中提取的部分元素的一个新数组。原数组的元素会按照下述规则拷贝:

  • 对象引用(非对象直接量):slice方法会拷贝对象引用放到新数组里,原数组和新数组中的对象引用都指向同一个实际的对象,所以,当实际的对象被修改时,两个数组也同时会被修改.
  • 字符串和数字(是原始值,而不是包装原始值的 String 和 Number 对象): slice方法会拷贝字符串和数字的值放到新数组里.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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]
    Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 })
    // <- ['a', 'b']
    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

array.splice(start, deleteCount[, item1[, item2[, ...]]]) splice() 方法用新元素替换旧元素,以此修改数组的内容。如果添加进数组的元素个数不等于被删除的元素个数,数组的长度会发生相应的改变。

  • 返回值 由被删除的元素组成的一个数组。如果只删除了一个元素,则返回只包含一个元素的数组。如果没有删除元素,则返回空数组。
  • 参数
    • start 从数组的哪一位开始修改内容。如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位。
    • deleteCount 整数,表示要移除的数组元素的个数。如果 deleteCount 是 0,则不移除元素。这种情况下,至少应添加一个新元素。如果 deleteCount 大于start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)。
    • itemN 要添加进数组的元素。如果不指定,则 splice() 只删除数组元素。
1
2
3
4
5
6
7
8
9
10
11
ar 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

arr.indexOf(searchElement[, fromIndex = 0])indexOf()方法返回给定元素能找在数组中找到的第一个索引值,否则返回-1。 indexOf 使用strict equality (无论是 ===, 还是 triple-equals操作符都基于同样的方法)进行判断 searchElement与数组中包含的元素之间的关系。searchElement:要查找的元素,fromIndex:开始查找的位置。

1
2
3
4
5
6
7
8
9
10
11
12
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

reverse

array.reverse reverse() 方法颠倒数组中元素的位置。第一个元素会成为最后一个,最后一个会成为第一个。简称逆序
reverse 方法颠倒数组中元素的位置,并返回该数组的引用。

1
2
3
var a = [1, 2, 3, 4]
a.reverse()
// [4, 3, 2, 1]

最后分享一段有趣的代码,在你的浏览器控制台中运行,页面中各层的HTML就会被不同的颜色标记出来。

1
2
3
[].forEach.call($$("*"),function(a){
a.style.outline="1px solid #"+(~~(Math.random()*(1<<24))).toString(16)
})

是不是很好玩! :-)