一、Array

  除了Object,Array应该就是ECMAScript中最常用的类型了。ECMAScript数组跟其他编程语言的数组有很大区别。跟其他语言中的数组一样,ECMAScripte数组也是一组有序的数据,但跟其他语言不同的是,数组中每个槽位可以存储任意类型的数据。这意味着可以创建一个数组,它的第一个元素是字符串,第二个元素是数值,第三个是对象。ECMAScript数组也是动态大小的,会随着数据添加而自动增长。

 

(1)创建数组的几种方法

1、
let colors=new Array();
let colors=new Array(20);


2、
let colors=new Array(‘red','blue','green');

3、省略new
let colors=Array(3);
let names=Array(“Greg”);

4、数组字面量创建法
let colors=["red","blue","green"];       //创建一个包含3个元素的数组
let name=[];          //创建 一个空数组
let value=[1,2,]      //创建一个包含两个元素的数组

(2)Array.from(可以用来浅拷贝)和Array.of(重要)⭐⭐⭐

Array构造函数还有两个ES6新增的用于创建数组的静态方法:from()和of()。from()用于将类数组结构转换为数组实例,而of()用于将一组参数转换为数组实例。

  Array.from()第一个参数是一个类数组对象,即任何可迭代的结构,或者有一个length属性和可索引元素的结构。这种方式可用于很多场合:

//字符串会被拆分为单字符数组
console.log(Array.from('Matt'));        //['M','a','t','t']

//可以使用from()将集合和映射转换为一个新数组
const m=new Map().set(1,2),set(3,4);

const s=new Set().add(1).add(2).add(3).add(4);


console.log(Array.from(m));     //[[1,2],[3,4]]
console.log(Array.from(s));      //[1,2,3,4]


//Array.from()对现有数组执行浅复制
const a1=[1,2,3,4]
const a2=Array.from(a1);'

console.log(a1);               //[1,2,3,4]
alert(a1===a2);                //false

//可以使用任何可迭代对象
const iter={
  *[Symbol.iterator](){
      yield 1;
      yield 2;  
      yield 3;  
      yield 4;  
}  
};
console.log(Array.from(iter));      //[1,2,3,4]

//arguments对象可以被轻松转换为数组
function getArgsArray(){
  return Array.from(arguments);
}

console.log(getArgsArray(1,2,3,4));     //[1,2,3,4]

//from()也能转换带有必要属性的自定义对象
const arrayLikeObject={
  0:1,
  1:2,
  2:3,
  3:4,
  length:4
};
console.log(Array.from(arrayLikeObject));     //[1,2,3,4]


//from()也能转换带有必要属性的自定义对象
const arrayLikeObject={
  0:1,
  1:2,
  2:3,
  3:4,
  length:4
};
console.log(Array.from(arrayLikeObject));      //[1,2,3,4]

  Array.from()还能接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值,而无须像调用Array.from().map()那样先创建一个中间数组。还可以接收第三个可选参数,用于指定映射函数中的this的值。但这个重写的this值在箭头函数中不适用。

const a1=[1,2,3,4]
const a2=Array.from(a1,x=>x**2);
const a3=Array.from(a1,function(x){return x**this.exponent},{exponent:2})


console.log(a2);   //[1,4,9,16]
console.log(a3);   //[1,4,9,16]

  Array.of()可以把一组参数转换为数组。这个方法用于替代在ES6之前常用的Array.prototype.slice.call(arguments),一种异常笨拙的将arguments对象转换为数组的写法:

console.log(Array.of[1,2,3,4]);    //[1,2,3,4]
console.log(Array.of(undefined));   //[undefined]

简单来说Array.from将伪数组转换成真数组,还有浅拷贝的作用。Array.of能将参数转成数组。

二、数组空位

使用数组字面量初始化数组时,可以使用一串逗号来创建空位(hole)。ECMAScript会将逗号之间相应索引位置的值当成空位,ES6规范重新定义了该如何处理这些空位。

可以像下面这样创建一个空位数组:

const options=[,,,,,];              //创建包含5个元素的数组
console.log(options.length);    //5
console.log(options);              //[,,,,,]

ES6新增的方法和迭代器与早期ECMAScript版本中存在的方法行为不同。ES6新增方法普遍将这些空位当成存在的元素,只不过值为undefined;

const options=[1,,,,5];
for(const option of options){
  console.log(option===undefined);  
}
//false
//true
//true
//true
//false

const a=Array.from([,,,]);    //使用ES6的Array.from()创建的包含3个空位的数组

for(const val of a){
  alert(val ===undefined);  
}

//true
//true
//true

alert(Array.of(...[,,,]));        //[undefined,undefined,undefined]

for (const [index,value]) of options.entries()){
  alert(value);  
}
//1
//undefined
//undefined
//undefined
//5

ES6之前的方法则会忽略这个空位,但具体的行为也会因方法而异:

const options=[1,,,,5];

//map()会跳过空位置
console.log(options.map()=>6));        // [6,undefined,undefined,undefined,6]


//join()视空位置为空字符串
console.log(options.join('-'));          //"1----5"

注意:由于行为不一致和存在性能隐患,因此实践中避免使用数组空位。如果确实需要空位,则可以显式地用undefined值代替。

 三、数组索引

我们可以利用数组的length属性对数组内的元素进行增加或者删除,如下图所示

let colors=['red','blue','green'];
colors.length=4;
alert(colors[3]); //undefined


let colors=['red','blue','green'];      //创建一个包含3个字符串的数组
colors[colors.length]='black';         //添加一种颜色
colors[colors.length]='brown';        //再添加一种颜色




let colors=['red','blue','green'];         //创建一个包含3个字符串的数组
colors[99]='black';                          //添加一种颜色(位置99)
alert(colors.length);                        //100

数组最多可以包含4294967295个元素,这对于大多数编程任务应该足够了。如果尝试添加添加更多项,则会导致抛出错误。以这个最大值作为初始值创建数组,可能导致脚本运行时间过长的错误。

四、检测数组(判断是否为数组Array.isArray())

一个经典的ECMAScript问题是判断一个对象是不是数组。在只有一个网页(因而只有一个全局作用域的情况下),使用instanceof操作符就足够了。

但是,使用instanceof的问题是假定只有一个全局执行上下文。如果网页里有多个框架,则可能涉及两个不同的全局执行上下文,因此就会有两个不同版本的Array构造函数。如果要把数组从一个框架传给了另一个框架,则这个数组的构造函数将有别于在第二个框架内本地创建的数组。

为了解决这个问题,ECMAScript提供了Array.isArray()方法。这个方法的目的就是确定一个值是否为数组,而不用管他是在哪个全局执行上下文创建的。来看下面的例子:

if(Array.isArray(value)){

  //操作数组

}

const a=['foo','bar','baz','qux'];

//因为这些方法都返回迭代器,所以可以将它们的内容通过Array.from直接转换为数组实例。

const aKeys=Array.from(a.keys());
const aValues=Array.from(a.values());
const aEntries=Array.from(a.entries());


console.log(aKeys);          //[0,1,2,3]
console.log(aValues);        //['foo','bar','baz','qux']
console.log(aEntries);       //[[0,'foo'],[1,'bar'],[2,'baz'],[3,'qux']]

使用ES6的解构可以非常容易地在循环中拆分键/值对:

const a=['foo','bar','baz','qux']
for (const [idx,element] of a.entries()){
  alert(idx);
  alert(element);    
}

//0
//foo
//1
//bar
//2
//baz
//3
//qux

5、复制和填充方法⭐⭐⭐,fill()和copyWithin()

  ES6新增了两个方法:批量复制方法fill(),以及填充数组方法copyWithin()。这两个方法的函数签名类似,都需要指定既有数组实例上的一个范围,包含开始索引,不包含结束索引。使用这个方法创建的数组不能缩放。

  使用fill()方法可以向一个已有的数组中插入全部或者部分相同的值。开始索引用于指定开始填充的位置,他是可选的。如果不提供结束索引,则一直填充到数组末尾。负值索引从数组末尾开始计算,也可以将负索引想象成数组长度加上它得到的一个正索引:

const zeros=[0,0,0,0,0]

//用5填充整个数组
zeroes.fill(5);
console.log(zeroes)   //[5,5,5,5,5]
zeroes.fill(0);            //重置


//用6填充索引大于等于3的元素
zeroes.fill(6,3);
console.log(zeroes);        //[0,0,0,6,6]
zeroes.fill(0);                //重置


//用7填充索引大于等于1且小于3的元素
zeroes.fill(7,1,3);
console.log(zeroes);        //[0,7,7,0,0]
zeroes.fill(0);                //重置

//用8填充索引大于等于1且小于4的元素
//(-4+zeroes.length=1)
//(-1+zeroes.length=4)
zeroes.fill(8,-4,-1);
console.log(zeroes);    //[0,8,8,8,0]

fill()静默忽略超出数组边界、零长度及方向相反的索引范围:

const zeroes=[0,0,0,0,0];
//索引过低,忽略
zeroes.fill(1,-10,-6);
console.log(zeroes);     //[0,0,0,0,0]

//索引过高,忽略
zeroes.fill(1,10,15);
console.log(zeroes);     //[0,0,0,0,0]

//索引反向,忽略
zeroes.fill(2,4,2);
console.log(zeroes);     //[0,0,0,0,0]

//索引部分可用,填充可用部分
zeroes.fill(4,3,10)
console.log(zeroes); [0,0,0,4,4]

与fill()不同,copyWithin()会按照指定范围浅复制数组中的部分内容,然后将它们插入到指定索引开始的位置。开始索引和结束索引则与fill()使用同样的计算方法:

let ints,reset=()=>ints=[1,2,3,4,5,6,7,8,9];
reset();

//从ints中复制索引0开始的内容,插入到索引5开始的位置
//从源索引或目标索引到达数组边界时停止
ints.copyWithin(5);
console.log(ints);        //[0,1,2,3,4,0,1,2,3,4]


//从ints中复制索引5开始的内容,插入到索引0开始的位置
ints.copyWithin(0,5);
console.log(ints)    //[5,6,7,8,9,5,6,7,8,9]
reset();

//从ints中复制索引0开始到索引3结束的内容
//插入到索引4开始的位置
ints.copyWithin(4,0,3);
alert(ints);      //[0,1,2,3,0,1,2,7,8,9]
reset();

//JavaScript引擎在插值前会完整复制范围内的值
//因此复制期间不存在重写的风险
ints.copyWithin(2,0,6);
alert(ints);    //[0,1,0,1,2,3,4,5,8,9]
reset();


//支持负索引值,与fill()相对于数组末尾计算正向索引的过程是一样的
ints.copyWithin(-4,-7,-3);
alert(ints);         //[0,1,2,3,4,5,3,4,5,6]

copyWithin()静默忽略超出数组边界、零长度及方向相反的索引范围:
let ints,reset=()=>ints=[0,1,2,3,4,5,6,7,8,9];
reset();


//索引过低,忽略
ints.copyWithin(1,-15,-12);
alert(ints);    //[0,1,2,3,4,5,6,7,8,9];
reset()


//索引过高,忽略
ints.copyWithin(1,12,15);
alert(ints);        //[0,1,2,3,4,5,6,7,8,9]


//索引反向,忽略
ints.copyWithin(2,4,2);
alert(ints);       //[0,1,2,3,4,5,6,7,8,9];
reset()


//索引部分可用,复制、填充可用部分
ints.copyWithin(4,7,10)
alert(ints);    //[0,1,2,3,7,8,9,7,8,9]

6.转换方法toLocaleString()、toString()和valueOf()方法⭐⭐⭐

前面提到过,所有对象都有toLocaleString()、toString()和valueOf()方法。其中,valueOf()

返回的还是数组本身。而toString()返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。也就是说,对数组的每个值都会调用其toString()方法,以得到最终的字符串。来看下面的例子:

let colors=['red','blue','green'];    //创建一个包含3个字符串的数组
alert(colors.toString());    //red,blue,green
alert(colors.valueOf());    //red,blue,green
alert(colors);                  //red,blue,green

首先是被显示调用的toString()和valueOf()方法,它们分别返回了数组的字符串表示,即将所有字符串组合起来,以逗号分割。最后一行代码直接用alert()显示数组,因为alert()期待字符串,所以会在后台调用数组的toString()方法,从而得到跟前面一样的结果。

  toLocaleString()方法也可能返回跟toString()方法和valueOf()相同的结果,但也不一定,在调用数组的toLocaleString()方法时,会得到一个逗号分割的数组值的字符串。它与另外两个唯一的区别是,为了得到最终的字符串,会调用数组每个值的toLocaleString()方法,而不是toString()方法。看下面的例子:

let person1={
  toLocaleString(){
     return 'Nikolaos';      
},
  tostring(){
     return'Nocholas';  
}  
};

let person2={
  toLocaleString(){
     return 'Grigorios';    
    },
  toString(){
     return 'Greg';    
}               
};


let people=[person1,person2]
alert(people);    //Nicholas,Greg
alert(people.toString());        //Nicholas,Greg
alert(people.toLocaleString());         //Nikolaos,Grigorios

这里定义了两个对象person1和person2,它们都定义了toString()和toLocaleString()方法,而且返回不同的值。然后又创建了一个包含这两个对象的数组people。在将数组传给alert()时,输出的是’Nicholas,Greg‘,这是因为会在数组每一项上调用toString()方法(与下一行显示调用toString()方法结果一样)。而在调用数组的toLocaleString()方法时,结果变成了’Nikolaos,Grigorios‘,这是因为调用了数组每一项的toLocaleString()方法。

  继承的方法toLocaleString()以及toString()都返回数组值的逗号分隔的字符串。如果想使用不同的分隔符,则可以使用join()方法。join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串。来看下面的例子:

let colors=['red','green','blue'];

alert(colors.join(','));     //red,green,blue

alert(colors.join('||'));      //red||green||blue
这里再colors数组上调用了join()方法,得到了与调用toString()方法相同 的结果。传入逗号,结果就是逗号分隔的字符串。最后一行给join()传入了双竖线,得到了字符串’red||green||blue'.如果不给join()传入任何参数,或者传入undefined,则仍然使用逗号作为分隔符。
注意:
如果数组中某一项是null或undefined,则在join()、toLocaleString()、toString()和valueOf()返回的结果会以空字符串表示。

7.栈方法

let colors=new Array();        //创建一个数组
let count=colors.push("red","green");    //推入两项
alert(count);


count=colors.push("black");    //再推入一项
alert(count);            //3


let item=colors.pop();    //取得最后一项
alert(item);                    //black
alert(colors.length)            //2

栈方法还可以与数组的其他任何方法一起使用

let colors=['red','blue']
colors.push('brown');        //再添加一项
colors[3]='black';            //添加一项
alert(colors.length)        //4


let item=colors.pop();      //取得最后一项    
alert(item)    //black

8.队列方法

因为有了在数组末尾添加数据的push()方法,所以要模拟队列就差一个从数组开头取得数据的方法了。这个数组方法叫shift(),他会删除数组的第一项并返回它,然后数组长度减1.使用shift()和push(),可以把数组当成队列来使用:

let colors=new Array();
let count=colors.push("red","green");//推入两项

alert(count);

count=colors.push("black")   //再推入一项
alert(count);

let item=colors.shift();     //取得第一项
alert(item);                    //red
alert(colors)                   //2

这里,先创建一个数组,再通过unshift()填充数组。首先,给数组添加添加“red”和“green”,再添加“black”,得到[“black”,"red","green"]。调用pop()时,删除最后一项“green”并返回它

 

9.排序方法

数组有两个可以用来对元素重新排序:reverse()和sort()。顾名思义 ,reverse()方法就是将数组元素反向排序。比如:

let values=[1,2,3,4,5]
values.reverse();
alert(values);    //5,4,3,2,1

这里,数组values的初始状态为[1,2,3,4,5]。通过调用reverse()反向排序,得到了[5,4,3,2,1]。这个方法很直观,但不够灵活,所以才有了sort()方法。

  默认情况下,sort()会按照升序重新排列数组元素,即最小的值再前面,最大的值再后面。为此,sort()会在每一项调用String()转型函数,然后比较字符串来决定顺序。即使数组的元素都是数值。也会先把数组转换为字符串再比较、排序。比如:

let values=[0,1,5,10,15]
values.sort();
alert(values);    //0,1,10,15,5

一开始数组中数值的顺序是正确的,但调用sort()会按照这些数值的字符串形式重新排序。因此,即使5小于10,但字符串”10“在字符串”5“的前头,所以10还是会排到5前面。很明显,这在多数情况下都不是最合适的。为此,sort()方法可以接收一个比较函数,用于判断哪个值应该排在前面。

  比较函数接收两个参数,如果第一个参数应该排在第二个参数前面,就返回负值;如果两个参数相等,就返回0;如果第一个参数应该排在第二个参数后面,就返回正值。:

function compare(value1,value2){
  if(value1<value2){
       return -1;  
} else if (value1>value2){
       return 1;
}else{
       return 0;
}
}

这个函数可以比较大多数数据类型,可以把它当作参数传给sort方法。如下图所示:

let values=[0,1,5,10,15];
values.sort(compare);
alert(values);         //0,1,5,10,15

  在给sort()方法传入比较函数后,数组中的数值在排序后保持了正确的顺序。当然,比较函数也可以产生降序效果,只要把返回值交换一下即可:

function compare(value1,value2){
  if(value1<value2){
     return 1;  
} else if(value1 > value2){
     return -1;
}else{
     return 0;
}
}

let values=[0,1,5,10,15];
values.sort(compare);
alert(values);        //15.10,5,1,0

此外,这个比较函数还可以简写为一个箭头函数:

let values=[0,1,5,10,15];
values.sort((a,b)=>a<b?1:a>b?-1:0);
alert(values);       //15,10,5,1,0

注意:reverse()和sort()都返回调用它们的数组的引用。

如果数组的元素是数值,或者是其valueof()方法返回数值的对象(如Date对象),这个比较函数还可以写得更简单,因为这时可以直接用第二个值减去第一个值:

function compare(value1,value2){
  return value2-value1  
}

比较函数就是要返回小于0、0和大于0的数值,因此减法操作完全可以满足要求

 

10.操作方法(concat(),slice(),splice())

对于数组中的元素,我们有很多操作方法,比如,concat()方法可以在现有数组全部元素基础上创建一个新数组。它首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这个新构建的数组。如果传入一个或者多个数组,则concat()会把这些数组 的每一项都添加到结果数组。如果参数不是数组,则直接把它们添加到结果数组末尾。来看下面的例子:

let colors=['red','green','blue'];
let colors2=colors.concat('yellow',['black','brown']);

console.log(colors);        //['red','green','blue']
console.log(colors2);        //['red','green','blue','yellow','black','brown']

  这里先创建了一个包含3个值的数组colors,然后colors调用concat()方法,传入字符串’yellow‘和一个包含’black‘和’brown‘的数组。保存在colors2中的结果就是['red','green','blue','yellow','black','brown']。原数组colors保持不变。

  打平数组参数的行为可以重写,方法是参数数组上指定一个特殊的符号:Symbol.isConcatSpreadable。这个符号能偶阻止concat()打平参数数组。相反把这个值设置为true可以强制打平类数组对象:

let colors=['red','green','blue'];
let newColors=['black','brown'];
let moreNewColors={
  [Symbol.isConcatSpreadable]:true,
  length:2,
  0:"pink",
  1:"cyan"        
};

newColors[Symbol.isConcatSpreadable]=false;

//强制不打平数组
let colors2=colors.concat("yellow","newColors");

//强制打平类数组对象
let colors3=colors.concat(moreNewColors);

console.log(colors);        //["red","green","blue"]
console.log(colors2);      //["red","green","blue","yellow",["black","brown"]]

console.log(colors3);    //["red","green","blue","pink","cyan"]

接下来,方法slice()用于创建一个包含原有数组中的一个或者多个元素的新数组。slice()方法可以接收一个或两个参数;返回元素的开始索引和结束索引。如果只有一个参数,则slice()会返回该索引到数组末尾的所有元素。如果有两个参数,则slice()返回从开始索引到结束索引对应的所有元素,其中不包含结束索引对应的元素。记住,这个操作不影响原始数组。来看下面的例子:

let colors=["red","green","blue","yellow","purple"];
let colors2=colors.slice(1);
let colors3=colors.slice(1,4);

alert(colors2)     //green,blue,yellow,purple
alert(colors3)     //green,blue,yellow

注意:如果slice()参数有负值,那么就以数值长度加上这个负值的结果确定位置。比如,在包含5个元素的数组上调用slice(-2,-1),就相当于调用slice(3,4).如果结束位置小于开始位置,则返回空数组。

或许最强大的数组方法属splice()了,使用它的方式可以有很多种。splice()的主要目的是在数组中间插入元素,但有3种不同的方式使用这个方法。

①删除。需要给splice()传2个参数:要删除的第一个元素的位置和要删除的元素数量。可以从数组中删除任意多个元素,比如splice(0,2)会删除前两个元素。

②插入。需要给splice()传入3个参数:开始位置、0(要删除的元素数量)和要插入的元素,可以在数组中指定的位置插入元素。第三个参数之后还可以传第四个、第五个参数,乃至任意多个要插入的元素。比如,splice(2,0,”red“,”green“)会从数组位置2开始插入字符串”red“和”green“

③替换。splice()在删除元素的同时可以在指定位置插入新元素,同样要传入三个参数:开始位置、要删除的元素数量和要插入的任意多个元素。要插入的元素数量不一定跟删除的元素数量一致。比如,splice(2,1,”red“,”green“)会在位置2删除一个元素,然后从该位置开始向数组插入”red“和”green“。

splice()方法始终返回这样一个数组,它包含从数组中被删除的元素(如果没有删除元素,则返回空数组)。一下示例展示了上述3种使用方式。

let colors=['red','green','blue']
let removed=colors.splice(0,1);    //删除第一项
alert(colors);    //green,blue
alert(removed)    //red,只有一个元素的数组

removed=colors.splice(1,0,"yellow","orange");  //在位置1插入两个元素
alert(colors);     //green,yellow,orange,blue
alert(removed);   //空数组

removed=colors.splice(1,1,'red','purple');   //插入两个值,删除一个元素
alert(colors);     //green,red,purple,orange,blue
alert(removed);   //yellow,只有一个元素的数组

这个例子中,colors数组一开始包含了三个元素。第一次调用splice()时,只删除了第一项,colors中还有”green“和”blue“。第二次调用slice()时候,在位置1插入两项,然后colors包含”green“、”yellow“,”orange“和”blue“。这次没有删除任何项,因此返回空数组。最后一次调用splice()时删除了位置1上的一项,同时又插入了”red“和”purple“。最后。colors数组包含”green“,”red“、”purple“、”orange“和”blue“。

11.搜索和位置方法(indexOf(),lastIndexOf()和includes())。

ECMAScript提供了3个严格相等的搜索方法:indexOf()、lastIndexOf()和includes()。其中,前两个方法在所有版本中都可用,而第三个方法是ES7中新增的。这些方法都是接收两个参数:要查找的元素和一个可选的起始搜索位置。indexOf和includes方法从数组前头开始向后搜索,而lastIndexOf()从数组末尾开始向前搜索。

indexOf()和lastIndexOf()都返回要查找的元素在数组中的位置,如果没找到则返回-1.includes()返回布尔值,表示至少找到一个与指定元素匹配的项。在比较第一个参数跟数组每一项时候,会使用全等(===)比较,也就是说两项必须严格相等。来看下面例子:

let numbers=[1,2,3,4,5,4,3,2,1];
alert(numbers.indexOf(4));    //3
alert(numbers.lastIndexOf(4));    //5
alert(numbers.includes(4));    //true


alert(numbers.indexOf(4,4));    //5
alert(numbers.lastIndexOf(4,4));    //3
alert(numbers.includes(4,7));    //false


let person={name:"Nicholas"};
let people=[{name:"Nicholas"}];
let morePeople=[person];

alert(people.indexOf(person));    //-1
alert(morePeople.indexOf(person));   //0
alert(people.includes(person));    //false
alert(morePeople.includes(person))   //true