Array 数组扩展
扩展运算符
console.log(...[1,2,3]);
//1,2,3
扩展运算符会将后面跟着的数组转换为参数序列,这种效果一般用于函数参数,数组操作里面。
扩展运算符后面甚至可以接表达式,但是表达式一定要有数组抛出
const arr = [...(x>0?[1]:[]),2];
如果是一个空数组使用扩展运算符,则无任何效果
const a = [];
console.log([...a,1]);
//[1]
代替数组的apply方法
首先要知道apply除了改变this的指向外,第二个参数是一个数组,apply会将这个数组转为序列参数。
如:
function test(x,y,z){
...
};
const a = [1,2,3];
test.apply(null,a);
这样就可以将a数组转为参数传给函数了。
但是有了扩展运算符,就不用这样麻烦了。
function test(x,y,z){
...
};
const a = [1,2,3];
test(...a);
Math.max
//es5
Math.max.apply(null,[1,2,3]); //3
//es6
Math.max(...[1,2,3]); //3
数组push
在以前,如果一个a数组要push进来一个数组,这个push进来的数组要成为a数组的序列参数,那么只能这样
var a = [1,2,3];
var b = [4,5,6];
Array.prototype.push.apply(a,b); //[1,2,3,4,5,6]
用扩展运算符就简单很多了。
var a = [1,2,3];
var b = [4,5,6];
a.push(...b);
扩展运算符的应用
合并数组
//es5
[1,2].concat(more);
//es6
[1,2,...more];
多个数组合并也是相同。
与解构结合
[a,...more] = [1,2,3];
console.log(a,more);
//1
//[2,3]
数组的解构居然可以不用创建变量声明,不过还是建议给个const、let
[a,...more] = [];
console.log(a,more);
//undefined
//[]
可以看到,扩展运算符是创建一个数组,然后将对应的内容丢入,如果没有,他就是一个空数组。
如果我们将运算符放入解构中,则只能放入最后一位,因为他会将所有的内容都接收了,导致其他的接收不到。
const [...more,a] = [1,2,3]; //报错
const [a,...more,b] = [1,2,3]; //报错
函数返回值
函数只能返回一个值,如果有多个,则只能返回对象或者数组,如果是数组,我们就可以通过扩展运算符将其作为字符串参数了。
function test() {
return [2020,2];
}
var time = new Date(...test());
字符串
扩展运算符可以将字符串转为数组
[..."hello"];
//["h","e","l","l","o"]
使用这种方式转换有一个重要的好处,就是他能正确识别unicode32位字符,一般情况下,一个unicode16位的会被识别为一个字符,32就是两位,但是32往往是一些特殊的字符,比如中文的一些不常用的,复杂的文字,一些表情字符。
所以,一个准确的获取字符串长度的方法可以这样写:
function stringLength(string) {
return [...string].lenght;
}
为此我们还需要注意,但凡是操作32位的unicode字符所使用的字符串方法,都会将32位识别为两个字符,所以,我们要用扩展符进行改写,才能正确的操作。
let str = "x\Ud83D\uDE80y"; //一个32位字符
//错误的
str.split("").reverse().join("");
//"y\uDE80\uD83Dx"
//正确的
[...str]..reverse().join("");
//"x\Ud83D\uDE80y"
实现iterator对象
任何Iterator对象,都可以使用扩展运算符转为真正的数组对象。
let nodeList = document.querySelectorAll("div");
var array = [...nodeList];
nodeList 是一个类似数组的对象,这个对象有Iterator接口,所以扩展运算符就能将它转为真正的数组对象。
对于没有配置Iterator接口的对象,则无法进行转换
let arr = {
"0":"1",
"1":"2",
"2":"3",
length:3
}
let newArr = [...arr]; //报错
//TypeError: Cannot spread non-iterable object
arr是一个类似数组的对象,但是没有Iterator接口,我们无法通过扩展符转为数组,但是可以使用Array.from()
进行转换。
Map和Set结构、Generator函数
map对象有Iterator接口,所以我们可以将map对象转换为数组
let map = new Map([
[1,"a"],
[2,"b"],
[3,"c"],
])
let arr = [...map.keys()]; //["a","b","c"]
Generator函数
var go = function* () {
yield 1;
yield 2;
yield 3;
};
[...go()]; //[1,2,3]
Array.from
Array.from用于将类似数组对象转为真正数组,类似数组对象(array-like object)、可遍历对象(iterable、Set结构和Map)
类似数组对象
什么是类似数组对象?
从本质特征来讲,就是含有length属性的对象。因此只要含有length属性,就可以通过Array.from进行转换,而这种没有Symbol.iterator
接口的类似数组对象,扩展运算符就不能进行转换。
let arr = {
"0":"1",
"1":"2",
"2":"3",
length:3
}
//ES5写法
var arr1 = [].slice.call(arr); //["1","2","3"]
//ES6写法
var arr2 = Array.from(arr); //["1","2","3"]
实际应用中,类似数组对象经常是dom操作返回的NodeList节点集合,以及函数内部arguments对象,这些都可以使用该方法转为真正的数组。
只有转为真正的数组,才能使用数组遍历的方法forEach这些。
let arr = {
length:3
}
Array.from(arr); //[undefined,undefined,undefined]
因为没有这个对象没有下标和下标对应的值,所以转换后得到的是三个undefined的数组
let arr = {
"a":1,
"b":2,
"c":3
length:3
}
Array.from(arr); //[undefined,undefined,undefined]
这种也是一样,有值但是没有下标0123这些。
可遍历对象
只要转换的对象拥有Iterator接口,就可以进行转换,比如字符串和Set结构
Array.from("hello"); //["h","e","l","l","o"];
let nameSet = new Set(["a","b"]);
Array.from(nameSet); //["a","b"]
如果是一个真正的数组,Array.from会返回一个一模一样的新数组,这就是数组克隆。
let arr = [1,2];
Array.from(arr); // [1,2]
事实上扩展运算符也能做到这些,但是扩展运算符无法转换手动写的类似数组对象,因为扩展运算符实际上调用的是对象的遍历器接口Symbol.iterator
,如果没有这个接口就无法转换,而Array.from就更强一些,他可以转换这些扩展运算符无法转换的对象。
兼容性
如果浏览器不支持,我们可以使用es5的方式
const toArray = (()=>{
return Array.from ? Array.from : obj => [].slice.call(obj);
})();
Array.from的第二个参数
Array.from支持第二个参数,他是一个方法,效果等同于数组的map方法,他会将方法return出来的值传入数组保存,最后返回这个数组。
let arr = [1,2];
Array.from(arr,x=>x*x);
//等同于
arr.map(x=>x*x);
例子1:
let spans = document.querySelectorAll("span.name");
let names = Array.from(spans,s=>s.textContent);
拿到所有span的文本内容
例子2:
Array.from([1,"",2,"",3],n=>n||0);
//[1,0,2,0,3];
直接将空字符转换为0;
第三个参数 this关键字
如果在第二个参数,map函数中,使用到了this,我们可以使用第三个参数来指定this指向谁。
Array.from([1,"",2,"",3],n=>n||0,window);
//[1,0,2,0,3];
但这种情况比较少,目前也没啥例子。
其他
Array.from也可以识别32位的unicode字符,所以也可以拿来做一些操作,如:
function length(string) {
return Array.from(string).length;
}
一样可以用于获取文字的字符数。
Array.of
Array.of()方法用于将一组值转换为数组。因为Array()方法创建,他其实有一些行为差异,比如:
Array(2); //["",""]
Array(1,2); //[1,2]
在只有一个参数的时候,表示的是创建对应length数的数组,只有存在两个值得时候,才会创建对应的数组,为了弥补这个不足,有了Array.of方法。
Array.of非常稳定,他永远会返回一个参数组成的数组,如果没有参数,则返回一个空数组。
Array.of(1); //[1]
Array.of(1,2); //[1,2]
Array.of(undefined); //[undefined]
Array.copyWithin
Array.copyWithin是一个可以改变原数组的方法,他是对数组里面的内容进行copy覆盖的。
它支持三个参数:
- target,必填,从哪个位置开始,数组下标值
- start,从哪个位置开始进行复制,默认为0,如果是负值,就表示从后开始计算
- end,到该位置停止,默认等于数组的长度length,如果为负值,表示从后开始计算
截取的范围:从0开始计位,[1,2,3,4,5]
数组可以理解为:[0位1,1位2,2位3,4位5,5位]
如图所示,一个箭头为一个位。
[1,2,3,4,5].copyWithin(0,3,4);
//[4,2,3,4,5]
从0开始进行覆盖,从3位开始,也就是3后面起始,到4位结束,也就是5的左侧结束,中间选中的就是数字4,只有一个数字4,所以只有1被覆盖了,1成了4
[1,2,3,4,5].copyWithin(0,3,5);
//[4,5,3,4,5]
负值情况
[1,2,3,4,5].copyWithin(0,-2,-1);
//[4,2,3,4,5]
-2就是从4的左侧开始,-1就是5的左侧结束,那么中间只有4,从0开始替换,只替换了一个数字,1变成了4
[1,2,3,4,5].copyWithin(0,-5,-1);
//[1,2,3,4,5]
-5就是1左侧,-1为5左侧,选中了1,2,3,4,然后0位开始替换,替换了4 位,最后结果值是没有变化的,和原来一样顺序。
find()和findIndex()
find()
find()是用于找到第一个符合条件的数组成员,他接收一个回调函数作为参数,回调函数接收三个参数,分别为:当前的值,当前值的index,原数组。
当第一个return出true的时候,停止数组的遍历,并返回return的值,如果没有符合条件的,则返回undefined
[1,2,3,4].find(i=>i>3); //4
[1,2,3,4].find((item,index,arr)=>{
return item>3;
}); //4
findIndex()
findIndex()和find差不多,都是找到第一个符合条件的数组成员,只是findIndex是返回数组成员的下标位置。
[1,2,3,4].findIndex(i=>i>3); //3
如果没有则返回-1
[1,2,3,4].findIndex(i=>i>4); //-1
findIndex的回调函数也有三个参数,分别为:当前的值,当前值的index,原数组。
find和findIndex的第二个参数
第一个参数是回调函数,第二个参数是绑定回调函数的this指向。
发现NaN
旧的数组无法通过indexOf来发现NaN,使用find和findIndex可以发现NaN对象
[NaN].indexOf(NaN); //-1\
[NaN].findIndex(y=>{
return Object.is(NaN,y);
}); //0
数组的fill()
fill()用于给一个固定值,然后填充一个数组。
[1,2,3,4].fill("a"); //["a","a","a","a"]
["","",""].fill(1); //[1,1,1]
new Array(3).fill(2); //[2,2,2]
fill常常用于空数组初始化,数组中已有的值会被全部抹去。
fill还可以接收第二个参数和第三个参数,用于指定填充的位置。
[1,2,3,4].fill("a",2,3); //[1,2,"a",4]
位数可以参考copyWithin。
entries()、keys()、values()
es6新增了三个数组的遍历方法。
entries()
entries用于获取数组的键值对,也就是下标和值,他的结果类似下面这种:
["a","b","c"].entries(); //[[0,"a"],[1,"b"],[2,"c"]]
这个结果不是真正的显示结果,显示的是Array Iterator { }
,这个结果只能通过for--of的方法进行遍历处理
for(let arr of ["a","b","c"].entries()){
console.log(arr);
};
//[0,"a"]
//[1,"b"]
//[2,"c"]
这样可以得到对应的值,因为是个数组我们可以进行解构
for(let [index,value] of ["a","b","c"].entries()){
console.log(index,value);
};
//0,"a"
//1,"b"
//2,"c"
keys()
keys实际上获取的是数组的下标集合,但是他也不是直接返回一个数组,而是也要通过for--of方法遍历出来
for(let index of ["a","b","c"].keys()){
console.log(index);
};
//0
//1
//2
values()
values获取是数组的值,当然返回的也不是一个数组,所以也要用for--of方法遍历出来
for(let value of ["a","b","c"].values()){
console.log(value);
};
//"a"
//"b"
//"c"
手动遍历
如果我们不想使用for--of的方法,可以手动遍历
let values = ["a","b","c"].values();
console.log(values.next().value); //"a"
console.log(values.next().value); //"b"
console.log(values.next().value); //"c"
三种方法都可以使用这种手动遍历的方式。
includes()
includes用于判断数组里面是否包含给定的值,与字符串的includes方法相似。
[1,2,3].includes(2); //true
[1,2,3].includes(4); //false
[1,2,NaN].includes(NaN); //true
includes用于代替indexOf方法,因为indexOf从语义上讲是找到参数第一个出现的位置下标,而不是判断是否存在,表达起来不够直观,而且indexOf内部使用的是===
全等运算符,所以导致NaN无法正确判断是否存在。
includes则使用完全不一样的判断方法,就没有这个问题。
兼容性写法:
const contains = (()=>{
return Array.prototype.includes ? (arr,value)=> arr.includes(value) : (arr,value) => arr.some(el=>el===value)
})();
contains([1,2],2); //true
数组的空位
数组的空位是指数组中某一个位置没有任何值。比如通过Array构造函数返回的数组都是空位。
Array(3); //[, , ,]
空位不是undefined,undefined依然是有值的,空位是没有任何值。
0 in [undefined] //true
0 in [] //false
第一个in表明数组第一个位置是有值得,第二个表明没有值。
在es5中,对于空值的处理很不一致,es6中,要么保留空值,要么统一为undefined。
es5
- forEach(),filter(),every(),some()都会跳过空位
- map()会跳过空位,但是输出的值依旧会保留空位
- join()和toString()会将空位视为undefined,而undefined和null会被处理为空字符串
es6
- Array.from()会将空位视为undefined
- 扩展运算符也是将空位视为undefined
- copyWithin()会保留空位,会连同空位一起复制
- fill()会将空位视为正常的数组位置
- for...of循环也会遍历空位
- entries(),keys(),values(),find(),findIndex()会将空位处理成undefined
因为空位的处理规则非常不统一,所以建议避免出现空位。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据