集合引用类型
一、Object
Object是ECMAScript在最常用的类型之一,很适合存储和在应用程序间交换数据。
1.显示创建实例
Object构造函数
使用new操作符和Object构造函数创建实例。
代码如下(示例):
let person=new Object();
person.name="Nicholas";
person.age=29;
对象字面量
使用对象字面量表示法创建实例。在对象字面量中,属性名可以是字符串或数值。
代码如下(示例):
let person={
name:"Nicholas";
age:29;
}
2.存取方法
点语法
代码如下(示例):
console.log(person.name);
中括号
在使用中括号时,要在括号内使用属性名的字符串形式。
代码如下(示例):
console.log(person["name"]);
二、Array
ECMAScript数组也是一组有序的数据,数组中的每个槽位可以存储任意类型的数据。
1.创建数组
Array构造函数
Array构造函数可以传入一个数组,也可以传入要保存的元素,还可以省略new操作符。
代码如下(示例):
let colors=new Array();
let colors1=new Array(3);//创建一个只包含3个元素的数组
let colors2=new Array("red");//创建一个只包含1个元素,即字符串“red”的数组
let colors3=Array(3);//省略new
数组字面量
数组字面量是在中括号中包含以逗号分隔的元素列表。
代码如下(示例):
let colors=["red","green","blue"];
创建数组的静态方法
from()用于将类数组结构 转换为数组实例,而of()用于将一组参数 转换为实例。
Array.from()的第一个参数是一个类数组对象 ,即任何可迭代的结构,或者有一个length属性和可索引元素 的结构。
代码如下(示例):
//字符串会被拆分为单字符数组
console.log(Array.from("Matt"));
//使用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]
Array.of()可以把一组参数转化为数组。
代码如下(示例):
console.log(Array.of(1,2,3,4));//[1,2,3,4]
console.log(Array.of(undefined));//undefined
2.数组空位
使用数组字面量初始化数组时,可以使用一串逗号来创建空位。ECMAScript会将逗号之间相应索引位置的值当成空位。
代码如下(示例):
const options=[,,,,,];//创建包含5个元素的数组
console.log(options.length);//5
console.log(options);//[,,,,,]
3.数组索引
要去的或设置数组的值,需要使用中括号并提供相应值的数字索引。中括号中提供的索引表示要访问的值。如果索引小于数组包含的元素数,则返回存储在相应位置的元素。如果把一个值设置给超过数组最大索引的索引,则数组长度会自动扩展该索引值加1。
代码如下(示例):
let colors=["red","blue","green"];//定义一个字符串数组
alert(color[0]);//显示第一项
colors[2]="black";//修改第三项
colors[3]="brown";//增加第四项
通过修改length属性,可以从数组末尾删除或添加元素 。如果将length设置为大于数组元素数的值,则新添加的元素都将以undefined填充 。
代码如下(示例):
let colors=["red","blue","green"];//创建一个包含3个字符串的数组
colors.length=2;//修改长度为2
alert(colors[2]);//undefined
4.检测数组
在只有一个网页(只有一个全局作业域)的情况下 ,使用instanceof()判断一个对象是不是数组。
if(value instanceof Array){
//操作数组
}
Array.isArray()方法目的就是确定一个值是否为数组,而不管它是在哪个全局执行上下文中创建 。
if(Array.isArray(value)){
//操作数组
}
5.迭代器方法
Array有3个用于检索数组内容的方法:keys()、values()和entires()。keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而entries()返回索引/值对的迭代器。
代码如下(示例):
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"]]
6.复制和填充方法
ES6新增两个方法:批量复制方法copyWithin()、填充数组方法fill()。
fill()方法可以向一个已有的数组中插入全部或部分相同的值。负值索引从数组末尾开始计算,也可以将负索引想象成数组长度加上它得到的一个正索引。
代码如下(示例):
const zeroes=[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.log(0);//重置
//用7填充索引大于等于1且小于3的元素
zeroes.fill(7,1,3);
console.log(zeroes);//[0,7,7,0,0]
zeroes.log(0);//重置
//用8填充索引大于等于1且小于4的索引
zeroes.fill(8,-4,-1);//-4+5=1,-1+5=4
console.log(zeroes);//[0,8,8,8,0]
fill()静默忽略超出数组边界、零长度及方向相反的索引范围 。
copyWithin()按照指定范围浅复制数组中的部分内容,然后将它们插入到指定索引的开始的位置。
代码如下(示例):
let ints,
reset=()=>ints=[0,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];
reset();
//从ints中复制索引5开始的内容插入到索引0开始的位置
ints.copyWithin(0,5);//[5,6,7,8,9,5,6,7,8,9];
console.log(ints);
reset();
从ints中复制索引0开始到3的内容插入到索引4开始的位置
ints.copyWithin(4,0,3);//[0,1,2,3,0,1,2,7,8,9];
console.log(ints);
reset();
//
ints.copyWithin(2,0,6);//[0,1,0,1,2,3,4,5,8,9];
console.log(ints);
reset();
//负索引
ints.copyWithin(-4,-7,-3);//[0,1,2,3,4,5,3,4,5,6]
alert(ints);
copyWithin()静默忽略超出数组边界、零长度及方向相反的索引范围 。
7.转换方法
valueOf()返回的还是数组对象。toString()返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。toLocaleString()会返回一个逗号分隔的数组值的字符串,会调用数组每个值得toLocaleString()方法。
8.栈方法
栈是一种后进先出的结构。ECMAScript()数组提供了push()和pop()方法。push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的长度 。pop()方法则用于删除数组的最后一项,同时减少数组的length()值,返回被删除的项 。
代码如下(示例):
let colors=new Array();
let count=colors.push("red","green");
alert(count);//2
count=colors.push("black");
alert(count);//3
let item=colors.pop();
alert(item);//black
alert(colors.length);//2
9.队列方法
队列是一种先进先出的结构。使用shift()和push()可以把数组当成队列来使用。shift()会删除数组的第一项并返回 ,然后数组长度减1。unshift()在数组开头添加任意多个值,然后返回新的数组长度 。
代码如下(示例):
let colors=new Array();
let count=colors.push("red","green");
alert(count);//2
count=colors.push("black");
alert(count);//3
let item=colors.shift();
alter(item);//red
alter(colors.length);//2
10.排序方法
reverse()方法是将数组元素反向排列 。
代码如下(示例):
let values=[1,2,3,4,5];
values.reverse();
alert(values);//[5,4,3,2,1]
默认情况下 ,sort()会按照升序重新排列数组元素,即最小的值在前面,最大的值在后面。为此,sort()会在每一项上调用String()转型函数,任何比较字符串 来决定顺序。
代码如下(示例):
let values=[0,1,5,10,15];
values.sort();
alert(values);//0,1,10,15,5
sort()方法可以接收一个比较函数。比较函数接收两个参数,如果第一个参数应该排在第二个参数前面,就返回负值;如果两个参数相等,则返回0;如果第一个参数应该排在第二个参数后面,就返回正值。
代码如下(示例):
//降序
function compare1(value1,value2){
if(value1<value2){
return 1;
}else if(value1>value2){
return -1;
}else {
return 0;
}
}
//升序
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);//0,1,5,10,15
11.操作方法
concat()方法可以在现有数组全部元素基础上创建一个新数组。它首先会创建一个当前数组的副本,然后再把他的参数添加到副本末尾,最后返回这个新构建的数组 。
代码如下(示例):
let colors1=["red","greeb","blue"];
let colors2=colors1.concat("yellow",["black","brown"]);//参数数组打平
console.log(colors1);//["red","greeb","blue"]
console.log(colors2);//["red","greeb","blue","yellow","black","brown"]
Symbol.isConcatSpreadable能够阻止concat()打平参数数组。把这个值设置为true可以强制打平类数组对象 。
代码如下(示例):
let colors=["red","green","blue"];
let newColors=["black","brown"];
let moreNewColors={
[Symbel.isConcatSpreadable]:true;
length:2,
0:"pink";
1:"cyan";
};
newColors[Symbel.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()用于创建一个包含原有数组中一个或多个元素的新数组。
代码如下(示例):
let colors=["red","green","blue","yellow","purple"];
let colors2=colors.slice(1);
let colors3=colors.slice(1,4);
console.log(colors2);//green,blue,yellow,purple
console.log(colors3);//green,blue,yellow
splice()的主要目的是在数组中间插入元素,但有3种不同的方式使用这个方法:
- 删除:需要给splice()传2个参数,要删除的第一个元素的位置和要删除的元素数量。
- 插入:需要给splice()传3个参数,开始位置、0(要删除的元素数量)和要插入的元素,可以在数组中指定的位置插入元素。
- 替换:splice()在删除元素的同时可以在指定位置插入新元素,同样要传入3个参数,开始位置、要删除位置的数量和要出插入的任意多个元素。
splice()方法始终返回一个数组,包含从数组中被删除的元素(如果没有这个数组,则返回空数组)。
代码如下(示例):
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");
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
12.搜索和位置方法
ECMAScript提供了两类搜索数组的方法:按严格相等搜索和按断言函数搜索。
ECMAScript一个了 3个严格相等的搜索方法:indexOf()、lastIndexOf()和includes()。这些方法都接收两个参数:要查找的元素和一个可选的起始搜索位置。indexOf()和lastIndexOf()都返回要查找的元素在数组中的位置,如果没有找到则返回-1。includes()返回布尔值,表示是否至少找到一个与指定元素匹配的项。
代码如下(示例):
let number=[1,2,3,4,5,4,3,2,1];
alert(number.indexOf(4));//3
alert(number.lastIndexOf(4));//5
alert(number.includes(4));//true
alert(number.indexOf(4,4));//5
alert(number.lastIndexOf(4,4));//3
alert(number.includes(4,7));//false
ECMAScript也允许按照定义的断言函数搜索数组,每个索引都会调用这个函数。断言函数的返回值决定了相应索引的元素是否被认为匹配。find()和findIndex()方法都是从数组的最小索引开始。find()返回第一个匹配的元素,findIndex()反悔第一个匹配元素的索引。这两个方法也都接收第二个可选的参数,用于指定断言函数内部this的值。
代码如下(示例):
const people=[
{
name:"Matt";
age:27
},
{
name:"Nicholas";
age:29
}
];
alert(people.find((element,index,array)=>element.age<28));
//{name:"Matt",age:27}
alert(people.findIndex((element,index,array)=>element.age<28));//0
13.迭代方法
ECMAScript为数组定义了5种迭代方法。每个参数接收两个参数:以每一项为参数运行的函数,以及可选的 作为函数运行上下文的作用域。传给每个方法的函数接收3个参数:数组元素、元素索引和数字本身。数组的5种迭代方法:
- every():对数组每一项都运行传入的函数,如果对每一项函数都返回true,则这个方法返回true。
- filter():对数组每一项都运行传入函数,函数返回true的项会组成数组之后返回。
- forEach():对数组每一项都运行传入函数,没有返回值。
- map():对数组每一项都运行传入函数,返回由每次函数调用的结果构成的数组。
- some():对数组每一项都运行传入函数,如果有一项函数返回true,则这个方法返回true。
every()方法传入的函数必须对每一项都返回true ,它才会返回true,否则,它就返回false。而对some()而言,只要有一项让传入函数返回true ,它就会返回true。
代码如下(示例):
let numbers=[1,2,3,4,5,4,3,2,1];
let everyResult=numbers.every((item,index,array) => item>2);
alert(everyResult);//false
let someResult=numbers.some((item,index,array) => item>2);
alert(someResult);//true
filter()方法基于给定的函数来决定某一项是否应该包含在它的返回数组中。
代码如下(示例):
let numbers=[1,2,3,4,5,4,3,2,1];
let filterResult=numbers.filter((item,index,array) =>item>2);
alert(filterResult);//3,4,5,4,3
map()方法对数组每一项都运行传入函数,返回由每次函数调用的结果构成的数组。
代码如下(示例):
let numbers=[1,2,3,4,5,4,3,2,1];
let mapResult=number.map((item,index,array) => item*2);
alert(mapResult);//2,4,6,8,10,8,6,4,2
forEach()对每一项运行传入的函数,没有返回值。本质上,forEach()方法相当于使用for循环遍历数组。
代码如下(示例):
let numbers=[1,2,3,4,5,4,3,2,1];
numbers.forEach((item,index,array)=>{
//执行某些操作
});
14.归并方法
ECMAScript为数组提供了两个归并方法:reduce()和reduceRight()。这两个方法都会迭代数组的所有项,并在此基础上构建一个最终返回值。reduce()方法从数组第一项开始遍历到最后一项,而reduceRight()从最后一项开始遍历至第一项。这两个方法都接收两个参数:对每项都会运行的归并函数,以及可选的一直未归并起点的初始值。传给reduce()和reduceRight()的函数接收4个参数:上一个归并值、当前项、当前项的索引和数组本身。
代码如下(示例):
let values=[1,2,3,4,5];
let sum=values.reduce((prev,cur,index,array) =>prev+cur;);
alert(sum);//15
let values=[1,2,3,4,5];
let sun=values.reduceRight(function(prev,cur,index,array){
return prev+cur;
}
alert(sum);//15
三、定型数组
定型数组是ECMAScript新增的结构,目的是提升向原生库传输数据的效率。实际上JavaScript并没有“TypeArray”类型,它指的是一种特殊的包含数值类型的数组。
1.历史
目的是开发一套JavaScript API,从而充分利用3D图像API和GPU加速,以便在元素上渲染复杂的图形。
- WebGL
- 定型数组
2.ArrayBuffer
Float32Array实际上是一种“视图”,可以允许JavaScript运行时访问一块名为ArrayBuffer的预分配内存 。ArrayButter是所有定向数组即视图引用的基本单位 。
ArrayBuffer()是一个普通的JavaScript构造函数,可用于在内存中分配特定数量的字节空间 。ArrayBuffer()一经创建就不能再调整大小 ,但是可以使用slice()复制其全部或部分到一个新实例中。
代码如下(示例):
const buf1=new ArrayBuffer(16);//在内存中分配16字节
alert(buf1.byteLength);//16
const buf2=buf1.slice(4,12);
alert(buf2.byteLength)//8
ArrayBuffer某种程度上与C++的malloc()类似,区别在于:
- malloc()在分配失败时会返回一个null指针。ArrayButter在分配失败时会抛出错误。
- malloc()可以利用虚拟内存,因此最大可分配尺寸只受可寻址系统内存限制。ArrayButter分配的内存不能超过Number.MAX_SAFE_INTEGER(253-1)字节。
- malloc()调用成功不会初始化实例的地址。声明ArrayButter则会将所有二进制位初始化为0。
- 通过malloc()分配的堆内存除非调用free()或程序退出,否则系统不能再使用。而通过声明ArrayButter分配的堆内存可以被挡车垃圾回收,不用手动释放。
不能仅通过对ArrayBuffer的引用就读取或写入其内容。要读取或写入ArrayBuffer必须通过视图 。视图是用来操作内存的接口。视图可以操作数组缓冲区或缓冲区字节的子集,并按照其中一种数值数据类型来读取和写入数据。
3.DataView
DataView视图专为文件I/O和网络I/O设计,其API允许对缓冲数据的高度控制,但相比于其他类型的视图性能也差一些。必须在对已有 的ArrayBuffer读取或写入时才能创建DataView实例。
代码如下(示例):
const buf=new ArrayBuffer(16);
//DataView默认使用整个ArrayBuffer
const fullDataView=new DataView(buf);
alert(fullDataView.byteOffset);//0
alert(fullDataView.byteLength);//8
alert(fullDataView.buffer===buf);//true
//构造函数接收一个可选的字节偏移量和字节长度
//byteOffset=0表示视图从缓冲起点开始
//byteLength=8表示限制视图为前8个字节
const firstHalfDataView=new DataView(buf,0,8);
alert(firstHalfDataView.byteOffset);//0
alert(firstHalfDataView.byteLength);//8
alert(firstHalfDataView.buffer===buf);//true
//如果不指定,则DataView会使用剩余的缓冲
//byteOffset=8表示视图从缓冲的第9个字节开始
//byteLength为指定,默认为剩余缓冲
const secondHalfDataView=new DataView(buf,8);
alert(secondHalfDataView.byteOffset);//8
alert(secondHalfDataView.byteLength);//8
alert(secondHalfDataView.buffer===buf);//true
要通过DataView读取缓冲,还需要几个组件:
- 首先使用读或写的字节偏移量。可以看成DataView中的某种“地址”。
- DataView应该使用ElementType来实现JavaScript的Number类型到缓冲内二进制格式的转换。
- 最后是内存中值的字节序。默认为大端子字节序。
ElementType
DataView对存储在缓冲内的数据类型没有预设。它暴露的API强制开发者在读、写时指定一个ElementType,然后DataView就会忠实地为读、写而完成相应的转换。
DataView为ElementType的每种类型都暴露了get和set方法,这些方法使用byteOffset(字节偏移量)定位要读取或写入值的位置。
字节序
字节序指的是计算机系统维护的一种字节顺序的约定。DataView只支持两种约定:大端字节序和小端字节序。大端字节序也称为“网络字节序”,意思是最高有效位保存在第一个字节,而最低有效位保存在最后一个字节。小端字节序正好相反,最低有效位保存在第一个字节,而最高有效位保持在最后一个字节。
DataView是一个中立接口,会遵循你指定的字节序 。
边界情况
DataView完成读、写操作的前提是必须有充足的缓冲区,否则就会抛出RangeError。DataView在写入缓冲里会尽最大努力把一个值转换为适当的类型,后备为0。如果无法转换,则抛出错误。
4.定型数组
定型数组是另一种形式的ArrayBuffer视图。它特定于一种ElementType且遵循系统原生的字节序 。
创建定型数组的方式包括读取已有的缓冲 、使用自有缓冲 、填充可迭代结构 ,以及填充基于任意类型的定型数组 。另外,通过<ElementType>.from()和<ElementType>.of()也可以创建定型数组。
代码如下(示例):
//创建了一个12字节的缓冲
const buf=new ArrayBuffer(12);
//创建一个引用该缓冲的Int32Array
const ints=new Int32Array(buf);
//这个定型数组知道自己的每个元素需要4字节,因此长度为3
alert(ints.length);//3
//创建一个长度为6的Int32Array
const ints2=new Int32Array(6);
//每个数值使用4字节,因此ArrayBuffer是24字节
alert(ints2.length);//6
alert(ints2.buffer.byteLength);//24
const ints3=new Int32Array([2,4,6,8]);
alert(ints3.length);//4
alert(ints3.buffer.byteLength);//16
alert(ints3[2]);//6
const ints4=new Int16Array(ints3);
alert(ints4.length);//4
alert(ints4.buffer.byteLength);8
alert(ints4[2]);//6
const ints5=Int16Array.from([3,5,7,9]);
alert(ints5.length);//4
alert(ints5.buffer.byteLength);//8
alert(ints5[2]);//7
const floats=Float32Array.of(3.14,2.718,1.618);
alert(floats.length);//3
alert(floats.buffer.byteLength);//12
alert(floats[2]);//1.6180000305175781
定型数组的构造函数和实例都有一个BYTES_PER_ELEMENT属性,返回该类型数组中的每个元素的大小。
代码如下(示例):
alert(Int16Array.BYTES_PER_ELEMENT);//2
alert(Int32Array.BYTES_PER_ELEMENT);//4
const ints=new Int32Array(1);
floats=new Float64Array(1);
alert(ints.BYTES_PER_ELEMENT);//4
alert(floats.BYTES_PER_ELEMENT);//8
定型数组行为
定型数组支持如下操作符、方法和属性:
- []
- copyWithin()
- entiries()
- every()
- fill()
- filter()
- find()
- findIndex()
- forEach()
- indexOf()
- join()
- keys()
- lastIndexOf()
- length
- map()
- reduce()
- reduceRight()
- reverse()
- slice()
- some()
- sort()
- toLocaleString()
- toString()
- values()
定型数组返回新数组的方法也会返回包含同样元素类型的新定型数组。
合并、复制和修改定型数组
定型数组同样使用数组缓冲来存储数据,而数组缓冲无法调节大小,因此下列方法不适用于定型数组:
- concat()
- pop()
- push()
- shift()
- splice()
- unshift()
定型数组提供了可以快速向外或向内复制数据:set()和subarray()。
set()从提供的数组或定型数组中把值复制到当前定型数组中指定的索引位置。
代码如下(示例):
const container=new Int16Array(8);
//把定型数组复制到前4个值
container.set(Int8Array.of(1,2,3,4));
console.log(container);//[1,2,3,4,0,0,0,0]
//把普通数组复制到后4个值
container.set([5,6,7,8],4);
console.log(container);//[1,2,3,4,5,6,7,8]
container.set([5,6,7,8],7);//溢出抛出错误
console.log(container);
subarray()基于从原始定型数组中复制的值返回一个新定型数组。
代码如下(示例):
const source=Int16Array.of(2,4,6,8);
const fullyCopy=source.subarry();
console.log(fullyCopy);//[2,4,6,8]
const halfCopy=source.subarry(2);
console.log(halfCopy);//[6,8]
const partialCopy=source.subarry(1,3);
console.log(partialCopy);//[4,6]
下溢和上溢
定型数组中值的下溢和上溢不会影响到其他索引,但仍然需要考虑数组的元素应该是什么类型。定型数组对于可以存储的每个索引只接受一个相关位,而不考虑它们对实际数值的影响。
四、Map
1.基本API
使用new关键字和Map构造函数可以创建一个空映射。
const m=new Map();
如果想在创建的同时初始化实例,可以给Map构造函数传入一个可迭代对象,需要包含键/值对数组。可迭代对象中的每个键/值对都会按照迭代顺序插入到新映射实例中。
代码如下(示例):
//使用嵌套数组初始化映射
const m1=new Map([
["key1","val1"];
["key2","val2"],
["key3","val3"],
]);
alert(m1.size);//3
//使用自定义迭代器初始化映射
const m2=new Map({
[Symbol.iterator]:function*(){
yeild ["key1","val1"];
yeild ["key2","val2"];
yeild ["key3","val3"];
}
});
alert(m2.size);//3
初始化后可以使用set()方法再添加键/值对。另外可以使用get()和has()进行查询,可以通过size()属性获得映射中的键/值对的数量,还可以使用delete()和clear()删除值。
代码如下(示例):
const m=new Map();
alert(m.has("firstName"));//false
alert(m.get("firstName"));//undefined
alert(m.size);//0
m.set("firstName","Matt"),
.set("lastName","Frisbie");
alert(m.has("firstName"));//true
alert(m.get("firstName"));//Matt
alert(m.size);//2
m.delete("firstName");//删除"firstName"这一项
alert(m.has("firstName"));//false
alert(m.has("lastName"));//true
alert(m.size);//1
m.clear();//全部删除
alert(m.has("firstName"));//false
alert(m.has("lastName"));//false
alert(m.size);//0
Map可以使用任何JavaScript数据类型作为键。映射的值是没有限制的。
在映射中用作键和值的对象及其他“集合”类型,在自己的内容或属性被修改是仍然保持不变。
代码如下(示例):
const m=new Map();
const objKey={};
const objVal={};
const arrKey=[];
const arrVal=[];
m.set(objKey,objVal);
m.set(arrKey,arrVal);
//修改属性
objKey.foo="foo";
objVal.bar="bar";
arrKey.push("foo");
arrVal.push("bar");
console.log(m.get(objKey));//{bar:"bar"}
console.log(m.get(arrKey));//["bar"]
2.顺序与迭代
与Object类型的一个主要差异是,Map实例会维护键值对的插入顺序 ,因此可以根据插入顺序执行迭代操作。
映射实例可以提供一个迭代器,能以插入顺序生成[key,value]形式的数组。可以通过entries()或者Symbol.iterator属性取得这个迭代器。
代码如下(示例):
const m=new Map([
["key1","val1"];
["key2","val2"],
["key3","val3"],
]);
alert(m.entries==m[Symbol.iterator]);
for(let pair of m.entries()){
alert(pair);
}
//key1,val1
//key2,val2
//key3,val3
for(let pair of m[Symbol.iterator]()){
alert(pair);
}
//key1,val1
//key2,val2
//key3,val3
使用映射的forEach(callback,opt thisArg)方法并传入回调,依次迭代每个键/值对。
代码如下(示例):
const m=new Map([
["key1","val1"];
["key2","val2"],
["key3","val3"],
]);
m.forEach((val,key) => alert(`${key} -> ${val}`));
//key1 -> val1
//key2 -> val2
//key3 -> val3
keys()和values()可以返回以插入顺序生成键和值的迭代器。
键和值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改。
3.Object和Map的比较
对象(Object)和映射(Map)存在的差别:
-
内存占用
Object和Map的工程级实现在不同的浏览器间存在明显差异,但存储单个键/值对所占用的内存数量都会随键的数量线性增加。不同浏览器的情况不同,但给定固定大学的内存,Map大约可以比Object多存储50%的键/值对。 -
插入性能
向Object和Map中插入新键/值对的消耗大致相当,不过插入Map在所有浏览器中一般会稍微快一点。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。 -
查找速度
与插入不同,从大型Object和Map中查找键/值对的性能差异极小,但如果只包含少量值/键对,则Object有时候速度更快。对这两个类型而言,查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择Object更好一些。 -
删除性能
使用delete删除Object属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。而对绝大多数浏览器引擎来说,Map的delete()操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择Map。
五、WeakMap
ECMAScript6新增的“弱映射”(WeakMap)是一种新的集合类型,WeakMap是Map的“兄弟”类型,其API也是Map的子集。WeakMap中的“weak”描述的是JavaScript垃圾回收程序对的“弱映射”中键的方式。
1.基本API
可以使用new关键词实例化一个空的WeakMap。
代码如下(示例):
const wm=new WeakMap();
弱映射中的键只能是Object或者继承自Object的类型 ,尝试使用非对象设置键会抛出TypeError。值的类型没有限制 。
代码如下(示例):
const key1={id:1},
key2={id:2},
key3={id:3};
//使用嵌套数组初始化弱映射
const wm1=new WeakMap([
[key1,"val1"],
[key2,"val2"],
[key3,"val3"],
]);
alert(wm1.get(key1));//val1
alert(wm1.get(key2));//val2
alert(wm1.get(key3));//val3
//初始化时全有或全无的操作
//只要有一个键无效就会抛出错误,导致整个初始化失败
const wm2=new WeakMap([
[key1,"val1"],
["BADKEY","val2"],//抛出错误
[key3,"val3"];
]);
//原始值可以先包装称对象再用作键
const stringKey=new String("key1");
const wm3=new WeakMap([
stringKey,"val1"
]);
alert(wm3.get(stringKey));//"val1"
初始化后可以使用set()再添加键/值对,可以使用get()和has()查询,还可以使用delete()删除。
2.弱键
WeakMap中“weak”表示弱映射的键是“弱弱地拿着”的。意思是这些键不属于正式的引用,不会阻止垃圾回收。但是只要键存在,键/值对就会存在于映射中,并被当作对值引用,因此不会被当做垃圾回收。
代码如下(示例):
const wm=new WeakMap();
wm.set({},"val");
//当代码执行完成后,这个对象键就会被当做垃圾回收
//键/值对就会从弱映像中消失,成为一个空映射
3.不可迭代键
因为WeakMap中的键/值对任何时候都可能被销毁 ,所以没必要提供迭代其键/值对的能力。WeakMap实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值。如果允许原始值,就没办法区别初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。
4.使用弱映射
私有变量
弱映射造就了在JavaScript中实现真正私有变量的一种方式。前提很明确:私有变量会存储在弱映射中,以对象实例为键,以私有成员的字典为值。
DOM节点元数据
因为WeakMap实例不会妨碍垃圾回收,所以非常适合保存元数据。
代码如下(示例):
//假设原来的登录按钮从DOM树中被删除了,但映射中还保留着按钮的引用
//所以对应的DOM节点仍然会逗留在内存中,除非将其从映射中删除或者等待本身被销毁
const m=new Map();
const loginButton=document.querySelector("#login");
//给这个节点关联一些元数据
m.set(loginButton,{disabled:true}}
//如果这里使用弱映射,原来的登录按钮从DOM树中被删除了
//垃圾回收程序可以立即释放其内存
cosntwm=new WeakMap();
const loginButton=document.querySelector("#login");
//给这个节点关联一些元数据
m.set(loginButton,{disabled:true}}
六、Set
ECMAScript6新增的Set是一种新集合类型,为这门语言带来集合数据结构。
1.基本API
使用new关键字和Set构造函数可以创建一个空集合:
const m=new Set();
如果想在创建的同时初始化实例,则可以给Set构造函数传入一个可迭代对象,其中需要包含插到新集合实例中的元素:
代码如下(示例):
//使用数组初始化集合
const s1=new Set(["val1","val2","val3"]);
alert(s1.size);//3
//使用自定义迭代器初始化集合
const s2=new Set({
[Symbol.iterator]: function*(){
yield "val1";
yield "val2";
yield "val3";
}
});
alert(s2.size);//3
初始化之后,可以使用add()增加值,使用has()查询,通过size()取得元素数量,以及使用delete()和clear()删除元素。
代码如下(示例):
const s=new Set();
alert(s.has("Matt"));//false
alert(s.size);//0
s.add("Matt");
.add("Frisbie");
alert(s.has("Matt");//true
alert(s.size);//2
s.delete("Matt");
alert(s.has("Matt"));//false
alert(s.has("Frisbie"));//true
alert(s.size);//1
s.clear();
alert(s.has("Matt"));//false
alert(s.has("Frisbie"));//false
alert(s.size);//0
add()和delete()操作是幂等(任意多次执行所产生的影响均与一次执行的影响相同 )的。delete()返回一个布尔值 ,表示集合中是否存在要删除的值。
代码如下(示例):
const s=new Set();
s.add('foo');
alert(s.size);//1
s.add('foo');
alert(s.size);//1
alert(s.delete('foo'));//true
alert(s.delete('foo'));//false
2.顺序与迭代
Set会维护插入时的顺序,因此支持按顺序迭代。
集合实例可以提供一个迭代器,能以插入顺序生成集合内容。可以提供一个迭代器,能以插入顺序生成集合内容。可以通过value()方法及其别名方法key()取得这个迭代器。
集合的entries()方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合中每个值的重复出现。
代码如下(示例):
const s=new Srt(["val1","val2","val3"]);
for(let pair of s.entries()){
console.log(pair);
}
//["val1","val1"]
//["val2","val2"]
//["val3","val3"]
如果不使用迭代器,而是使用回调方法,则可以调用集合的forEach方法并传入回调,依次迭代每个键/值对。
七、WeakSet
ECMAScript6新增的弱集合是一种新集合类型,为这门语言带来集合数据结构。WeakSet中的“weak”描述的是JavaScript垃圾回收程序对待“弱集合”中值的方式。
1.基本API
可以使用new关键词实例化一个空的WeakSet:
const ws=new WeakSet();
弱集合中的值只能是Object或者继承自Object的类型,尝试使用非对象设置值会抛出TypeError。
代码如下(示例):
const val1={id:1},
val2={id:2},
val3={id:3};
//使用数组初始化集合
const ws1=new WeakSet([val1,val2,val3]);
alert(ws1.has(val1));
alert(ws1.has(val2));
alert(ws1.has(val3));
//初始化是全有或全无的操作
//只要有一个值无效就会抛出错误,导致整个初始化失败
const ws2=new WeakSet([val1,"BADVAL",val3]);
typeof ws2;//抛出错误
初始化后可以使用add()再添加新值,可以使用has()查询,还可以使用delete()删除。
2.弱值
WeakSet的值不属于正式的引用,不会阻止垃圾回收。
代码如下(示例):
const ws=new WeakSet();
ws.add({});//对象会被当成垃圾回收
3.不可迭代值
因为WeakSet中的值任何时候都可能被销毁,所以没有必要提供迭代其值的能力。WeakSet之所以限制只能对象作为值,是为了保证只有通过值对象的引用才能取得值 。如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。
4.使用弱集合
WeakSet可以让垃圾回收程序回收元素的内存:
代码如下(示例):
const disabledElements=new WeakSet();
const loginButton=document.querySelector('#login');
//通过加入对应集合,给这个节点打上“禁用”标签
disabledElements.add(loginButton);
八、迭代与扩展操作
有4种原生集合类型定义了默认迭代器,都支持顺序迭代,都可以传入for-of循环:
- Array
- 所有定型数组
- Map
- Set
这些类型都兼容扩展操作符。扩展操作符在对可迭代对象执行浅复制时特别有用,只需要简单的语法就可以复制整个对象。