数组的全面总结

数组在工作中使用频繁,但我们大多数情况下只使用到了部分特性,久而久之,对于数组其它的相关特性就变得模糊起来,不免有错漏不全的理解,这里花了一点时间,对最常用的数组做一些总结。

定义

数组的标准定义:一个存储元素的线性集合,元素可以通过索引来任意存取,索引通常是数字,用来计算元素之间存储位置的偏移量。
数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。

内置方法

  • concat / slice / splice
  • sort / reverse
  • push / unshift
  • pop / shift
  • toString
  • join

从已有的数组创建新的数组

concat / slice / splice

concat 合并两个或多个数组,返回合并结果

不改变原有数组,返回一个新的合并数组

arrayObject.concat(arrayX,arrayX,......,arrayX),
arrayX可以是值/数组对象

[1,2].concat(3,4); //[1, 2, 3, 4]
[1,2].concat([3],[4,5],[6]); //[1, 2, 3, 4, 5, 6]

//添加一个元素到头部
[1].concat([2,3]);//[1, 2, 3]
//添加一个元素到尾部
[1,2].concat(3);//[1, 2, 3]

slice 从现有的数组返回选定的元素组成的数组 不改变原有数组

arrayObject.slice(start,end) start 和 end 定义了选取范围,正数从0开始,负数从-1开始,始终为左闭右开,也就是左边包含右边不包含

[1,2,3,4].slice(0,1);//[1]
[1,2,3,4].slice(1,3);//[2,3]
[1,2,3,4].slice(-1);//[4]
[1,2,3,4].slice(-2);//[3,4]
[1,2,3,4].slice(-2,-1);//[3]

splice 方法向/从数组中添加/删除项目,然后返回被删除的项目组成的新数组 会改变原有数组

arrayObject.splice(index,howmany,items,...)
index 起始位置,可以是负数
howmany 要删除的个数,0为不删除
items 要添加的元素

var arr1 = [1,2,3,4];
var arr2 = arr1.slice();
var arr3 = arr1.slice();

// 删除元素
var removed = arr1.splice(1,1);
console.log(removed);//[2]
console.log(arr1); //[1, 3, 4]
//删除了索引为1的元素2,并返回这个元素,原数组发生改变

// 替换元素
var replace = arr2.splice(2,1,5);
console.log(replace);//[3]
console.log(arr2);//[1, 2, 5, 4]
// 在索引为2的位置,删除了1个元素3,并在此插入新的元素5,返回了删除的元素,原数组发生变化

// 删除并插入元素
var insert = arr3.splice(3,0,5,6,7);
console.log(insert); //[]
console.log(arr3); //[1, 2, 3, 5, 6, 7, 4]
// 在索引为3的位置,删除0个元素,插入了5、6、7这3个元素到索引为3的前面

数组排序

sort / reverse

sort 用于对数组的元素进行排序
将对原数组操作,会改变原数组

arrayObject.sort(sortFn)
sortFn 为函数,用于提供排序比较规则,返回值是正值,则第一个参数比第二个大,负值,则第1个比第2个小,零,则两个数相等
如果sortFn为空,则按照字符编码的顺序进行排序

[1,51,4,12,2,3].sort() //[1, 12, 2, 3, 4, 51]
[1,51,4,12,2,3].sort(function(a,b){return a-b}) //[1, 2, 3, 4, 12, 51]
[1,51,4,12,2,3].sort(function(a,b){return b-a}) //[51, 12, 4, 3, 2, 1]
//或者
[1,51,4,12,2,3].sort(new Function('a','b','return a-b'))
[1,51,4,12,2,3].sort((a,b)=>a-b)

reverse 用于颠倒数组中元素的顺序 会改变原有的数组

[1,3,4,5].reverse() //[5, 4, 3, 1]

//反转字符串
'54321'.split('').reverse().join(''); //'12345'
//反转数字
Number(Number(12345).toString().split('').reverse().join('')) //54321

为数组增减元素

  • push 添加 元素至数组尾部,返回新的数组长度
  • unshift 添加 元素至数组开头,返回新的数组长度
  • pop 删除 尾部元素,返回删除的元素
  • shift 删除 头部元素,返回删除的元素

都是直接对原数组操作,会改变原有数组

var arr = [1,2,3,4];
var arr1 = arr.slice();
var arr2 = arr.slice();
var arr3 = arr.slice();
var arr4 = arr.slice();

console.log(arr1.push(5), arr1); //5 [1, 2, 3, 4, 5]
console.log(arr2.unshift(0), arr2); //5 [0, 1, 2, 3, 4]
console.log(arr3.pop(), arr3); //4 [1, 2, 3]
console.log(arr4.shift(), arr4); //1 [2, 3, 4]

数组转字符串

toString / join

[1,2,3,4].toString() //"1,2,3,4"
[1,2,3,4]+'' //"1,2,3,4"
[1,2,3,4].join('-');//"1-2-3-4"

ES5新增

  • Array.isArray()
  • [].indexOf() / [].lastIndexOf()
  • [].forEach / [].map()
  • [].every() / [].some()
  • [].filter()
  • [].reduce() / [].reduceRight()

数组类型判断

自定义一个方法来判断是否为数组

function isArray(obj){
return Object.prototype.toString.call(obj) === '[object Array]';
}

isArray([]); //true

ES5新增 Array.isArray() 来判断是否为数组

Array.isArray([]);//true
Array.isArray({a:1});//false

// IE9以下兼容写法
if(typeof Array.isArray !== 'function'){
Array.isArray = function(obj){
return Object.prototype.toString.call(obj) === '[object Array]';
}
}

查找元素

indexOf(el, startIndex) / lastIndexOf(el, lastIndex)

没有查找到元素返回-1,查找到元素,返回查找到的第一个的索引值,lastIndexOf 为从后面查起

  • startIndex 表示从哪个索引开始找,默认为第1个索引
  • lastIndex 表示从倒数第几个索引开始找,默认为最后一个索引
[1,2,3,2,4].indexOf(2); //1
[1,2,3,2,4].indexOf(2,2); //3
[1,2,3,2,4].lastIndexOf(2); //3
[1,2,3,2,4].indexOf(5); //-1
[1,'1'].indexOf('1');//1
// 下面的查找返回了-1,=== 运算符的缺陷,后面ES6的includes方法会弥补这一缺陷
[1,NaN].indexOf(NaN);//-1
[1,{}].indexOf({});//-1

// IE9以下兼容写法
if(Array.prototype.indexOf !== 'function'){
Array.prototype.indexOf = function(searchEl, startIndex){
var index = -1;
startIndex = startIndex * 1 || 0;
for (var k = 0, len = this.length; k < len; k++) {
if (k >= startIndex && this[k] === searchEl) {
index = k;
break;
}
}
return index;
}
}

if(Array.prototype.lastIndexOf !== 'function'){
Array.prototype.lastIndexOf = function(searchEl, lastIndex){
var index = -1, len = this.length;
lastIndex = lastIndex * 1 || len;

while (len--) {
if (len <= lastIndex && this[len] === searchEl) {
index = len;
break;
}
}
return index;
}
}

数据遍历

[].forEach() / [].map()

[].forEach(function(value, index, arr){}, context)
循环遍历数组中的每一个元素,处理函数中的参数依次为:当前元素、当前元素索引、原数组
forEach的第二个参数,可以指定处理函数中this的指向

var arr = [1,2,3];
arr.forEach(function(v,i,a){
console.log(v,i,a);
v+=1;
})
console.log(arr); //[1,2,3]
// 1 0 [1, 2, 3]
// 2 1 [1, 2, 3]
// 3 2 [1, 2, 3]

// 等同于
for(var i=0; i<arr.length; i++){
console.log(arr[i], i, arr);
}

// IE9以下的兼容性写法
if(Array.prototype.forEach !== 'function'){
Array.prototype.forEach = function(fn, context){
for(var i=0, len=this.length; i<len; i++){
if(typeof fn === 'function' && Object.prototype.hasOwnProperty.call(this, i)){
fn.call(context, this[i], i, this);
}
}
}
}

jQuery中的 $.each(function(index, values, arr){}) 回调中的参数第1个是索引,第2个是元素,$.map() 亦是如此

map(function(value, index, arr){}) 与forEach类似,返回一个新的数组,不改变原有数组

var arr = [1,2,3];
var res = arr.map(function(v){
return v+1;
});
console.log(arr); //[2, 3, 4]
console.log(res); //[1, 2, 3]

// IE9以下的兼容性写法
if(Array.prototype.map !== 'function'){
Array.prototype.map = function(fn, context){
var res = [];
if(typeof fn === 'function'){
for(var i=0, len=this.length; i<len; i++){
res.push(fn.call(context, this[i], i, this));
}
}
return res;
}
}

map的回调处理函数一定要有返回值,如果没用,则会返回undefined

检测是否包含某元素

[].every(function(value, index, arr){}, context) / [].some(function(value, index, arr){}, context)

  • 数组元素的逻辑判定,回调函数返回true/false
  • some是所判定的元素只要数组中有一个符合条件就返回true
  • ervery是数组中必须所有元素都符合条件就返回true,否则返回false
  • 在空数组上调用every返回true,some返回false
console.log([1,2,3,4].some(v=>v>3)) //true
console.log([1,2,3,4].every(v=>v>3)) //false
console.log([].every(v=>v>3)) //true
console.log([].some(v=>v>3)) //false

// IE9以下兼容写法
if(Array.prototype.some !== 'function'){
Array.prototype.some = function(fn, context){
var passed = false;
if (typeof fn === "function") {
for (var k = 0, length = this.length; k < length; k++) {
if (passed === true) break;
passed = !!fn.call(context, this[k], k, this);
}
}
return passed;
}
}

if (typeof Array.prototype.every != "function") {
Array.prototype.every = function (fn, context) {
var passed = true;
if (typeof fn === "function") {
for (var k = 0, length = this.length; k < length; k++) {
if (passed === false) break;
passed = !!fn.call(context, this[k], k, this);
}
}
return passed;
};
}

数组过滤

[].filter(fn, context)

  • 当遍历元素时,fn返回true,则返回这个元素,否则不返回,fn中返回值只要弱等于true或false即可,使用==比较
  • filter返回一个新的结果数组,不改变原有数组
[1,2,3,4].filter(v=>v>2); //[3, 4]
[1,2,0,null,''].filter(v=>v); //[1, 2]

// IE9以下兼容写法
if(Array.prototype.some !== 'function'){
Array.prototype.some = function(fn, context){
var arr = [];
if (typeof fn === "function") {
for (var k = 0, length = this.length; k < length; k++) {
fn.call(context, this[k], k, this) && arr.push(this[k]);
}
}
return arr;
}
}

数组聚合

[].reduce(fn, initialValue) / [].reduceRight(fn, initialValue)

  • 将数组元素聚合(合并)为一个元素
  • fn 的参数依次为 (之前值、当前值、索引值、数组本身),
  • initialValue 表示最初的值,如果不存在,初始值为数组第1个元素

reduceRightreduce 用法一致,区别是从最后一个元素开始迭代

var res = [1,2,3,4].reduce(function(prev, curr, index, arr){
console.log(prev, curr, index, arr);
return prev+=curr;
})
console.log(res);//10
// 没有initialValue,则初始值为1
// 1 2 1 [1, 2, 3, 4]
// 3 3 2 [1, 2, 3, 4]
// 6 4 3 [1, 2, 3, 4]

[1,2,3,4].reduce(function(prev, curr, index, arr){
console.log(prev, curr, index, arr);
return prev+=curr;
}, 10)
// 如果initialValue有值,则初始值为initialValue的值
// 10 1 0 [1, 2, 3, 4]
// 11 2 1 [1, 2, 3, 4]
// 13 3 2 [1, 2, 3, 4]
// 16 4 3 [1, 2, 3, 4]

[1,2,3,4].reduceRight(function(prev, curr, index, arr){
console.log(prev, curr, index, arr);
return prev+=curr;
})
// 从最后一个元素开始迭代,初始值为4
// 4 3 2 [1, 2, 3, 4]
// 7 2 1 [1, 2, 3, 4]
// 9 1 0 [1, 2, 3, 4]

// 二维数组扁平化
[[1,2],[3,4]].reduce((prev,curr)=>prev.concat(curr)) //[1, 2, 3, 4]


// IE9以下兼容写法
if (typeof Array.prototype.reduce != "function") {
Array.prototype.reduce = function (fn, initialValue ) {
var prev = initialValue, k = 0, len = this.length;
if (typeof initialValue === "undefined") {
prev = this[0];
k = 1;
}

if (typeof fn === "function") {
for (k; k < len; k++) {
this.hasOwnProperty(k) && (prev = callback(prev, this[k], k, this));
}
}
return previous;
};
}

if (typeof Array.prototype.reduceRight != "function") {
Array.prototype.reduceRight = function (fn, initialValue ) {
var len = this.length, k = len - 1, prev = initialValue;
if (typeof initialValue === "undefined") {
prev = this[len - 1];
k--;
}
if (typeof fn === "function") {
for (k; k > -1; k--) {
this.hasOwnProperty(k) && (prev = callback(prev, this[k], k, this));
}
}
return previous;
};
}

ES6新增

  • Array.from(arrLike) //类数组转数组
  • Array.of(…args) //将一组值转成数组
  • [].copyWithin(target, start, end) //复制数组成员到指定位置,会覆盖原有成员
  • [].find(fn) // 查找元素,返回元素本身
  • [].findIndex(fn) // 查找元素返回元素索引,没有则返回-1
  • [].includes() // 检测数组是否包含某元素,返回true/false
  • [].fill(value, start, end) //填充数组
  • [].keys() // 返回数组的key组成的数组
  • [].values() // 返回数组的值组成的数组
  • [].entries() // 返回数组索引和值组成的数组

数组转换

Array.from(arrLike) / Array.of(...args)

Array.from 可以将类数组(有length属性的Object/可遍历(iterable)的对象/Set对象/Map对象)转换为数组

let arrayLike = {'0':'a', '1':'b', '2':'c', length:3};
// 以前的写法
[].slice.call(arrayLike)//["a", "b", "c"]
// 使用Array.from
let arr1 = Array.from(arrayLike); //["a", "b", "c"]

let set = new Set([1,3,4,5]);
let arr2 = Array.from(set);//[1,3,4,5]

let map = new Map([[1,'a'], [2,'b'], [3,'c']]);
let arr3 = Array.from(map);//[[1,'a'],[2,'b'],[3,'c']]

let arr = [1,2,3,4];
let iter = arr[Symbol.iterator]();
iter.next();//{value: 1, done: false}
let arr4 = Array.from(iter); //[2,3,4]

Array.of 用于将一组值转换为数组

使用 Array 的构造函数初始化数组时,如果参数是1个,那么会创建这个参数长度的数组,数组的每一项为 undefined,如果参数是多个,那么会创建这多个参数组成的数组,所以参数的个数会导致结果的不一致,Array.of 正是弥补这一点的扩展

Array(2);//[,]
Array(2,3);//[2,3]

Array.of(2);//[2]
Array.of(2,3);//[2,3]

数组元素复制、填充与查找

  • [].copyWithin(target, start, end) 复制数组成员到指定位置,会覆盖原有成员
  • [].fill(value, start, end) 填充数组
  • [].find(fn) 查找元素,返回元素本身
  • [].findIndex(fn) 查找元素返回元素索引,没有则返回-1
  • [].includes(value, startIndex) 检测数组是否包含某元素,返回true/false

Array.prototype.copyWithin(target, start = 0, end = this.length)

copyWithin 的三个参数:

  • target(必需):从该位置开始替换数据。
  • start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
  • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。

fill 的三个参数

  • value:要填充的元素
  • start: 从哪里开始填充
  • end: 从哪里填充结束
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

Array(5).fill(0); //[0, 0, 0, 0, 0]
  • find、findIndex 都是用于查找符合条件的元素,都可发现NaN,弥补了indexOf的不足
  • includes 用于检测是否含有某个元素,返回 true/false

区别:

  • includes 返回true或false,参数是要查找的元素,第2个参数会查找的起始索引,可以是负数
  • find 返回的查找到的元素,没有则返回undefined
  • findIndex 返回的是元素的索引值,没有则返回-1
[1,2,3,4].find(v=>v>3); //4
[1,2,3,4].find(v=>v>4); //undefined
[1,2,3,4].findIndex(v=>v>2); //2
[1,2,3,4].findIndex(v=>v>4); //-1

[1,2,NaN].indexOf(NaN); //-1 查找不到

[1,2,NaN].find(v=>Object.is(v,NaN)); //NaN
[1,2,NaN].findIndex(v=>Object.is(v,NaN)); //2

[1,2,NaN].includes(NaN); //true
[1,2,3,4].includes(2,2); //false

获取数组中的键和值

  • [].keys() // 返回 key 组成的数组
  • [].values() // 返回 value 组成的数组
  • [].entries() // 返回 {key:value} 组成的数组

这三个函数都返回一个 iterator 遍历器对象,可以使用 for...of 循环遍历

let iter1 = [1,2,3,4].keys();
let iter2 = [1,2,3,4].values(); //Chromium 50暂不支持
let iter3 = [1,2,3,4].entries();

for(let item of iter1){ console.log(item) };// 0 1 2 3
for(let item of iter2){ console.log(item) };// 1 2 3 4
for(let [index, item] of iter3){ console.log(index, item) };
// 0 1
// 1 2
// 2 3
// 3 4

改变数组的方法

  • splice
  • shift / pop
  • unshift / push
  • reverse / sort
  • copyWithin / fill

不改变数组的方法

  • indexOf / lastIndexOf
  • toString / join
  • concat / slice
  • some / every / filter
  • map / forEach
  • reduce / reduceRight
  • keys / values / entries
  • find / findIndex / includes

返回新数组的方法 concat / slice / map

返回iterator对象的方法 keys / values / entries

数组空位

空位表示没有任何值,也不等于 undefined

in 运算符可以检测数组的某个key是否含有值

0 in [undefined,undefined]; //返回true 0号位置有值
0 in [,,]; //返回false 0号位置没有值

数组方法对空位的处理

  • forEach(), filter(), every() 和some()都会忽略空位。
  • map()会跳过空位,但会保留这个值
  • join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
// forEach方法 忽略空位
[,'a'].forEach((x,i) => console.log(i)); // 1

// filter方法 忽略空位
['a',,'b'].filter(x => true) // ['a','b']

// every方法 忽略空位
[,'a'].every(x => x==='a') // true

// some方法 忽略空位
[,'a'].some(x => x !== 'a') // false

// map方法 保留空位
[,'a'].map(x => 1) // [,1]

// join方法
[,'a',undefined,null].join('#') // "#a##"

// toString方法
[,'a',undefined,null].toString() // ",a,,"

数组乱序

  • sort+random
  • 快速随机排序
  • 随机至新数组

sort+random

调用 sort 函数,随机返回 true or false 来决定是否交换位置

var arr = [0,1,2,3,4,5,6,7,8,9];
Array.prototype.shuffle = function(){
var res = this.slice();
return res.sort( (a, b)=>Math.random()-0.5 )
}
arr.shuffle(); //[1, 0, 6, 2, 4, 3, 8, 7, 9, 5]

这种方法打乱的数组中,越往后面最大数字出现的概率越高,所以并不稳定

改进: 由于原数组大数在后小数在前,会出现以上结果,如果随机将首尾交换将产生较理想的结果

var arr = [0,1,2,3,4,5,6,7,8,9];
Array.prototype.shuffle = function(){
var res = this.slice(), len = res.length;
res = res.concat(res).splice(~~(Math.random()*len), len);
return res.sort(function(){return Math.random() - 0.5});

}
arr.shuffle(); //[6, 0, 9, 8, 7, 2, 1, 5, 4, 3]

快速随机排序

例:数组有5个元素

  • 第一次从前4个元素中随机一个元素与第5个元素交换
  • 第二次从前3个元素中随机一个元素与第4个元素交换
  • 第三次从前2个元素中随机一个元素与第3个元素交换
  • 第四次将第1个元素中与第2个元素交换
var arr = [0,1,2,3,4];
Array.prototype.shuffle = function(){
var res = this.slice(), i = 0, len = res.length, idx, temp;
for(i; i<len-1; i++){
idx = Math.floor(Math.random() * (len - i));
temp = res[idx];
res[idx] = res[len - i -1];
res[len - i -1] = temp;
}
return res;
}
arr.shuffle();//[1, 0, 3, 4, 2]

效率高,循环次数少,稳定均匀,时间复杂度O(n-1)

随机至新数组

每次随机从原数组抽取一个元素,添加到新数组,直到原数组元素个数为0

var arr = [0,1,2,3,4,5,6,7,8,9];
Array.prototype.shuffle = function(){
var res = this.slice(), i=0, len = res.length, idx=0, newArr=[];
for(i; i<len; i++){
idx = Math.floor(Math.random()*(len-i));
newArr.push(res.splice(idx,1)[0]);
}
return newArr;
}

arr.shuffle();//[5, 6, 7, 8, 4, 0, 9, 3, 1, 2]

效率比快速随机差一点,时间复杂度O(n)

数组去重

  1. for+for
  2. for + indexOf
  3. filter + indexOf
  4. sort + filter
  5. splice
  6. HashTable
  7. from + Set (ES6)

for+for

创建一个新的数组,对原数组进行两层for循环遍历比较,新数组里如果存在,则跳出循环,进入下一个,如果没有,则将元素添加至新数组,
最后返回这个去重后的新数组

var arr1 = [1, '2', 9, 1, '1', 2, 4, 9];

Array.prototype.distinct = function(){
var arr = this, res = [], i=0, j=0, len = arr.length;

for(i; i<len; i++){
var item = arr[i];
// 如果元素在结果数组中存在,则进行下一次循环
for(j=0; j<res.length; j++){
if(res[j] === item) break;
}
// 两个数组比较完成后,没有重复,则将元素添加到结果数组中
j === res.length && res.push(item);
}
// 返回去重后的新数组
return res;
}

// 另外一种实现
Array.prototype.distinct = function(){
var arr = this, res = [], i=0, j, len = arr.length;

for(i; i<len; i++){
for(j=i+1; j<len; j++){
// 如果有重复的,i+1,进入下一次循环
// 如果没有重复的,i,不变
// 这样内层的for循环就会返回不重复的元素的索引和重复元素出现的最后一次的索引
// 这里比较绕,设计很精妙
if(arr[i] === arr[j]){
j = ++i;
}
}
log(i);
res.push(arr[i]);
}

return res;
}

arr1.distinct();

for+indexOf

与上面的原理相同,如果结果数组中不存在,则添加,否则继续循环下一次,时间复杂度O(n)

var arr1 = [1, '2', 9, 1, '1', 2, 4, 9];
Array.prototype.distinct = function(){
var arr = this, res = [], i=0, len = arr.length;

for(i; i<len; i++){
var item = arr[i];
(res.indexOf(item) === -1) && res.push(item);
}
return res;
}

filter+indexOf

使用filter替代上面的for循环

因为indexOf是返回元素中数组中第一次出现的索引,所以后面重复的元素返回的索引是第一次出现的索引,而不等于当前的索引,所以不会被返回

Array.prototype.distinct = function(){
return this.filter(function(v, i, arr){
return arr.indexOf(v) === i;
})
}

splice去重

双层for循环对比加splice实现,发现重复的值,则删除掉,再将length减1,

Array.prototype.distinct = function(){
var arr = this.slice(), i=0, j, len=arr.length;

for(i; i<len; i++){
for(j=i+1; j<len; j++){
if(arr[i] === arr[j]){
arr.splice(j, 1);
len--;
j--;
}
}
}
return arr;
}

速度慢,占用内存高

sort+filter

先利用sort排序(重复的值会被排到一起),第一个直接返回,从第二个元素开始,相邻两个元素比较,如果不相等,则返回后面这个元素

Array.prototype.distinct = function(){
return this.concat().sort().filter(function(v, i, arr){
return !i || v !== arr[i-1];
})
}

Hash去重

  • 将数组的元素做为Hash对象的键,如果Hash中不存在,则将元素添加到结果数组中,存在,则继续下一次循环
  • 由于1和’1’在hash对象中的key是会被转换成字符串的,所以 1 和 ‘1’ 会被认为是同一个,这里使用 key = typeof(item) + item 来做为key值
Array.prototype.distinct = function(){
var arr=this, hash = {}, i=0, res=[], len = arr.length, key;

for(i; i<len; i++){
key = typeof(arr[i])+arr[i];
if(hash[key]!==true){
res.push(arr[i]);
hash[key] = true;
}
}
return res;
}

Hash去重的改进

  • 当数组元素为Object复杂对象时,上面的方式就不适用了,如{a:1} === {a:1} //false
  • 将数组中的项序列化为字符串,做为Object的key,因为Oject的key值是字符串,1和‘1’会被认为是同一个键
var o = {};
o[1]=true; o['1']=true;
console.log(o) // {'1':true}
// 使用JOSN.stringify(),字符串类型会被转义,那么相同的值,不同的类型,将可以保持唯一

var o = {};
o[JSON.stringify(1)] = true;
o[JSON.stringify('1')] = true;
o // {'1':true, '\'1\'':true}

var arr2 = [ { a: 1 }, { a: 1 }, [ 1, 2 ], [ 1, 2 ], 1, 1, '1', '1' ]
Array.prototype.distinct = function(){
var hashTable = {};

return this.filter(function(v){
var key = JSON.stringify(v);
var match = Boolean(hashTable[key]);

return (match ? false : hashTable[key] = true);
})
}

ES6 的去重方法

Array.from() 将一个类数组转为数组,Set数据结构存储的元素是唯一的

Array.prototype.distinct = function(){
return Array.from(new Set(this));
}

总结
上面几种去重方式,filter+indexOf相当较快,但不能去除复杂对象如{a:1}和{a:1}, hash去重最准确,但速度稍慢,for+splice效率最差,ES6的from+Set仍然不能对复杂对象去重

数组排序(跳转)

数组排序方法众多,可以有很多种算法,这里请参考 经典排序算法_集锦

reduce高级用法

reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值。

reduce() 函数接收2个参数(M: 必填, O: 可选):

  • (M) 回调reducer 函数 处理先前的结算结果和下一个元素直到序列结束。
  • (O) 初值 作为第一次调用回调时的第一个参数。

rr.reduce(function(prev, cur, index, arr) {});

  • prev: 第一项的值或者上一次叠加的结果值
  • cur: 当前会参与叠加的项
  • index: 当前值的索引
  • arr: 数组本身
// 当前的购物清单
var items = [{price: 10}, {price: 120}, {price: 1000}];

// 1. 计算总价
var sum = items.reduce((prev, curr, i, arr) =>prev+curr.price,0);
log(sum);//1130

// 2. 减去折扣(优惠20块)
var sum = items.reduce((prev, curr, i, arr) =>prev+curr.price,-20);
log(sum);//1110

// 多个reducer合并为一个reducer
var reducers = {
totalInDollar: function(state, item){
state.dollar += item.price;
return state;
},
totalInEuros: function(state, item){
state.euros += item.price * 0.897424;
return state;
},
totalInPounds: function(state, item){
state.dollar += item.price * 0.6923412;
return state;
},
totalInYen: function(state, item){
state.dollar += item.price * 112.853;
return state;
}
}

var combineTotalPriceReducers = function(reducers){
return function(state, item){
return Object.keys(reducers).reduce(function(nextState, key){
reducers[key](state, item);
return state;
}, {})
}
}

var bigTotalPriceReducer = combineTotalPriceReducers(reducers);
var initialState = {dollars:0, euros:0, yens:0, pounds:0};
var totals = items.reduce(bigTotalPriceReducer, initialState);

计算字母在字符串中出现的次数

var result = 'abcawab'.split('').reduce(function(res, cur){
res[cur] ? res[cur]++ : (res[cur] = 1);
return res;
},{});
log(result);//{a: 3, b: 2, c: 1, w: 1}

元素添加

  • 尾部添加 效率: arr[arr.length] > push > concat
  • 头部添加 效率:[1].concat(arr) > arr.unshift(1) (safari相反)
  • 中间添加 arr.splice(arr.length/2, 0, 1)

多维数组扁平化

var arrs = [[1, 2],[3, 4, 5], [6, 7, 8, 9]];

var res = [];
for (var i = 0; i < arrs.length; ++i) {
for (var j = 0; j < arrs[i].length; ++j)
res.push(arr[i][j]);
}
console.log(res);

[].concat.apply([], arrs);

arrs.reduce(function(prev,curr){
return prev.concat(curr);
})

[].concat(...arrs);

清空数组

var a =[1,3]; a.length=0 与 a = [] 两种清空数组的区别:

  • list=[] 将一个新的引用数组赋值给变量,其它的引用不受影响,以前的数组内容如果被引用的话,将存储在内存,会导致内存泄漏
  • list.length 删除数组里的内容,将影响到其它引用
var foo = [1,2,3];
var bar = [1,2,3];
var foo2 = foo;
var bar2 = bar;
foo = [];
bar.length = 0;
console.log(foo, bar, foo2, bar2);

[] [] [1, 2, 3] []

求平均值、最大值、最小值

Mathmax 方法和 min 方法接收多个参数,分别返回参数中的最大值和最小值

Math.max(1,2,3,4);
Math.min(1,2,3,4);

// 如果参数是一个数组[1,2,3,4],那么应该调用apply方法
var arr = [1,2,3,4];
Math.max.apply(null, arr);
Math.min.apply(null, arr);

// ES6中使用延展符展开数组
Math.max(...arr);
Math.min(...arr);

// 最大值
var max = arr[0];
arr.forEach(v=>{ max = v > max ? v : max; })

// 平均值
let nums = [2,3,4,1,2,33,12,45];
let sum = nums.reduce((prev,curr)=>prev+=curr);
let avg = sum / nums.length;

扩展阅读