参考文章:【干货】js 数组详细操作方法及解析合集
本文绝大多数内容及整体骨架都出自上面这篇文章,全无新意,建议大家直接阅读上面的文章。本文仅作自己记录总结用,并补充了自己的一点想法。若构成侵权,请联系删除。
至今做前端也有一年半了,但很多API或一些基础的操作都是用到的时候再去百度,用过又忘记,再用再百度。又感于这样效率实在不高,所以想系统地整理一下工作中常用的操作和一些知识点,譬如数组操作,对象操作,字符串操作,正则操作及原型,this,作用域,异步等。咀嚼输出的过程也是再学习的过程。
创建数组
- 字面量方式
let arr = [1,2,3]; - 构造器方式
let arr = new Array(1,2,3);
其中构造器方式中new可以省略,new Array() 和 Array()效果是一样的
es6 Array.of
ES6提供了一种新的创建数组的方式:Array.of(),返回由所有参数组成的数组
Array.of(1,2,3) ==> [1,2,3]Array.of() 出现的目的是为了解决上述构造器因参数个数不同,导致的行为有差异的问题
Array(3) ==> [empty × 3] //若只传入一个参数,则该参数指代新创建的数组的长度补:稀疏数组
什么是稀疏数组:我理解稀疏数组是索引连续但值不连续的数组。在JS数组中,数组的索引和值在某种程度上是可以相对独立开来。
稀疏数组的创建:和普通数组一样有两种方式,字面量方式和构造函数方式
[,,,] ==> [empty × 3]
[1,,] ==> [1, empty]
Array(3) ==> [empty × 3]如果一开始声明了一个数组随后又扩展了其长度,那么该数组也会变为稀疏数组
let arr = [1];
arr.length = 10 ==> [1, empty × 9] //指定数组长度扩容
arr[9] = 1 ==> [1, empty × 8, 10] //直接指定数组索引扩容如果取稀疏数组中的“孔”(孔是逻辑上存在于数组中,但物理上不存在与内存中的那些数组项,可以减少内存空间的浪费),会得到undefined
let arr = Array(3);
arr[0] ==> undefiend
arr[0] === undefined ==> true但直接声明数组项为undefined并不会得到稀疏数组
let arr = [undefiend, undefiend, undefiend] ==> arr并不是稀疏数组我猜这应该是JS解释器做了处理,稀疏数组中孔的值并不是undefined,而是比undefined更为纯粹的空值。但是没有专门的数据类型标识孔,所以折衷使用undefined表示。
如果想了解常见的数组方法针对稀疏数组的特异行为可以参考这篇文章:JS稀疏数组与孔
这里总结一下以作记录。
1:slice会复制孔
var arr = [ 'a', , 'b' ]
arr.slice(1,2) //[empty]
2:forEach,some,every会跳过孔,即不对孔调回调函数
3:map不对孔调回调函数,但map返回的数组中会保留孔
4:filter不会对孔调回调函数但会将孔过滤调,即filter返回的数组中不会保留孔。用filter可以生成密集数组
var arr = [ 'a', , 'b' ]
arr.filter((item)=>item >= 'a') // ["a", "b"]
5:join会将孔转化为一个空字符串进行拼接,与undefiend一样
arr = ['a', , 'b'];
arr.join('-') // 'a--b'
arr = ['a', undefiend, 'b'];
arr.join('-') // 'a--b'
6:其他数组方法会正常对待孔,将孔所在的值视为undefined。当然展示是empty,取值是undefined
arr = ['a', , 'b'];
arr.sort() //['a','b',empty]
arr.sort()[2] //undefined
arr.splice(-1,0,'c') //["a", "b", "c", empty]
数组方法
将数组方法分成三类:一是调用后会改变原数组的方法,二是调用后不改变原数组的方法,三是遍历数组的方法。
改变原数组的方法(9个)
splice() 添加/删除数组元素
功能:splice可在指定位置添加,删除数组中的项目
语法: arr.splice(index, howmany, item1, item2...) / arr1.splice(index, howmany, ...arr2)
返回值:由被删除元素依次组合成的数组。如果没有被删除的元素(只是添加或指定的数组索引范围无对应元素),则返回空数组
参数:
- index:必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置,如-1则表示数组中倒数第一个元素,即最后一个元素,一次类推。数组方法中允许使用负数指定操作索引的大多遵循这样的表示方式。
- howmany:可选。要删除的项目数量。如果设置为 0,则不会删除项目。
- item1, ..., itemX: 可选。向数组添加的新项目。splice既可以删除又可以添加元素,当有第三个参数时,splice是添加,当没有第三个参数时,splice是删除
备注:
- splice在添加数组元素时,会以指定索引为原点,将新增元素依次插入到指定索引的左边。但在删除数组元素时,会以指定索引为起始点,向右删除指定数量的数组元素,若超出数组长度,则不再删除,并不会报错,删除的元素将包括指定索引。
arr1 = [1,2,3,4,5]
arr1.splice(-2,2) //[4,5]指定索引为删除的第一个元素 arr==>[1,2,3]向右删除
arr2 = [1,2,3,4,5]
arr2.splice(3,2) //效果同上
arr3 = [1,2,3,4,5]
arr3.splice(-2,0,"添加1","添加2") //arr3 ==> [1,2,3,"添加1","添加2",4,5] 向左添加,后添加的在后面- splice并不会跳过稀疏数组中的孔,而是视其为正常数组项,splice删除数组元素的本质应该是只是缩减其长度,并不会校验此项是否有值。另外使用splice在超出数组长度的索引处添加值并不会生成稀疏数组
arr = [1,2,,] //[1, 2, empty]
arr.splice(-1,1,3) //[empty] arr ==> [1,2,3]
arr1 = [1]
arr1.splice(9,0,2) //arr==>[1,2] 指定的索引值超出数组长度,但也并不会生成稀疏数组sort() 数组排序
功能:按照一定的规则对数组元素进行排序,并返回排序好的数组
参数:指定的排序函数,可选。若没有指定排序函数,默认按字母升序,如果元素不是字符串的话,会调用元素的toString()方法将元素转化为字符串的Unicode(万国码)位点,然后再比较字符
返回值:排序好的数组
比较函数的两个参数:
sort的比较函数有两个参数,这两个参数接收的是数组中相邻的要比较的两个元素。若指定了sort的比较函数,则sort以比较函数的返回值作为排序的依据。下面以a,b依次指代比较函数中的两个参数说明sort排序的规则
- 若比较函数返回值<0,那么a将排到b的前面;
- 若比较函数返回值=0,那么a 和 b 相对位置不变;
- 若比较函数返回值>0,那么b 将排在a 的前面;
以代码简单说明下
arr = [6,2,0,5,4,3,1]
arr.sort((a,b) => a-b) //[0, 1, 2, 3, 4, 5, 6],升序
arr1 = [6,2,0,5,4,3,1]
arr1.sort((a,b) => b-a) //[6, 5, 4, 3, 2, 1, 0],降序简单说一下我对上面代码的理解。第一段代码比较函数返回a-b,若返回值大于0,则表示a>b,排序后的数组b排在a的前面,小的在前大的在后,即升序排列
第二段代码比较函数返回b-a,假设返回值跟上面的一致,即返回值大于0,则表示b>a。排序后的数组b排在a的前面。还是"小"值在前"大"值在后,但此时小值和大值是调了个个。排在前面的b实际上是大值,即是降序排列
备注:
- 数组内部元素的排列会影响sort比较函数的循环次数
arr1 = [0,2,4,3,1]
count = 0
arr1.sort((a,b) => {
console.log(++count + '次');
console.log('\ta:'+a);
console.log('\tb:'+b);
return a-b
})上面函数的执行结果是
sort比较函数执行了8次,但如果数组元素按这样排列:[2,4,3,1,0],则sort内部会执行9次。这个应该涉及sort内部的排序算法。此处先不做了解
pop() 删除一个数组中的最后的一个元素
定义: pop() 方法删除数组中的最后的一个元素,并返回那个被删除的元素
shift() 删除数组的第一个元素
定义: shift()方法删除数组的第一个元素,并返回那个被删除的元素
push() 向数组的末尾添加元素
定义:push() 方法可向数组的末尾添加一个或多个元素,并返回新的数组长度
注意push可以接收多个参数,也可以接收使用扩展运算符传入的参数
ex:arr = []; arr.push(...[1,2,3]); //arr ==> [1,2,3]
unshift()向数组的头部添加元素
定义:unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度
和push一样也可以使用扩展运算符
reverse() 颠倒数组中元素的顺序
语法:let arr = [1,2,3]; arr.reverse() //[3,2,1]
返回值:倒序后的数组
ES6: copyWithin() 指定位置的成员覆盖其他位置
定义: 在当前数组内部,将指定位置的成员复制到其他位置。
返回值:处理后的数组
语法:
array.copyWithin(target, start = 0, end = this.length)
三个参数都是数值,如果不是,会自动转为数值.
- target(必需):替换数据的起始位置,即替换的数据包括该位置。支持使用负数表示
- start(可选):读取数据的起始位置,默认为 0。支持使用负数表示
- end(可选):到该位置前停止读取数据,即读取的数据不包括该索引的项。默认等于数组长度,支持使用负数表示
[1, 2, 3, 4, 5].copyWithin(0, -2, -1) //[4, 2, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(2) //[1, 2, 1, 2, 3],从数组第三位开始替换,默认从0开始读取,一直往下读取,直至读取回数组的长度便不再读取,即不会增加数组长度备注:
- 数组的长度不会改变
ES6: fill() 填充数组
定义:使用给定值,填充一个数组
返回值:被填充后的数组
参数:
- 第一个元素(必须): 要填充数组的值,不支持使用扩展运算符
- 第二个元素(可选): 填充的开始位置,默认值为0,支持负数表示
- 第三个元素(可选):填充的结束位置,填充值不包括此项。默认是为
this.length
['a', 'b', 'c'].fill(7) // [7, 7, 7]
['a', 'b', 'c'].fill(7, 1, 2) // ['a', 7, 'c']
备注:
- 只能指定一个值填充进数组,不可指定多个
不改变原数组的方法(8个)
slice() 浅拷贝数组的元素
定义:浅拷贝指定的数组元素。(浅拷贝,即如果是引用类型,则只是复制了一份指向引用类型数据的地址,实际还是指向同一个引用类型)
返回值:由指定数组元素组成的新数组
语法:
arr.slice(begin = 0, end = arr.length)参数:- begin。索引数值,接受负数。从此处开始拷贝。默认值0
- end。索引数值,接受负数。在此处的前一个位置处结束拷贝。默认值数组长度
备注:
- 字符串也有个slice方法,且接手参数和数组的一致,也都可以支持负数
- 若是给定的end > start,则返回空数组,不会报错
join() 数组转字符串
定义:join() 方法用于把数组中的所有元素(不支持自定义需要拼接的元素)通过指定的分隔符进行分隔放入一个字符串
返回值:新生成的字符串
语法:
array.join(separator)
参数:
separator(可选): 指定要使用的分隔符,默认使用逗号作为分隔符
备注:
- 调用数组的toString方法返回值和调用数据的join方法的返回值一致
arr = [1, 'adas', [1,2,'24',[1,2,{}]], {}]
arr.toString()
arr.join() //不传参数
生成:
"1,adas,1,2,24,1,2,[object Object],[object Object]"可见即使数组中任嵌有数组,toString和join也会将内部数组中的元素一个个抽取出来以逗号连接,如果遇到的是object,则生成"[object Object]"
2. join可以以指定的连接符将数组拼接成字符串,相对地,字符串有个可以指定切割符将字符串切割为数组的方法split
toLocaleString() 数组转字符串
定义:返回一个表示数组元素的字符串。该字符串由数组中的每个元素的 toLocaleString() 返回值经调用 join() 方法连接(由逗号隔开)组成
语法:
arr.toLocaleString()返回值:字符串
参数:无
EX:
let a=[{name:'OBKoro1'},23,'abcd',new Date()];
let str=a.toLocaleString(); // [object Object],23,abcd,2018/5/28 下午1:52:20 如上述栗子:调用数组的toLocaleString方法,数组中的每个元素都会调用自身的toLocaleString方法,对象调用对象的toLocaleString,Date调用Date的toLocaleString。
toLocaleString我也不熟,关于toLocaleString方法可以参考这篇文章:
toLocaleString主要可以用于便捷地格式化数字和日期对象。
toString() 数组转字符串 不推荐
定义:toString() 方法可把数组转换为由逗号链接起来的字符串
返回值:生成的字符串
语法:
array.toString()
参数:无
备注:
- 当数组和字符串操作的时候,js 会调用这个方法将数组自动转换成字符串
let a= ['调用toString','连接在我后面']+'啦啦啦'; // 调用toString,连接在我后面啦啦啦
此处应该是JS解释器自动为数组调用了Array.prototype.toString函数
该方法的效果和join方法一样,都是用于数组转字符串的,但是与join方法相比没有优势,也不能自定义字符串的分隔符,因此不推荐使用。
上面有个toLocaleString,那么toString和toLocaleString有什么区别?还有一个和toString类似的valueOf,这三者之间的区别可以参考:
Javascript toString()、toLocaleString()、valueOf()三个方法的区别
其中valueOf区别于另外两个,其返回的是对象的原始值,即返回值数据类型和包装对象数据类型一致:
bol = true;
typeof(bol.valueOf()); //"boolean"
cancat合并数组
定义: 用于合并两个或多个数组
返回值:合并后的新数组
语法:
var newArr =oldArray.concat(arrayX,arrayX,......,arrayX)
可以使用扩展运算符
b=[];
a=[[1,2,3],[4,5,6]]
b.concat(...a) //[1,2,3,4,5,6]
参数:
- arrayX(必须):该参数可以是具体的值,也可以是数组对象。可以是任意多个
eg1:
let a = [1, 2, 3];
let b = [4, 5, 6];
//连接两个数组
let newVal=a.concat(b); // [1,2,3,4,5,6]
// 连接三个数组
let c = [7, 8, 9]
let newVal2 = a.concat(b, c); // [1,2,3,4,5,6,7,8,9]
// 添加元素
let newVal3 = a.concat('添加元素',b, c,'再加一个');
// [1,2,3,"添加元素",4,5,6,7,8,9,"再加一个"]
// 合并嵌套数组 会浅拷贝嵌套数组
let d = [1,2 ];
let f = [3,[4]];
let newVal4 = d.concat(f); // [1,2,3,[4]]除concat外也可以使用扩展运算符合并数组,且相比concat更简洁且可以自定义
数组元素的位置
indexOf() 查找数组是否存在某个元素,返回下标
定义:返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1
返回值:第一个找到的给定元素的索引或-1。数字类型
语法:
array.indexOf(searchElement,fromIndex)searchElement(必须):被查找的元素。不支持正则
fromIndex(可选):开始查找的位置(不能大于等于数组的长度,返回-1),接受负值,默认值为0。indexOf会从改索引处查找,即查找范围包括了该索引。指定了该值并不会改变返回的索引值
eg:
a = [1,2,3,4,5]
a.indexOf(3, 2) //2
a.indexOf(3, 3) //-1
备注:
- 数组的indexOf使用严格相等
===搜索元素,即数组元素要完全匹配才能搜索成功。所以indexOf()不能识别NaN
NaN === NaN //falselastIndexOf() 查找指定元素在数组中的最后一个位置
定义:从数组后面往前查找指定元素出现的第一个位置,即指定元素在数组中最后出现的位置
返回值:若数组中存在指定元素,返回索引,不存在返-1。数字类型
语法:参数同indexOf
eg:
a = [1,2,3,4,5]
a.lastIndexOf(3, -3) //2
a.lastIndexOf(3, -4) //-1备注:
- lastIndexOf()不能识别NaN
关于fromIndex有两个规则:
- 正值。如果该值大于或等于数组的长度,则整个数组会被查找。如果是indexOf则会返回-1
- 负值。其绝对值大于数组长度,则方法返回 -1,即数组不会被查找。如果是indexOf则会查找整个数组。真是奇怪的行为。。
a = [1,2,3,4,5]
a.lastIndexOf(3, -9) //-1
a.lastIndexOf(3, 9) //2
a.indexOf(3, 9) //-1
a.indexOf(3, -9) //2仅作了解
ES7 includes() 查找数组是否包含某个元素 返回布值
返回值:布尔值,表示某个数组是否包含给定的值
语法:
array.includes(searchElement,fromIndex=0)参数:
searchElement(必须):被查找的元素,其他规则同上
fromIndex(可选):默认值为0,参数表示搜索的起始位置,接受负值。正值超过数组长度,数组不会被搜索,返回false。负值绝对值超过长数组度,重置从0开始搜索。行为同indexOf
includes可弥补indexOf的两个不足的地方:
- indexOf方法使用全等号判断,不能识别
NaN - indexOf方法检查是否包含某个值不够语义化,需要判断是否不等于
-1,表达不够直观
遍历方法(12个)
js中遍历数组并不会改变原始数组的方法总共有12个:
ES5:
forEach、every 、some、 filter、map、reduce、reduceRight、
ES6:
find、findIndex、keys、values、entries
不返回新数组:
forEach,every,some,reduce,reduceRight,find,findIndex
返回新数组:
map,filter,keys,values,entries
关于遍历:
- 关于遍历的效率,可以看一下这篇详解JS遍历
- 尽量不要在遍历的时候,修改后面要遍历的值
- 尽量不要在遍历的时候修改数组的长度(删除/添加)
forEach
定义: 按升序为数组中含有效值的每一项执行一次回调函数。
语法:
array.forEach(function(currentValue, index, arr), thisValue)
参数:
function(必须):数组中每个元素需要调用的函数。
// 回调函数的参数
1. currentValue(必须),数组当前元素的值
2. index(可选), 当前元素的索引值
3. arr(可选),数组对象本身
thisValue(可选):当执行回调函数时this绑定对象的值,浏览器中默认是Window
关于forEach()你要知道:
- 无法中途退出循环,只能用
return退出本次回调,进行下一次回调。 - 它总是返回 undefined值,即使你return了一个值。
下面类似语法同样适用这些规则
1. 对于空数组是不会执行回调函数的。跳过孔,对孔不执行回调
2. 对于已在迭代过程中删除的元素,或者空元素会跳过回调函数
3. 遍历次数在第一次循环前就会确定,再添加到数组中的元素不会被遍历。
4. 如果已经存在的值被改变,则传递给 callback 的值是遍历到他们那一刻的值。关于第三点贴一段代码说明一下:
a = [1, 2, 3, 4]
n = 1
a.forEach((item,i) => {
if(i==2) a.length = 10;
console.log(n++);
})
1,2,3,4every 检测数组所有元素应用于指定的回调函数是否都符合判断条件
定义: 方法用于检测数组所有元素是否都符合函数定义的条件
参数及参数用法同上
方法返回值规则:
- 如果数组中检测到有一个元素不满足,则整个表达式返回 false,且剩余的元素不会再进行检测,类似短路逻辑
- 如果所有元素都满足条件,则返回 true
eg:
arr = [1,2,3,4,5]
arr.every((item) => item < 6); //true
arr.every(() => true); //truesome 数组中的是否有满足判断条件的元素
定义:数组中的是否有满足判断条件的元素
参数及参数用法同上
方法返回值规则:
如果有一个元素满足条件,则表达式返回true, 剩余的元素不会再执行检测
如果没有满足条件的元素,则返回false
filter 过滤原始数组,返回新数组
返回值:由符合回调函数中指定的筛选条件(及将指定元素应用于数组后回调函数返回true)的数组元素组成的新数组。若没有,则返回孔数组
参数:同上
eg:
arr = [1,2,3,"a","b",4];
arr.filter(item => typeof(item) === "string"); //["a", "b"]
arr.filter(() => true); //[1,2,3,"a","b",4]
arr.fliter(() => {false}); //[]map 对数组中的每个元素进行处理,返回新的数组
返回值:由指定的回调函数的返回值组成的新数组(跟原数组中的元素没任何关联关系)
参数:同上
eg:
arr = [1,2,3]
arr.map(() => 1); [1,1,1]ES6:find()& findIndex() 根据条件找到数组成员并返回数组成员
find()定义:用于找出第一个符合条件的数组成员,并返回该成员,如果没有符合条件的成员,则返回undefined。
findIndex()定义:返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
返回值:
- find返回找到的第一个回调函数返回为true的元素或返回undefiend
- findIndex返回找到的第一个回调函数返回为true的函数的下标或者-1
a = [1,2,3]
a.findIndex(() => true) //0
a.find(() => true) //参数:同上
备注:
这两个方法都可以识别
NaN,弥补了indexOf的不足.
ES6 keys()&values()&entries() 遍历键名、遍历键值、遍历键名+键值
定义:三个方法都返回一个新的 Array Iterator 对象,对象根据方法不同包含不同的值语法:
array.keys()
array.values()
array.entries()
参数:无
对于Array Iterator 对象,我并不熟悉,具体的相关规则以后再补充。在chrome中我用一般for循环,map,forEach都无法遍历生成的Array Iterator对象。这里介绍开头那篇文章介绍的两种遍历Array Iterator对象的方式
- for of循环
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"在for..of中如果遍历中途要退出,可以使用break退出循环。
2. 手动调用遍历器对象的next方法,进行遍历
let letter = ['a', 'b', 'c'];
let entries = letter.entries();
console.log(entries.next().value); // [0, 'a']
console.log(entries.next().value); // [1, 'b']
console.log(entries.next().value); // [2, 'c']reduce 为数组提供累加器,合并为一个值
定义:reduce() 方法对累加器和数组中的每个元素(从左到右)应用一个函数,最终合并为一个值
返回值:最终的合并值
语法:
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)参数:
function(必须):数组中每个元素需要调用的函数。
// 回调函数的参数
1. total(必须),初始值, 或者上一次调用回调返回的值
2. currentValue(必须),数组当前元素的值
3. index(可选), 当前元素的索引值
4. arr(可选),数组对象本身
initialValue(可选):指定第一次回调 的第一个参数
回调第一次执行时:
- 如果 initialValue 在调用 reduce 时被提供,那么第一个 total 将等于 initialValue,此时 currentValue 等于数组中的第一个值;
- 如果 initialValue 未被提供,那么 total 等于数组中的第一个值,currentValue 等于数组中的第二个值。此时如果数组为空,那么将抛出 TypeError。
- 如果数组仅有一个元素,并且没有提供 initialValue,或提供了 initialValue 但数组为空,那么回调不会被执行,数组的唯一值将被返回。
根据上面,可见如果提供了initialValue,则会循环多一次
a = [1,2,3]
(function() //循环两次
n=0
a.reduce((total, cv) =>
console.log(`循环${++n}次`)
return total+c
})
})()
(function() //循环三次
n=0
a.reduce((total, cv) =>
console.log(`循环${++n}次`)
return total+cv
})
}, 0)()
// 将二维数组转化为一维 将数组元素展开
let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
(a, b) => a.concat(b),
[]
);
// [0, 1, 2, 3, 4, 5]reduceRight 从右至左累加
这个方法除了与reduce执行方向相反外,其他完全与其一致,请参考上述 reduce 方法介绍。
总结
对负数的支持
在需要支持负数的数组方法中都支持负数。何谓支持负数,即可在指定位置添加/删除数组元素的方法(splice),可复制指定位置数组元素覆盖到数组的其他位置(copyWithin),可从指定位置开始填充数组的(fill),可在指定位置浅拷贝数组元素的(slice),可在指定位置查找数组元素的indexOf/lastIndexOf/includes。这些场景都有一个共同点,即可以指定位置,可以指定位置的方法即支持负数,因为这样会使方法更灵活
对起始位置和终止位置的理解
js数组方法中参数包括起始位置和终止位置的一共有三个。copyWithin,fill,slice。他们对起始位置和终止位置的理解都是一致的,即从起始位置开始,自终止位置前一位结束,即范围不包括终止的那个索引