从头开始复习js之让你彻彻底底搞清楚数组

538 阅读12分钟

关于数组这一块,从开始写项目开始就一直在用,但是基本都没有整理过,怎么说呢?既然在复习这个东西,那我今天正好在复习这个数组,就集中整理一下在实际开发中用到频率最高的数据吧。

一、 es5中的数组方法

es5中定义了22个数组的方法,这22个方法的使用频率基本贯穿整个前端开发,如果你想进入前端,这22个应该成为你的本能反应。下面就针对这22个,我来简单做一下代码的实例吧:

1.1、 数组定义

关于数组定义一般有两种方式:

  • new字符
  • 数组字面量
  • 数组的清空

具体的定义如下:

var testArray = new Array();
var testArray1 = [1,2,3,4,5];

额,这里重点提一下数组的清空,我建议使用:

var testArray = [];

1.2、 数组检测

在es5里面检测数据类型是不是数组的方式主要有两个,length主要用来获取数组的长度:

  • instanceof:判断构造函数
  • isArray
  • length:返回数据内元素的个数

具体的代码如下:

var array = [1,2,3,4,5];
var object = { key:1,value:"hello"};

console.log(array instanceof Array); // true
console.log(object instanceof Array); // false

console.log(Array.isArray(array)); // true
console.log(Array.isArray(object)); // false

console.log(array.length) // 5

1.3、 数组转化成字符串

数据转化成字符串一般有三种方式:

  • toLocaleString
  • toString
  • join

来看下面一段代码:

console.log(array.toString()); // 1,2,3,4,5
console.log(array.toLocaleString()); // 1,2,3,4,5
console.log(array.join("")); // 12345
console.log(array.join("&&")); // 1&&2&&3&&4&&5
console.log(array.join()); // 1,2,3,4,5

首先toStringtoLocaleString是将数组直接转化成String,中间用逗号分隔。(到现在为止我都不知道两者在Array中有什么区别,我自己做实验只在Date中发现了一点异常。)

其次是join:数组元素先被转换为字符串,再将这些字符串用 separator 分割连接在一起。如果没提供分隔符,将一个逗号用作分隔符。

1.4、 数组的添加和删除

数组的增删操作的属性一共下面几种:

  • push:在数组的最末尾添加元素
  • pop:在数据的最末尾删除元素
  • unshift: 在数组的最前面添加一个元素
  • shift: 删除数组的最前面一项

来看下面一段代码:

var array = [1,2,3,4,5];
array.push(999); // [ 1, 2, 3, 4, 5, 999 ]
array.pop(); // [ 1, 2, 3, 4, 5 ]
array.unshift(999); // [ 999, 1, 2, 3, 4, 5 ]
array.shift(); // [ 1, 2, 3, 4, 5 ]

1.5、 数组的排序

关于数组的排序,主要有两个方法:

  • reverse 翻转数组
var array = [1,2,3,4,5];
array.reverse(); // [ 5, 4, 3, 2, 1 ]
  • sort //数组中按元素来排序,默认是按照首个字符的Unicode编码排序,所以默认是从小到大排序。

sort方法接收两个参数,第一个是数组,第二个参数如果存在,就会按照第二个函数的返回值来排序,如果不存在 缺省值是从小到大。来看下面一段代码:

array.sort() // [ 1, 2, 3, 4, 5 ]

array.sort((a,b)=>{
	return b-a;
});
console.log(array); // [ 5, 4, 3, 2, 1 ]

关于数组的排序这一块的源码呢,之前我有看过一部分,我发现,在源码中数组长度小于23的时候采用的是插入排序,反之则用的是快速排序。(甚至命名都是InsertionSort,QuickSort),关于排序的实现方式,可以去看一下 我之前写的排序的文章来复习一下,我觉得我自己写的还比较好理解的。

1.6、 数组元素的裁剪/缝补

  • concat: 合并数组
var array1 = [1,2,3];
var array2 = ["1","2","3"];
console.log(array1.concat(array2)); // [1,2,3,"1","2","3"];
  • slice: 数组截取
var arr = [1, 2, 3, "a", "b", "c"];
console.log(arr.slice(3));      //["a", "b", "c"]
console.log(arr.slice(0,3));      // [1, 2, 3]
console.log(arr.slice(0,10));      // [1, 2, 3, "a", "b", "c"]
console.log(arr.slice(7,10));      //[]
console.log(arr.slice(-2));      // ["b", "c"]
console.log(arr.slice(3,0));      // []
console.log(arr); // 

这里值得我们注意的点是: 1、 slice默认接收两个参数,slice(start,end),表示从开始截取的位置和截取结束的位置。 2、 如果只填写一个参数,是从该位置开始向后截取,直到末尾。 3、 如果只填写一个负数参数的话,是从末尾开始向前截取。 4、 如果第一个参数比后面大,返回值为空数组。 5、 如果截取的开始位置大于数组长度返回空数组。 6、 如果截取的结束位置大于数组的长度,返回开始截取到数据的末尾的数列。

  • splice:数组的删除,删除,增加的数据

首先来看一个基础的例子

var arr = [1, 2, 3, "a", "b", "c"];
arr.splice(2);// [ 1, 2 ]
arr.splice(0,1); // [2]

然后我们再来看一个带插入的例子:

var arr1 = [1, 2, 3, "a", "b", "c"];
arr1.splice(3,2,"hello","world");// [ 1, 2, 3, 'hello', 'world', 'c' ]

值得我们注意的点是: 1、 splice的参数含义是:splice(开始操作的索引值,删除几项,后面的n项代表从开始操作的索引值开始插入的项) 2、 如果只传入一个参数,默认返回0到指定参数的值

1.7、 数组的查找

  • indexof/lastIndexOf,从数组的前/后查数据的索引 来看下面一个例子
var arr = [1, 2, 3, "a", "b", "c"];
console.log(arr.indexOf("b")); // 4
console.log(arr.lastIndexOf("b")); // 4
console.log(arr.indexOf("es")); // -1

这里值得注意的是:indexOf是去查询元素第一次出现的索引,而lastIndexOf是去查询元素最后一次出现的索引。

  • every 如果数组内的每一个元素都满足一个要求,就返回true
var arr = [ 1, 2, 3, 4, 5 ];

arr.every((item,index,array)=>{
	if(item>0) return true;
	return false;
}); //true

arr.every((item,index,array)=>{
	if(item>3) return true;
	return false;
}); // false

从上面的代码,我们可以看出: 1、 every接收一个函数,函数的返回参数有三个,遍历的项,当前遍历的index和数组本身 2、 当函数的全部项都返回true的时候every才成立;有一项不返回true,every将不会成立。

  • some:如果数组内存在一个元素满足回调函数内的要求,就返回true 关于some可以跟every做对比记忆。
var arr = [ 1, 2, 3, 4, 5 ];

arr.some((item,index,array)=>{
	if(item>4) return true;
	return false;
}); //true

arr.some((item,index,array)=>{
	if(item>7) return true;
	return false;
}); // false

从上面的代码,我们可以看出 1、 some接收一个函数,函数的返回参数有三个,遍历的项,当前遍历的index和数组本身 2、 当函数的全部项都返回false的时候some就会返回false;有一项返回true,some都将成立。

  • filter:返回数组内满足回调函数的元素 同样这个函数也能对比和上面两个记忆。来看下面一段代码。
var arr = [ 1, 2, 3, 4, 5 ];

arr.filter((item,index,array)=>{
	return item>3;
}); //[ 4, 5 ]

从上面可以看出来,filter将会筛选出满足条件的数列出来。

  • forEach: 数组遍历 这个就不做演示了,主要是遍历,跟for一样

1.8、 数组的重装和合并

  • map:对于数组的每一项都用函数进行重组,如果不return,返回undefined 关于map方法,其实在开发中用到的真的特别多,来看下面一个例子:
var arr = [ 1, 2, 3, 4, 5 ];

arr.map((item,index,array)=>{
	if(item>3){
		return {key:"小于三的数",value:item}
	}
	return {key:"大于二的数",value:item}
}); 
//[ { key: '大于二的数', value: 1 },
// { key: '大于二的数', value: 2 },
// { key: '大于二的数', value: 3 },
// { key: '小于三的数', value: 4 },
// { key: '小于三的数', value: 5 } ]

从上面我们可以得出,map方法是将每一项的返回值重新组装成一个新的数组,如果没有return会返回。

  • reduce/reduceRight:从左/右开始对元素的累加。
var arr = [ 1, 2, 3, 4, 5 ];

var result1 = arr.reduce((sum,item)=>{
	return sum*10 + item;
});

var  result2 = arr.reduceRight((sum,item)=>{
	return sum*10 + item;
});

console.log(result1)
console.log(result2)

二、 es5后新增的数组方法

2.1、 数组的创建和初始化

关于from和of,其实可能遇到call和bind,可以看看之前我的文章。

  • Array.from 我们来看一组代码:
var obj = {
	"0":"a",
	"1":"b",
	"2":"c",
	length: 3
}
var obj1 = {
	"0":"a",
	"1":"b",
	"2":"c",
}
var obj2 = {
	"a":"a",
	"b":"b",
	"c":"c",
	length: 3
}

console.log(Array.from(obj)); // [ 'a', 'b', 'c' ]
console.log(Array.from(obj1)); // []
console.log(Array.from(obj2)); // [ undefined, undefined, undefined ]

从上面的代码,我们可以清楚的发现: 1、 Array.from是需要length属性的,如果没有length属性,将返回空数组 2、 Array.from操作对象的属性名一定是数字,不然会返回undefined。

  • Array.of

Array.of主要用于将一组值转化成数组

Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1

Array.of的创建主要是为了弥补Array方法的一些小漏洞,例如:

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
  • fill

fill方法的作用是,将给的定值填充到数组中,来看下面一组代码:

['a', 'b', 'c'].fill(7);  // [7, 7, 7]
new Array(3).fill(7)  // [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
let arr = new Array(3).fill({name: "jkb"});
arr[0].name = "klivitam";
console.log(arr); // [{name: "klivitam"}, {name: "klivitam"}, {name: "klivitam"}]

从上面的代码我们可以发现: 1、 fill接收三个参数,fill(填充的值,填充的开始值,填充的结束值) 2、当只存在一个参数时,默认将全部数组都填充给列表项 3、 当填充的值是对象的时候,我们是填充的对象的指针,所以这是一个坑 在用的时候请谨慎哦。

2.2、 数组的查找

  • find/findIndex

let data = [1,2,3,4,5];
let obj = {
   name:"klivitam",
   number:1
}
let result1 =  data.find((it,index,arr)=>{
   return it>3;
})
let result2 = data.findIndex((it,index,arr)=>{
   return it>3;
})
let result3 =  data.find((it,index,arr)=>{
   return it>10;
})
let result4 = data.find(function(v){
   return v>this.number;
},obj)
console.log(result1) // 4
console.log(result2) // 3
console.log(result3) // undefined
console.log(result4) // 2

从面的代码我们可以发现: 1、 find和findIndex分别都接收两个参数,第一个是函数,第二个是对象,前一个函数的this指针指向后一个对象 2、 find和findIndex只能发现数组内出现的第一个值,如果没有发现返回undefined

  • includes
let data = [1,2,3,4,5,NaN];

console.log(data.includes(1)); // true
console.log(data.includes(1,2)) // false
console.log(data.includes(NaN)) // true
console.log(data.indexOf(NaN)) // -1

从面的代码我们就可以清楚的发现: 1、 includes方法接收两个参数,第一个是搜索的值,第二个是从数据的第几项开始搜索 2、 indexOf无法查找到NaN的位置,但是includes可以。

  • entries/keys/values
let data = [1,2,3];
for (let index of data.keys()) {
  console.log(index);
}
// 0
// 1
// 2

for (let elem of data.values()) {
  console.log(elem);
}
// 1
// 2
// 3

//这里使用了解构赋值
for (let [index, elem] of data.entries()) {
  console.log(index, elem);
}
// 0 1
// 1 2
// 2 3

2.2、 数组的操作

  • 扩展运算符 扩展运算符(spread)是三个点(...)。它将一个数组转为用逗号分隔的参数序列。具体的事例代码如下:
console.log(...[1, 2, 3]); // 1,2,3
console.log((...[1, 2,3])); // Uncaught SyntaxError: Unexpected number
Math.max(...[14, 3, 77]) // 77
const [...[1,2,3]] = a1;  // 1,2,3
[...['a', 'b'], ...['c'], ...['d', 'e']]; // [a,b,c,d,e]
[...'hello'];// [h,e,l,l,0]

关于扩展运算符这一块,我不想过多的来讲解,其实很多东西 还是要靠自己在写代码的时候来总结,关于有些技术,我也不知道哪里好,但是遇到问题了 我就想用。

  • copyWithin
[1, 2, 3, 4, 5].copyWithin(0, 3); // [4, 5, 3, 4, 5]
// 将3号位复制到0号位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]

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

[].copyWithin.call({length: 5, 3: 1}, 0, 3)// {0: 1, 3: 1, length: 5}

从上面的代码我们可以发现: 1、 数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。 2、 该方法接受三个参数,分别是从该位置开始替换数据,从该位置开始读取数据,到该位置前停止读取数据。

  • flat/flatMap
[1, 2, [3, 4]].flat(); // [1, 2, 3, 4]

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

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

[1, [2, [3]]].flat(Infinity)// [1, 2, 3]

[1, 2, , 4, 5].flat() // [1, 2, 4, 5]

[2, 3, 4].flatMap((x) => [x, x * 2])// [2, 4, 3, 6, 4, 8]

从上面的代码我们可以发现:

  • flat的作用是:用于将嵌套的数组“拉平”,变成一维的数组。flapmap的作用是首先将元素进行一次map方法,然后再进行一次flat方法。
  • flat方法接受一个参数,代表拉伸的层数,也可以用Infinity,拉平所有的嵌套数组
  • flat再拉平数组的时候,然后遇到空位会跳过该空位

三、数组的空位

前面在讲解flat的时候,我们提到了一个空位的概念,结合开发以及阮大神在博客中的总结,我也更深的了解到空位的差异。 首先我们来看一段代码:

[,'a'].forEach((x,i) => console.log(i)); // 1
['a',,'b'].filter(x => true) // ['a','b']
[,'a'].every(x => x==='a') // true
[1,,2].reduce((x,y) => x+y) // 3
[,'a'].some(x => x !== 'a') // false
[,'a'].map(x => 1) // [,1]
[,'a',undefined,null].join('#') // "#a##"
[,'a',undefined,null].toString() // ",a,,"

这里我们就可以明显的看到在es5中对空格的处理方法就已经出现了分歧。

  • forEach(), filter(), reduce(), every() 和some()都会跳过空位。
  • map()会跳过空位,但会保留这个值
  • join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。

那我们来看es6呢?来看下面一段代码:

Array.from(['a',,'b'])// [ "a", undefined, "b" ]
[...['a',,'b']] // [ "a", undefined, "b" ]
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]
new Array(3).fill('a') // ["a","a","a"]
let arr = [, ,];
for (let i of arr) {
  console.log(1);
}
// 1
// 1
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]
[...[,'a'].keys()] // [0,1]
[...[,'a'].values()] // [undefined,"a"]
[,'a'].find(x => true) // undefined
[,'a'].findIndex(x => true) // 0

从上面代码我们可以发现:

  • Array.from、扩展运算符、entries()、keys()、values()、find()和findIndex()方法会将数组的空位,转为undefined
  • fill()会将空位视为正常的数组位置。
  • copyWithin()会连空位一起拷贝。
  • 上面代码中,数组arr有两个空位,for...of并没有忽略它们。如果改成map方法遍历,空位是会跳过的。

其实用代码演示了这么多,无非就是想说明一件道理:空位不同的方法对其的处理方式不同 如果你能够记得所有方法的处理方式,那你大可以任意使用 如果不行 还是尽量避免出现空位吧

说在最后

这篇文章写的我很是烦躁呀,主要总结下来东西实在是太多了,但是怎么说呢?还是有很大的好处的,比如数组的空位这个之前我就只知道基础的几种,剩下的 我基本都没有去验证,现在抽时间把这些东西全部验证了一遍感觉印象也加深了不少。好了 差不多一点钟了,去睡觉了 (说好的从这周起开始睡美容觉的)