从头开始复习js之让你彻彻底底搞清楚数组
关于数组这一块,从开始写项目开始就一直在用,但是基本都没有整理过,怎么说呢?既然在复习这个东西,那我今天正好在复习这个数组,就集中整理一下在实际开发中用到频率最高的数据吧。
一、 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
首先toString和toLocaleString是将数组直接转化成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方法遍历,空位是会跳过的。
其实用代码演示了这么多,无非就是想说明一件道理:空位不同的方法对其的处理方式不同 如果你能够记得所有方法的处理方式,那你大可以任意使用 如果不行 还是尽量避免出现空位吧
说在最后
这篇文章写的我很是烦躁呀,主要总结下来东西实在是太多了,但是怎么说呢?还是有很大的好处的,比如数组的空位这个之前我就只知道基础的几种,剩下的 我基本都没有去验证,现在抽时间把这些东西全部验证了一遍感觉印象也加深了不少。好了 差不多一点钟了,去睡觉了 (说好的从这周起开始睡美容觉的)