扩展运算符(spread)用三个点表示(...
),作用是将一个数组转为用逗号分隔的参数列表,是函数rest参数的逆向操作。
var array = [1,2];
console.log(...array);
上面的代码使用扩展运算符逐个输出数组中的元素。console.log(...array)
相当于console.log(array[0],array[1])
var array = [1,2];
function add(x,y){
return x+y;
}
add(...array);//3
add(...array)
相当于add(array[0],array[1])
扩展运算符也可以与函数的正确参数一起使用
var array = [1,2];
function add(unit,x,y,z){
return x+y+z+(unit);
}
add('元',...array,3);//6元
扩展运算符后面也可以放表达式
var flag=-1;
var array = [0,...(flag>=0?[1,2]:[-1,-2])];
console.log(...array);//0 -1 -2
上面的代码通过flag
决定array
后两位元素内容,在使用扩展运算符转换为参数列表传递给log
函数
如果扩展运算符作用于一个空数组,则不产生任何效果
var array=[1,...[]];
console.log(array);//[1]
注意:只有在调用函数,将数组转为参数列表时,扩展运算符才能放在圆括号里,其他情况下不能放在圆括号里。
(...[1, 2])
// Uncaught SyntaxError: Unexpected number
console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number
console.log(...[1, 2])
// 1 2
上面三种情况,都是将扩展运算符放在圆括号里,但是前两种会报错,因为扩展运算符所在的圆括号不是函数调用。
es5中如果想找出数字类型数组中最大元素,想将一个数组追加到另一个数组中,我们会使用函数的apply
方法这样写
//es5写法:找出数组中最大的元素
var array = [2,7,4,3];
var result = Math.max.apply(null,array);
console.log(result);
//es5写法:将一个数组追加到另一个数组中
var array = [2,7,4,3];
var tailArray = [11,22];
Array.prototype.push.apply(array,tailArray);
console.log(array);//[2, 7, 4, 3, 11, 22]
apply()
有两个参数,第一个是当前作用域,即this
对象;第二个参数是一个数组,数组中的元素会作为函数实例的入参,数组元素的位置与函数实例参数列表一一对应
es6使用扩展运算符的写法
var array = [2,7,4,3];
var result = Math.max(...array);
console.log(result);//7
var array = [2,7,4,3];
var tailArray = [11,22];
array.push(...tailArray);
console.log(array);//[2, 7, 4, 3, 11, 22]
数组是复合类型数据结构,把一个数组变量赋值给另一个数组变量,只不过是复制了指针,两个数组都指向一个内容,并没有达到克隆数组的目的
es5可通过使用数组concat()
方法变通实现
var a1 = [1,2];
var a2 = a1.concat();
console.log(a2);//false
es5中的扩展运算符实现
//方式一
var a1 = [1,2];
var a2 = [...a1]
方式二
var a1 = [1,2];
var [...a2] = a1;
console.log(a1===a2);
方式二其实是使用了数组的解构赋值+扩展运算符来实现的。上面的代码等同于
var a1 = [1,2];
var a2 = [];
([a2[0],a2[1]] = a1);
console.log(a1===a2);
注意:以上代码中写法都是对数组浅克隆,并不是深度克隆。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
扩展运算符可以与解构赋值结合起来,用于生成数组。
var first = null;
var rest = [];
[first,...rest] = [1,2,3];
console.log(first); //1
console.log(rest); //[2, 3]
const [first, ...rest] = [];
first // undefined
rest // []
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
上面的代码扩展运算符将rest
数组转为了列表的形式,即...rest
转为了rest[0],rest[1]
,所以等同于下面的代码
var first = null;
var rest = [];
[first,rest[0],rest[1]] = [1,2,3];
console.log(first); //1
console.log(rest); //[2, 3]
注意:扩展运算符在数组结构赋值中只能是最后一个参数,否则会报错
//都会报错
[...rest,last] = [1,2,3];
[first,...rest,last] = [1,2,3];
扩展运算符还可以将字符串转换为数组形式
console.log([...'Hello']);//["H", "e", "l", "l", "o"]
任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
上面代码中,querySelectorAll方法返回的是一个NodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator
扩展运算符的内部调用的是数据结构的Iterator 接口,因此只要具有Iterator 接口的对象,扩展运算符都能转换为数组
如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。
const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object
Map 和 Set 结构,Generator 函数相关知识还没有接触到,逐个知识点不总结了,回头再说
Array.from
方法有两个作用:1.将类似数组的对象转换为真正的数组;2.将实现了Iterator
接口的对象转换为数组对象。
Array.from(object,function,thisObject)
有三个参数:
object:需要转换的类似数组或实现了Iterator
接口的对象
function:对数组元素处理的函数,每个数组元素都会调用这个函数,返回值是数组最终的元素值。
thisObject:如果function
参数有this
关键字,可以指定this
的指向
let likeArrayObject = {
"0":"A",
"1":"B",
"2":"C",
"length":3
};
console.log(Array.from(likeArrayObject).indexOf("B"));//1
上面的例子是将一个类似数组对象转换为一个真正的数组,从而可以使用数组的丰富的API。
Iterator
接口的对象转换成数组let set = new Set(['A','B','C']);
console.log(Array.from(set).indexOf("B"));//1
let str = "Hellow";
console.log(Array.from(str).indexOf("o"));//4
以上代码Set
和String
类型都实现了Iterator
接口。
//获取所有class带有name的span元素
let spans = document.querySelectorAll('span.name');
//es5 使用 map() 实现
let names1 = Array.prototype.map.call(spans, s => s.textContent);
// es6 使用 Array.from() 实现
let names2 = Array.from(spans, s => s.textContent)
以上代码获取所有class带有name的span元素,Array.from
将spans转换为一个数组,在转换之前通过值处理函数对获取每个span元素的textContext内容并返回作为数组元素。
let object = {
toLowerCase:function(val){
return val.toLowerCase();
}
}
let set = new Set(['A','B','C']);
console.log(Array.from(
set,
function(itemValue){
return this.toLowerCase(itemValue);
},object
));
//["a", "b", "c"]
以上代码将set
对象中的元素转换为小写,在放入数组中,通过第三个参数指定了第二次参数函数中的this
指向,即第二次参数函数中的this
指向的是object
对象。
Array.of
方法用于将一组值转换为一个数组。此方法是new Array()
和Array()
方法的替代方法。
在es5中Array()
传递的参数个数不同,会导致不同的行为
console.log(Array());//[]
console.log(Array(3));//[,,]
console.log(Array('A','B'));["A", "B"]
不传递参数时,会返回一个length
为0的数组;传递一个参数时,会返回一个length
为3的空数组;传递两个以上的参数时,参数是数组中的元素。
Array.of
无论传递几个参数,行为都是统一的,都会将传递的参数形成一个数组。
console.log(Array.of());//[]
console.log(Array.of(3));//[3]
console.log(Array.of('A','B'));["A", "B"]
数组实例的copyWithin()
方法,在当前数组内部,将指定位置的成员复制到其它位置(会覆盖原有位置的成员),然后返回当前的数组。也就是说会修改当前数组。
copyWithin(target,start,end)
方法有三个参数:
target
:从该位置替换数据。如果是负值,表示从尾部开始计算。
start
:从该位置开始读取元素,默认为0。如果是负值从尾部开始计算。
end
:从该位置停止读取元素,默认为数组长度。如果是负值从尾部开始计算。
注意:target
和start
参数从0开始数,即这两个参数指定的是索引为;end
从1开始数,即指的是元素的位置;这个三个参数都应该是数字,否则将会强自动转换为数字。
//将4,5复制到1,2的位置
[1, 2, 3, 4, 5].copyWithin(0, 3)
//[4, 5, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(-5, -2)
//[4, 5, 3, 4, 5]
//将4复制到2的位置
console.log([1, 2, 3, 4, 5].copyWithin(1, 3, 4));
//[1, 4, 3, 4, 5]
console.log([1, 2, 3, 4, 5].copyWithin(-4, -2, -1));
//[1, 4, 3, 4, 5]
find
和findIndex
方法接收两个参数:
第一个参数:是一个会调用函数,用户查找数组中元素的逻辑,并返回一个Boolean值,true表示找到了数组中第一个符合条件的元素。回调函数会接受三个参数,分别是元素值、元素索引和当前数组对象。
第二个参数:第一个参数回调函数中有this指向的对象
注意:find
和findIndex
都会找到第一个符合条件的元素,前者返回这个元素的值,后者返回元素的索引。没有找到符合条件的元素,返回undefined
。
let array = [1, 2, -1, -2];
var value = array.find((item)=> item<0 );
console.log(value);//-1
var index = array.findIndex((item)=> item<0 );
console.log(index);//2
以上代码都是查询数组中大于零的元素,find
返回的是第一个符合条件的元素,而findIndex
返回的是元素的索引。
另外,这两个方法都可以发现NaN,弥补了数组的indexOf方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
上面代码中,indexOf
方法无法识别数组的NaN
成员,但是findIndex
方法可以借助Object.is
方法做到。
数组实例的fill(fillValue,start,end)
用于填充数组。此方法接受三个参数:
fillValue
:需要填充的值
start
:填充的起始位置。start只得是数组元素索引,从0开始数
end
:填充的结束位置。end指得是数组元素位置,从1开始数
注意:fill()
会修改原有数组
let array = [1, 2, 3, 4, 5];
console.log(array.fill(-1));//[-1, -1, -1, -1, -1]
console.log(array);//[-1, -1, -1, -1, -1]
以上代码是将数组array
所有元素填充成-1,很适合初始化数组中元素为某个值。
//最后两个元素填充为-1
let array = [1, 2, 3, 4, 5];
console.log(array.fill(-1,3));//[1, 2, 3, -1, -1]
//2,3元素填充为-1
array = [1, 2, 3, 4, 5];
console.log(array.fill(-1,1,3));//[1, -1, -1, 4, 5]
以上代码使用了fill()
方法start
和end
参数将填充数组中得部分元素为-1
let array = [1, 2, 3, 4, 5];
console.log(array.fill({name:"zhangxy"}));
array[0].name='ZHANGXY';//数组所有元素对象的name属性值都为'ZHANGXY'
以上代码,使用对象填充了一个数组,注意填充的对象都是浅复制,数组的元素值都是指向{name:"zhangxy"}
的指针
数组实例的includes(value,start,end)
方法返回一个Boolean值,用于判断某个数组是否包含给定的值。此方法接受三个参数:
value
:用于判定是否包含的值
start
:从指定的start位置开始判断,start指的是元素的索引,从0开始数
end
:到指定的end位置结束判断,end指的是元素的位置,从1开始数
let array = [1, 2, 3, NaN];
//array对象是否包含1
console.log(array.includes(1)); //true
//array对象1到array.length范围内是否包含1
console.log(array.includes(1,1));//false
//array对象0到1范围内是否包含1
console.log(array.includes(1,0,1));//true
includes()
主要是替代indexOf()
,indexOf()
有两个缺点:1.语义不明确,indexOf
是查找元素在数组中的位置,没有查找到会返回-1,还要用严格等于(===
)判断indexOf
返回值是否等于-1;2. 不能在数组中查找NaN
值。
//此方法也可以用于判断数组中是否包含NaN
console.log([NaN].includes(NaN)); //true
//返回-1说明查找不到NaN的index(明明存在数组中)
console.log([NaN].indexOf(NaN)); //-1
数组实例的flat()
用于将多位数组拉平为一个一维数组。不改变原有数组,会返回一个新的数组。此方法接受一个数字类型参数,表示拉平多维数组的深度;不传递参数默认为1;无限拉平多位数组传递参数Infinity
。
let array = [1,2,[3,4]];
console.log(array.flat());// [1, 2, 3, 4]
以上代码数组中还有个数组,flat()
将子数组取出来,添加到原来的位置。
let array = [1,2,[3,4,[5,6]]];
console.log(array.flat(2));// [1, 2, 3, 4, [5, 6]]
console.log(array.flat(2));// [1, 2, 3, 4, 5, 6]
console.log(array.flat(Infinity));// [1, 2, 3, 4, 5, 6]
以上代码有个三维数组,通过flat(2)
或flat(Infinity)
将array
拉平为一维数组。
数组实例的flatMap()
方法,为每个元素调用一个回调函数,入参为当前元素,返回值为新数组的元素,然后对新数组的每个元素执行flat()
。此方法接受两个参数:
第一个参数:每个元素执行的回调函数,该函数可以接受三个参数,分别是当前数组成员、当前数组成员的位置(从零开始)、原数组。
第二个参数:函数中this
指向的对象。
let array = [1,2,3];
console.log(array.flatMap(x=>[x,x*2]));//[1, 2, 2, 4, 3, 6]
//上面的代码相当于
let newArray = array.map(x=>[x,x*2]);
console.log(newArray);//[[1, 2], [2, 4], [3, 6]]
console.log(newArray.flat());//[1, 2, 2, 4, 3, 6]
以上的代码,flatMap()
传入的回调函数将会返回一个数组,作为新数组中的元素,即最终会返回一个二维数组,flatMap
会将这个二维数组拉平为一个一维数组。
注意:flatMap
的拉平深度为1,且不能指定。
数组的空位指的是数组某个位置没有任何值。
[1,2,]//index为2的位置是空位
[,]//index为0,1的位置是空位
Array(3);//[,,] //使用Array创建3个空位的数组
注意:空位不是值是undefined或null,而是索引位置上什么也没有。
let array = [,];
//1 数组中的空位是算作length的
console.log(array.length);
//0: undefined 1: undefined 这里返回undefined并不是返回index对应的元素值,而中array["0"]找不到0属性
console.log("0:",array[0],"1:",array[1]);
//false 说明array不存在属性名称为0的属性
console.log(0 in array);
//true 说明array中元素是undefined或null还是有0属性的
console.log(0 in [undefined]);
console.log(0 in [null]);
ES5 对空位的处理,已经很不一致了,大多数情况下会忽略空位。
forEach()
, filter()
, reduce()
, every()
和some()
都会跳过空位。map()
会跳过空位,但会保留这个值join()
和toString()
会将空位视为undefined,而undefined和null会被处理成空字符串。ES6 则是明确将空位转为undefined
Array.from
方法会将数组的空位,转为undefined
...
)也会将空位转为undefined
copyWithin()
会连空位一起拷贝fill()
会将空位视为正常的数组位置,并进行填充值for...of
循环也会遍历空位entries()
、keys()
、values()
、find()
和findIndex()
会将空位处理成undefined
需要get到的经验:开发中少尼玛的给数组留下空位,制造不必要的麻烦和歧义
排序稳定性(stable sorting)是排序算法的重要属性,指的是排序关键字相同的项目,排序前后的顺序不变。
const arr = [
'peach',
'straw',
'apple',
'spork'
];
const stableSorting = (s1, s2) => {
if (s1[0] < s2[0]) return -1;
return 1;
};
arr.sort(stableSorting)
// ["apple", "peach", "straw", "spork"]
上面代码对数组arr按照首字母进行排序。排序结果中,straw在spork的前面,跟原始顺序一致,所以排序算法stableSorting是稳定排序。
const unstableSorting = (s1, s2) => {
if (s1[0] <= s2[0]) return -1;
return 1;
};
arr.sort(unstableSorting)
// ["apple", "peach", "spork", "straw"]
上面代码中,排序结果是spork在straw前面,跟原始顺序相反,所以排序算法unstableSorting
是不稳定的。
早先的 ECMAScript 没有规定,Array.prototype.sort()
的默认排序算法是否稳定,留给浏览器自己决定,这导致某些实现是不稳定的。ES2019 明确规定,Array.prototype.sort()
的默认排序算法必须稳定。这个规定已经做到了,现在 JavaScript 各个主要实现的默认排序算法都是稳定的。