6.集合引用类型
对象
数组与定型数组
Map、WeakMap、Set 以及 WeakSet 类型
6.1.Object:
Object的引用值创建:两种方法:
1.是使用 new 操作符和 Object 构造函数
2.是通过字面量写{}包裹的对象字面量语句,不会调用Object的构造函数。
关于字面量创建的一些知识点:在表达式语句中的括号{}代表的是表达式上下文(表达式上下文指的是期待返回值的上下文。赋值操作符表示后面要期 待一个值,因此左大括号表示一个表达式的开始。) 而语句上下文则是语句块中存在。
对象的取值:
1.使用.方法取值
2.使用获取key后再调用对象[key]的方法取值,[ ]给予了对象一种类hash类数组的感觉,可以借助此实现类似操作。
6.2Array类型:
区别于其他语言:
1.js的数组对象可以是元组(可以通过ts来约束)
2.js的数据对象是动态大小(隐藏了销毁和重赋值操作,因为要选取合适空间)
数组的创建:
1.构造函数创建方式;传入可以传入长度或者内部元素(多构造器)
2.使用数组字面量的方法来进行创建,与对象一样,在使用数组字面量表示法创建数组不会调用 Array 构造函数。
3.方法转换:.from()方法可以转换一个类数组对象以及任何可以迭代的结构,或者有length和可索引元素的结构。 它的第二个参数可以直接传入一个函数遍历调用增强数组的值。 它的第三个参数可以指定函数中this的值。
数组空位
在数组字面量创建时,可能会出现逗号之间 相应索引位置的值当成空位的情况
const options = [,,,,,]; // 创建包含 5 个元素的数组
ES6 新增方法将空位解析成为undefined,ES6之前的方法会忽略空位(但有些方法不会)。由于行为不一致和存在性能隐患,因此实践中要避免使用数组空位。如果确实需要 空位,则可以显式地用 undefined 值代替。(wb适配打包后防止报错)
数组索引:
通过中括号可以访问数组值,对于数组的length属性,它并非只读,它可以被赋值来改变数组的大小。
数组最多可以包含 4 294 967 295 个元素。
如何检测一个对象是否为数组:
本身其实只需要使用instanceof操作符来查看其原型链上是否为Array构造函数对象(存在于window上),但是如果页面有两个window的嵌入框架,会导致window对象有多个,如果在两框架中传递了数组元素,会导致判断基于另一个Array对象来进行,与原原型链不一致。
所以为解决这个问题,ECMAScript 提供了 Array.isArray()方法。这个方法的目的就是确定一个值是 否为数组,而不用管它是在哪个全局执行上下文中创建的。
数组上的迭代器方法:
Array 的原型上暴露了 3 个用于检索数组内容的方法:keys()、values()和 entries()。
keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而 entries()返回 索引/值对的迭代器。
可以使用ES6的解构方法来拆解索引键/值对的迭代器:
for (const [idx, element] of a.entries()) {
alert(idx);
alert(element);
}
关于数组的复制和填充方法:
批量复制方法 copyWithin(),以及填充数组方法 fill()–》fill可以帮助初始化的数组赋值。–》copy方法可以复制数组中的一段内容进行拓展值。
关于数组的转换方法:
toLocaleString()、toString()和 valueOf()以及join方法来转换数组生成字符串
关于数组的栈方法和队列方法:
push,pop这些常见的数组方法,shift可以剔除数组的首位。这些都可以模拟自己实现,绑定在数组的原型链上调用返回新数组,置原数组为空。
排序方法:
数组有两个方法可以用来对元素重新排序:reverse()和 sort(),sort在排序时调用String转型函数来比较字符串比较,如果想要用数值排序的话可以使得sort接入一个比较函数来判断值的排序。
(但注意原方法返回的是调用它们的数组引用)
操作方法:
concat()方法可以在现有数组全部元素基础上创建一个新数组,slice()用于创建一个包含原有数组中一个或多个元素的新数组(数组截取),因为.方法的调用可以访问this,所以参数为负数会被额外与length处理。splice可以添加删除并处理。
搜索和位置方法:
indexOf()、lastIndexOf()和 includes()–>有正则不怎么介绍这个了。
断言函数的索引;这个索引可以应用于元组中对象以及数组的一些复杂类型索引,通过传入数组的元素在断言函数中处理,在经过断言的返回值决定返回结果。总的来说也是一个迭代方法。
迭代方法:
数组上有五个迭代方法,
every():对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。
filter():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。
forEach():对数组每一项都运行传入的函数,没有返回值。
map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。
some():对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。
迭代每一项(迭代项调用的函数,它的this)调用的函数内部(传入每一项的值,每一项的建,数组本身) filter适合从数组中赛选满足给定条件的元素。
归并方法:
ECMAScript 为数组提供了两个归并方法:reduce()和 reduceRight()。这两个方法都会迭代数 组的所有项,并在此基础上构建一个最终返回值。
这个方法会归并每一项,并且将参数传入下一步中。
6.3定型数组:
定型数组(typed array)是 ECMAScript 新增的结构,目的是提升向原生库传输数据的效率。
出现的原因:是因为WebGL通常不需要以js双精度浮点格式传递的数据,所以定义了CanvasFloatArray (变成了Float32Array)
1.ArrayBuffer:Float32Array 实际上是一种“视图”,可以允许 JavaScript 运行时访问一块名为 ArrayBuffer 的 预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位。
ArrayBuffer 某种程度上类似于 C++的 malloc(),但也有几个明显的区别。
malloc()在分配失败时会返回一个 null 指针。ArrayBuffer 在分配失败时会抛出错误。
malloc()可以利用虚拟内存,因此最大可分配尺寸只受可寻址系统内存限制。ArrayBuffer
分配的内存不能超过 Number.MAX_SAFE_INTEGER(253 1)字节。
malloc()调用成功不会初始化实际的地址。声明 ArrayBuffer 则会将所有二进制位初始化
为 0。
通过 malloc()分配的堆内存除非调用 free()或程序退出,否则系统不能再使用。而通过声明
ArrayBuffer 分配的堆内存可以被当成垃圾回收,不用手动释放。
2.DataView:
关于dataview视图,它专用于文件的I/O和网络 I/O 设计。
它相当于先创建的arraybuffer的一个映射,可以通过初始化的方法来指定其偏移量等。如果需要通过dataview读取缓冲还需要几个组件。
首先是要读或写的字节偏移量。可以看成 DataView 中的某种“地址”。
DataView 应该使用 ElementType 来实现 JavaScript 的 Number 类型到缓冲内二进制格式的转
换。
最后是内存中值的字节序。默认为大端字节序
1.ElementType:
DataView 对存储在缓冲内的数据类型没有预设。它暴露的 API 强制开发者在读、写时指定一个 ElementType,然后 DataView 就会忠实地为读、写而完成相应的转换。
2.字节序:
大端字节序也称为“网络字节序”,意思 是最高有效位保存在第一个字节,而最低有效位保存在最后一个字节。小端字节序正好相反,即最低有 效位保存在第一个字节,最高有效位保存在最后一个字节//模拟内存环境
3.边界情形:
DataView 完成读、写操作的前提是必须有充足的缓冲区,否则就会抛出 RangeError
3.定型数组:
定型数组是另一种形式的 ArrayBuffer 视图。虽然概念上与 DataView 接近,但定型数组的区别 在于,它特定于一种 ElementType 且遵循系统原生的字节序。相应地,定型数组提供了适用面更广的 API 和更高的性能。
定型数组就是以提供的规范来创建分配自己的缓冲区,分配完成后它会有自己的缓冲指向,
定型函数的构造函数和实例都会有有一个 BYTES_PER_ELEMENT 属性,返回该类型数组中每个元素的大小
如果定型数组没有用任何值初始化,则其关联的缓冲会以 0 填充。
【1】定型数组行为:定型数组支持着一系列的数组语句以及函数方法。其中返回新数组的方法也会返回包含同样元素类型(element type)的新定型数组。 也可以支持迭代方法。
【2】合并、复制和修改定型数组,常规改变数组大小的方法不再适用,数组缓冲无法调整大小。定型数组也提供了两个新方法,可以快速向外或向内复制数据:set()和 subarray(),只能通过其length构建一个大数组来封装其所有拼接值。
【3】下溢和上溢:定型数组中值的下溢和上溢不会影响到其他索引,但仍然需要考虑数组的元素应该是什么类型。
6.4Map:
1.基本API:使用 new 关键字和 Map 构造函数可以创建一个空映射,如果向创建时同时初始化可以传入一个可迭代对象,内部有键值和对值。
2.初始化后可以使用 set()方法再添加键/值对。另外,可以使用 get()和 has()进行查询,可以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete()和 clear()删除值。
set()方法返回映射实例,因此可以把多个操作连缀起来,类似promise 的then链调用。
(可以用任何值来当做键值对)与严格相等一样,在映射中用作键和值的对象及其他“集合”类型,在自己的内容或属性被修改时 仍然保持不变:
map的值get只要键值全等便可以获取到相同的对。
SameValueZero 是 ECMAScript 规范新增的相等性比较算法。
const m = new Map();
const a = 0/"", // NaN
b = 0/"", // NaN
pz = +0,
nz = -0;
alert(a === b); // false
alert(pz === nz); // true
m.set(a, "foo");
m.set(pz, "bar");
alert(m.get(b)); // foo
alert(m.get(nz)); // bar
3.js的Map实例会维护护键值对的插入顺序,因此可以根据插入顺序执 行迭代操作,映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。
通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器
键和值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改。当然,这并不妨碍修改作为 键或值的对象内部的属性,因为这样并不影响它们在映射实例中的身份(感觉hash计算的是引用的值–在计算对象类型时)
4.选择Object还是Map
【1】内存占用:Map的内存占用较低,给定固定大小的内存,Map 大约可以比 Object 多存储 50%的键/值对。
【2】插入性能:过插入 Map 在所有浏览器中一般会稍微快一些。
【3】查找速度:与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对, 则 Object 有时候速度更快。在把 Object 当成数组使用的情况下(比如使用连续整数作为属性),浏 览器引擎可以进行优化,在内存中使用更高效的布局。
【4】删除性能:如果代码涉及大量删除操作,那么毫无疑问应该选择 Map
6.5.WeekMap
ECMAScript 6 新增的“弱映射”(WeakMap)是一种新的集合类型,为这门语言带来了增强的键/ 值对存储机制。WeakMap 是 Map 的“兄弟”类型,其 API 也是 Map 的子集。WeakMap 中的“weak”(弱), 描述的是 JavaScript 垃圾回收程序对待“弱映射”中键的方式。
弱映射解决的是js中对于键的处理方式:
1.基本API:
它有自己的构造方法,弱映射中的键只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置键会抛出 TypeError。值的类型没有限制。
2.弱键:
键不属于正式的引用, 不会阻止垃圾回收。意思是键只能是存在于内存中的值,就不会被垃圾回收和释放,一旦键被回收,值也会被回收。
3.不可迭代键:
因为不可能迭代, 所以也不可能在不知道对象引用的情况下从弱映射中取得值。
WeakMap 实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值。如果 允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。
4.弱映射:
WeakMap 实例与现有 JavaScript 对象有着很大不同。
【1】私有变量:弱映射造就了在 JavaScript 中实现真正私有变量的一种新方式。
const wm = new WeakMap(); //定义弱类型,只能通过对象去拿值
class User {
constructor(id) {//初始化了属性
this.idProperty = Symbol('id');
this.setId(id); //调用set
}
setPrivate(property, value) {
const privateMembers = wm.get(this) || {};//获取私有对象组
privateMembers[property] = value;//给私有对象组符号赋值新属性
wm.set(this, privateMembers); //以当前对象引用来设置为键
}
getPrivate(property) {
return wm.get(this)[property];
}
setId(id) { //调用并且传入了唯一字符以及id的值
this.setPrivate(this.idProperty, id);
}
getId() {
return this.getPrivate(this.idProperty);
}
}
const user = new User(123);
alert(user.getId()); // 123
user.setId(456);
alert(user.getId()); // 456
// 并不是真正私有的
alert(wm.get(user)[user.idProperty]); // 456
//真正的实现需要一个闭包把weakMap包装起来隔离弱类型和外界环境。 但是整个代码也完全陷入了 ES6 之前的闭包私有变量模式。
【2】DOM节点元数据:因为 WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据(弱类型可以保留一些随时会被删除的对象做为键值来帮助垃圾回收)
6.6Set:
ECMAScript 6 新增的 Set 是一种新集合类型,为这门语言带来集合数据结构。Set 在很多方面都 像是加强的 Map,这是因为它们的大多数 API 和行为都是共有的。
只是数据操作较为不同:初始化之后,可以使用 add()增加值,使用 has()查询,通过 size 取得元素数量,以及使用 delete() 和 clear()删除元素。
Set 可以包含任何 JavaScript 数据类型作为值。集合也使用 SameValueZero 操作
1.顺序与迭代:
Set 会维护值插入时的顺序,因此支持按顺序迭代。 基本和Map相似,没什么需要特殊介绍的。
2.定义正式集合操作:
某些 Set 操作是有关联性的,因此最好让实现的方法能支持处理任意多个集合实例。
Set 保留插入顺序,所有方法返回的集合必须保证顺序。
尽可能高效地使用内存。扩展操作符的语法很简洁,但尽可能避免集合和数组间的相互转换能
够节省对象初始化成本。
不要修改已有的集合实例。union(a, b)或 a.union(b)应该返回包含结果的新集合实例。
自己扩展子类来实现集合的一些操作
6.7 WeakSet:
WeakSet 中的“weak”(弱),描述的 是 JavaScript 垃圾回收程序对待“弱集合”中值的方式
1.弱集合中的值只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置值会抛出 TypeError,初始化之后可以使用 add()再添加新值,可以使用 has()查询,还可以使用 delete()删除.
2.弱值:
WeakSet 中“weak”表示弱集合的值是“弱弱地拿着”的。意思就是,这些值不属于正式的引用, 不会阻止垃圾回收。
在外层环境中,weakset拿到的对象引用消失会导致,值从弱集合中消失。
3.它也是不可迭代的
4.使用弱集合的案例
6.8迭代与扩展操作:
ECMAScript 6 新增的迭代器和扩展操作符对集合引用类型特别有用。这些新特性让集合类型之间 相互操作、复制和修改变得异常方便。
(…)扩展操作符在对可迭代对象执行浅复制时特别有用,浅复制意味着只会复制对象引用
对于可迭代对象的构造函数,只要传入一个可迭代对象就可以实现复制。