数组的扩展
   
    
    
    扩展运算符
   
    扩展运算符(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
上面三种情况,都是将扩展运算符放在圆括号里,但是前两种会报错,因为扩展运算符所在的圆括号不是函数调用。
    
    
    用途
   
- 代替函数的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]
- 复制数组
    数组是复合类型数据结构,把一个数组变量赋值给另一个数组变量,只不过是复制了指针,两个数组都指向一个内容,并没有达到克隆数组的目的
    
    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)接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
上面代码中,querySelectorAll方法返回的是一个NodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator
- 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
    
    的指向
   
- 将类似数组的对象转换成真正的数组
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
    
    接口。
   
- 获取所有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内容并返回作为数组元素。
   
- 通过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
    
    :从该位置停止读取元素,默认为数组长度。如果是负值从尾部开始计算。
    
    注意:
    
     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方法
   
    
     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()
   
    数组实例的
    
     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()
   
    数组实例的
    
     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()
 
 和t
 
 oString()
 
 会将空位视为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 各个主要实现的默认排序算法都是稳定的。
   
 
