数组的扩展

数组的扩展

扩展运算符

扩展运算符(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

上面三种情况,都是将扩展运算符放在圆括号里,但是前两种会报错,因为扩展运算符所在的圆括号不是函数调用。

用途

  1. 代替函数的apply方法

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]
  1. 复制数组

数组是复合类型数据结构,把一个数组变量赋值给另一个数组变量,只不过是复制了指针,两个数组都指向一个内容,并没有达到克隆数组的目的
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);

注意:以上代码中写法都是对数组浅克隆,并不是深度克隆。

  1. 合并数组
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' ]
  1. 扩展运算符与数组结构一起使用

扩展运算符可以与解构赋值结合起来,用于生成数组。

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];
  1. 字符串转数组

扩展运算符还可以将字符串转换为数组形式

console.log([...'Hello']);//["H", "e", "l", "l", "o"]
  1. 实现了 Iterator 接口的对象

任何定义了遍历器(Iterator)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

上面代码中,querySelectorAll方法返回的是一个NodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator

  1. Map 和 Set 结构,Generator 函数

扩展运算符的内部调用的是数据结构的Iterator 接口,因此只要具有Iterator 接口的对象,扩展运算符都能转换为数组
如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错。

const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object

Map 和 Set 结构,Generator 函数相关知识还没有接触到,逐个知识点不总结了,回头再说

Array.from

Array.from方法有两个作用:1.将类似数组的对象转换为真正的数组;2.将实现了Iterator接口的对象转换为数组对象。
Array.from(object,function,thisObject)有三个参数:
object:需要转换的类似数组或实现了Iterator接口的对象
function:对数组元素处理的函数,每个数组元素都会调用这个函数,返回值是数组最终的元素值。
thisObject:如果function参数有this关键字,可以指定this的指向

  1. 将类似数组的对象转换成真正的数组
let likeArrayObject = {
    "0":"A",
    "1":"B",
    "2":"C",
    "length":3
};
console.log(Array.from(likeArrayObject).indexOf("B"));//1

上面的例子是将一个类似数组对象转换为一个真正的数组,从而可以使用数组的丰富的API。

  1. 将实现了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

以上代码SetString类型都实现了Iterator接口。

  1. 获取所有span元素的内容,存储到一个数组中
//获取所有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内容并返回作为数组元素。

  1. 通过Array.from的第三个参数指定this
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

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()方法,在当前数组内部,将指定位置的成员复制到其它位置(会覆盖原有位置的成员),然后返回当前的数组。也就是说会修改当前数组。
copyWithin(target,start,end)方法有三个参数:
target:从该位置替换数据。如果是负值,表示从尾部开始计算。
start:从该位置开始读取元素,默认为0。如果是负值从尾部开始计算。
end:从该位置停止读取元素,默认为数组长度。如果是负值从尾部开始计算。
注意:targetstart参数从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方法

findfindIndex方法接收两个参数:
第一个参数:是一个会调用函数,用户查找数组中元素的逻辑,并返回一个Boolean值,true表示找到了数组中第一个符合条件的元素。回调函数会接受三个参数,分别是元素值、元素索引和当前数组对象。
第二个参数:第一个参数回调函数中有this指向的对象
注意:findfindIndex都会找到第一个符合条件的元素,前者返回这个元素的值,后者返回元素的索引。没有找到符合条件的元素,返回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()

数组实例的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()方法startend参数将填充数组中得部分元素为-1

let array = [1, 2, 3, 4, 5];
console.log(array.fill({name:"zhangxy"}));
array[0].name='ZHANGXY';//数组所有元素对象的name属性值都为'ZHANGXY'

以上代码,使用对象填充了一个数组,注意填充的对象都是浅复制,数组的元素值都是指向{name:"zhangxy"}的指针

数组实例的includes()

数组实例的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()、flatMap()

数组实例的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到的经验:开发中少尼玛的给数组留下空位,制造不必要的麻烦和歧义

Array.prototype.sort() 的排序稳定性

排序稳定性(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 各个主要实现的默认排序算法都是稳定的。

你可能感兴趣的:(ES6)