[路飞]_红宝书读书笔记(四)

400 阅读10分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

读书系列笔记第四篇,本篇主要总结第五、六章的内容,基本引用类型和集合引用类型,其中基本引用类型可总结的不多,主要是一些方法的使用,我在这里罗列出了链接,集合引用类型做了大概的介绍,后面章节会对具体概念进行展开和学习。 目前这部读书笔记可能过于个人,很多地方我觉得自己完全掌握的部分就没用太详细的记录,我会在后面整理的时候不断完善这部读书笔记,期待自己能整理的完善且易懂,加油💪🏻

引用类型

基本引用类型

Date

RegExp

原始值包装类型

Boolean

  1. Boolean.prototype.toString()
  2. Boolean.prototype.valueOf()

Number

  1. Properties

    1. Number.EPSILON
    2. Number.MAX_SAFE_INTEGER
    3. Number.MAX_VALUE
    4. Number.MIN_SAFE_INTEGER
    5. Number.MIN_VALUE
    6. Number.NaN
    7. Number.NEGATIVE_INFINITY
    8. Number.POSITIVE_INFINITY
  2. Methods

    1. Number.isFinite()
    2. Number.isInteger()
    3. Number.isNaN()
    4. Number.isSafeInteger()
    5. Number.parseFloat()
    6. Number.parseInt()
    7. Number.prototype.toExponential()
    8. Number.prototype.toFixed()
    9. Number.prototype.toLocaleString()
    10. Number.prototype.toPrecision()
    11. Number.prototype.toString()
    12. Number.prototype.valueOf()

String

  1. Properties

    1. String length
  2. Methods

    1. String.prototype[@@iterator]()
    2. String.prototype.at()
    3. String.prototype.charAt()
    4. String.prototype.charCodeAt()
    5. String.prototype.codePointAt()
    6. String.prototype.concat()
    7. String.prototype.endsWith()
    8. String.fromCharCode()
    9. String.fromCodePoint()
    10. String.prototype.includes()
    11. String.prototype.indexOf()
    12. String.prototype.lastIndexOf()
    13. String.prototype.localeCompare()
    14. String.prototype.match()
    15. String.prototype.matchAll()
    16. String.prototype.normalize()
    17. String.prototype.padEnd()
    18. String.prototype.padStart()
    19. String.raw()
    20. String.prototype.repeat()
    21. String.prototype.replace()
    22. String.prototype.replaceAll()
    23. String.prototype.search()
    24. String.prototype.slice()
    25. String.prototype.split()
    26. String.prototype.startsWith()
    27. String.prototype.substring()
    28. String.prototype.toLocaleLowerCase()
    29. String.prototype.toLocaleUpperCase()
    30. String.prototype.toLowerCase()
    31. String.prototype.toString()
    32. String.prototype.toUpperCase()
    33. String.prototype.trim()
    34. String.prototype.trimEnd()
    35. String.prototype.trimStart()
    36. String.prototype.valueOf()

单例内置对象

Global

  1. encodeURI() && encodeURIComponent()
  2. decodeURI() && decodeURIComponent()
  3. eval() 当解释器发现 eval()调用时,会将参数解释为实际的 ECMAScript 语句,然后将其插入到该位置。 通过 eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意 味着定义在包含上下文中的变量可以在 eval()调用内部被引用.
 let msg = "hello world";
 
  eval("console.log(msg)");// "hello world"
 
 eval("function sayHi() { console.log('hi'); }");
 sayHi(); // "hi"

通过 eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在 一个字符串中的。它们只是在 eval()执行的时候才会被创建。 在严格模式下,在 eval()内部创建的变量和函数无法被外部访问。换句话说,最后两个例子会报 错。同样,在严格模式下,赋值给 eval 也会导致错误:

"use strict"; 
eval = "hi";// 导致错误
  1. Global 对象属性(undefined, Array, Object, RexExp, TypeError, ...)

Math

  • 舍入方法

    • Math.ceil() 方法始终向上舍入为最接近的整数。
    • Math.floor() 方法始终向下舍入为最接近的整数。
    • Math.round() 方法执行四舍五入。
    • Math.fround() 方法返回数值最接近的单精度(32 位)浮点值表示。
  • random() : [0, 1)

  • 其他计算方法

集合引用类型

Object

名词记忆

  • 对象字面量 (Object literal) -> 表达式上下文
  • 表达式上下文 (expression context) : 在 ECMAScript 中,表达式上下文指的是期待返回值的上下文。
  • 语句上下文(statement context): eg: if语句的条件后面

Array

ES6新增静态方法

  • Array.from() 用于将 类数组结构转换为数组实例
  • Array.of() 用于将一组参数转换为数组实例. ES6之前使用Array.prototype.slice.call(arguments) 来实现参数到数组的转换,现在可以用Array.of(arguments)来代替

数组空位

使用数组字面量初始化数组时,可以使用一串逗号来创建空位(hole)。

ECMAScript 会将逗号之间 相应索引位置的值当成空位,ES6 规范重新定义了该如何处理这些空位。

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

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

for (const option of options) { console.log(option === undefined); } 
// false // true // true // true // false

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

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

// map()会跳过空位置 
console.log(options.map(() => 6)); // [6, undefined, undefined, undefined, 6]
// join()视空位置为空字符串 
console.log(options.join('-'));
// "1----5"
由于行为不一致和存在性能隐患,因此实践中要避免使用数组空位。如果确实需要空位,则可以显式地用 undefined 值代替。

检测数组

Array.isArray()

迭代器方法

keys()、values()、entries()

复制和填充方法

ES6新增方法

copyWithin(target, ?start, ?end): 批量复制方法

fill(value, ?start, ?end): 填充数组方法

这两个方法的 函数签名类似,都需要指定既有数组实例上的一个范围,包含开始索引,不包含结束索引。使用这个方 法不会改变数组的大小。

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]

转换方法

  • toLocaleString() : 调用item.toLocaleString,接成的字符串
  • toString() // 返回由数组中每个值的等效字符串拼(调用item.toString())接而成的一个逗号分割的字符串。
  • valueOf() // 返回数组本身。

栈方法

栈是一种后进先出(LIFO, last-in-first-out) 的结构。

数据的推入(push(element0, element1, ... , elementN): The new length property of the object upon which the method was called.)

删除(pop():The removed element from the array; undefined if the array is empty.)

队列方法

队列以先进先出(FIFO,First-In-First-Out)形式 限制访问。

队列的删除(shift( ): the shift() method removed element from the array; undefined if the array is empty.)

队列的推入(push())

排序方法

reverse():The reversed array.

sort(?compareFunction(firstEl, secondEl)):the sorted array. Note that the array is sorted in place, and no copy is made. ( return value > 0 secondEl在 firstEl前,value < 0 firstEl 在 secondEl前, 0 不交换位置 )

操作方法

concat(? value0, value1, ... , valueN):A new Array instance. concat()会把这些数组的每一项都添加到结果数组。 如果参数不是数组,则直接把它们添加到结果数组末尾。

slice(?start, ?end): A new array containing the extracted elements.

splice(start, ?deleteCount, ?item1, item2, itemN):An array containing the deleted elements.

搜索和位置方法

ECMAScript 提供两类搜索数组的方法:按严格相等搜索和按断言函数搜索。

严格相等

  • indexOf(searchElement, ?fromIndex):The first index of the element in the array; -1 if not found.
  • lastIndexOf(searchElement, ?fromIndex):The last index of the element in the array; -1 if not found.
  • includes(searchElement, ?fromIndex):A boolean value which is true if the value searchElement is found within the array (or the part of the array indicated by the index fromIndex, if specified).

断言函数

  • find(callbackFn(el, ? index, array), ? thisArg):The value of the first element in the array that satisfies the provided testing function. Otherwise, undefined is returned.
  • findIndex(callbackFn(el, ? index, array), ? thisArg):The index of the first element in the array that passes the test. Otherwise, -1.

迭代方法

  • every()对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。
  • filter()对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。
  • forEach()对数组每一项都运行传入的函数,没有返回值。
  • map()对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。
  • some()对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true。 这些方法都不改变调用它们的数组。

归并方法

  • reduce(callbackFn(accumulator, currentValue, ?index, array), ? initialValue): The value that results from the reduction
  • reduceRight()

定型数组 : TODO

定型数组(typed array)是 ECMAScript 新增的结构,目的是提升向原生库传输数据的效率。实际上, JavaScript 并没有“TypedArray”类型,它所指的其实是一种特殊的包含数值类型的数组。

Mozilla 为解决这个WebGL 原生数组和js数组之间的传递问题而实现了 CanvasFloatArray 。 这是一个提供 JavaScript 接口的、C 语言风格的浮点值数组。JavaScript 运行时使用这个类型可以分配、读取和写入数组。 这个数组可以直接传给底层图形驱动程序 API,也可以直接从底层获取到。最终,CanvasFloatArray 变成了 Float32Array,也就是今天定型数组中可用的第一个“类型”。

ArrayBuffer

Float32Array 实际上是一种“视图”,可以允许 JavaScript 运行时访问一块名为 ArrayBuffer 的 预分配内存。ArrayBuffer 是所有定型数组及视图引用的基本单位。

// ArrayBuffer()是一个普通的 JavaScript 构造函数,可用于在内存中分配特定数量的字节空间。
const buf = new ArrayBuffer(16); // 在内存中分配 16 字节
alert(buf.byteLength); // 16
// ArrayBuffer 一经创建就不能再调整大小。不过,可以使用 slice()复制其全部或部分到一个新 实例中:
const buf1 = new ArrayBuffer(16); 
const buf2 = buf1.slice(4, 12); 
alert(buf2.byteLength); // 8

ArrayBuffer 某种程度上类似于 C++的 malloc(),但也有几个明显的区别。// 6.3.2

DataView: TODO

第一种允许你读写 ArrayBuffer 的视图是 DataView。这个视图专为文件 I/O 和网络 I/O 设计,其 API 支持对缓冲数据的高度控制,但相比于其他类型的视图性能也差一些。DataView 对缓冲内容没有 任何预设,也不能迭代。

必须在对已有的 ArrayBuffer 读取或写入时才能创建 DataView 实例。这个实例可以使用全部或 部分 ArrayBuffer,且维护着对该缓冲实例的引用,以及视图在缓冲中开始的位置。

const buf = new ArrayBuffer(16);

// DataView 默认使用整个 ArrayBuffer 
const fullDataView = new DataView(buf); 
alert(fullDataView.byteOffset); // 0 
alert(fullDataView.byteLength); // 16 
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

ElementType

// TODO

字节序

“字节序”指的是计算系统维护的一种字节顺序的约 定。DataView 只支持两种约定:大端字节序和小端字节序。

大端字节序: 称为“网络字节序”,意思 是最高有效位保存在第一个字节,而最低有效位保存在最后一个字节。

小端字节序: 正好相反,即最低有 效位保存在第一个字节,最高有效位保存在最后一个字节。

16位宽的数0x1234在小端模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址0x40000x4001
存放内容0x340x12

在大端模式CPU内存中的存放方式则为:

内存地址0x40000x4001
存放内容0x120x34

边界情况

DataView 完成读、写操作的前提是必须有充足的缓冲区,否则就会抛出 RangeError

定型数组: TODO

定型数组是另一种形式的 ArrayBuffer 视图。虽然概念上与 DataView 接近,但定型数组的区别 在于,它特定于一种 ElementType 且遵循系统原生的字节序。设计定型数组的目的就是提高与 WebGL 等原生库交换二进制数据的效率。

定型数组同样使用数组缓冲来存储数据,而数组缓冲无法调整大小。因此,下列方法不适用于定型 数组:

  • concat()
  • pop()
  • push()
  • shift()
  • splice()
  • unshift()

定型数组也提供了两个新方法,可以快速向外或向内复制数据:set()和 subarray()。

下溢和上溢

MAP

作为 ECMAScript 6 的新增特性,Map 是一种新的集合类型,为这门语言带来了真正的键/值存储机制。

基本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*() { 
    yield ["key1", "val1"]; 
    yield ["key2", "val2"]; 
    yield ["key3", "val3"]; 
  } });
alert(m2.size); // 3

// 映射期待的键/值对,无论是否提供

const m3 = new Map([[]]);

alert(m3.has(undefined)); // true
alert(m3.get(undefined)); // undefined

初始化之后,可以使用 set()方法再添加键/值对。另外,可以使用 get()has()进行查询,可 以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete() clear()删除值。

const m = new Map().set("key1", "val1");
// set()方法返回映射实例,因此可以把多个操作连缀起来,包括初始化声明:
m.set("key2", "val2") .set("key3", "val3");

alert(m.size); // 3

与 Object 只能使用数值、字符串或符号作为键不同,Map 可以使用任何 JavaScript 数据类型作为 键。Map 内部使用 SameValueZero 比较操作(ECMAScript 规范内部定义,语言中不能使用),基本上相 当于使用严格对象相等的标准来检查键的匹配性。与 Object 类似,映射的值是没有限制的。

const m = new Map();

const functionKey = function() {};
const symbolKey = Symbol(); 
const objectKey = new Object();

m.set(functionKey, "functionValue");
m.set(symbolKey, "symbolValue"); 
m.set(objectKey, "objectValue");

alert(m.get(functionKey)); // functionValue
alert(m.get(symbolKey)); // symbolValue
alert(m.get(objectKey)); // objectValue

// SameValueZero 比较意味着独立实例不冲突
alert(m.get(function() {})); // undefined


// 与严格相等一样,在映射中用作键和值的对象及其他“集合”类型,在自己的内容或属性被修改时 仍然保持不变:
const objKey = {}, objVal = {}, arrKey = [], 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"]

// SameValueZero 比较也可能导致意想不到的冲突

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

SameValueZero 是 ECMAScript 规范新增的相等性比较算法。关于 ECMAScript 的相等性比较,可以参考 MDN 文档中的文章Equality comparisons and sameness

顺序与迭代

与 Object 类型的一个主要差异是,Map 实例会维护键值对的插入顺序,因此可以根据插入顺序执 行迭代操作。

// 映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以 通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器:
const m = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]);

alert(m.entries === m[Symbol.iterator]); // true

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]



// 因为 entries()是默认迭代器,所以可以直接对映射实例使用扩展操作,把映射转换为数组
console.log([...m]); 
// [[key1,val1],[key2,val2],[key3,val3]]


// 键和值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改。
const m1 = new Map([ ["key1", "val1"] ]);
// 作为键的字符串原始值是不能修改的
for (let key of m1.keys()) {
key = "newKey";
alert(key); 
alert(m1.get("key1"));
}

const keyObj = {id: 1};
const m = new Map([ [keyObj, "val1"] ]);
// 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值 
for (let key of m.keys()) {
	key.id = "newKey";
	alert(key); // {id: "newKey"}
  alert(m.get(keyObj));  // val1
}
alert(keyObj); // {id: "newKey"}

选择Object 还是 Map

对于多数 Web 开发任务来说,选择 Object 还是 Map 只是个人偏好问题,影响不大。不过,对于 在乎内存和性能的开发者来说,对象和映射之间确实存在显著的差别。

  1. 内存占用 Object 和 Map 的工程级实现在不同浏览器间存在明显差异,但存储单个键/值对所占用的内存数量 都会随键的数量线性增加。给定固定大小的内存,Map 大约可以比 Object 多存储 50%的键/值对。

  2. 插入性能 向 Object 和 Map 中插入新键/值对的消耗大致相当,不过插入 Map 在所有浏览器中一般会稍微快 一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。如果代码涉及大量插入操作,那么显然 Map 的性能更佳。

  3. 查找速度 与插入不同,从大型 Object 和 Map 中查找键/值对的性能差异极小,但如果只包含少量键/值对, 则 Object 有时候速度更快。

  4. 删除性能

    使用 delete 删除 Object 属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。。而对大多数浏览器引擎来说,Map 的 delete()操作都比插入和查找更快。 如果代码涉及大量删除操作,那么毫无疑问应该选择 Map。

WeakMap

ECMAScript 6 新增的“弱映射”(WeakMap)是一种新的集合类型,为这门语言带来了增强的键/ 值对存储机制。WeakMap 是 Map 的“兄弟”类型,其 API 也是 Map 的子集。WeakMap 中的“weak”(弱), 描述的是 JavaScript 垃圾回收程序对待“弱映射”中键的方式。

基本API

弱映射中的键只能是 Object 或者继承自 Object 的类型, 尝试使用非对象设置键会抛出 TypeError。值的类型没有限制。

如果想在初始化时填充弱映射,则构造函数可以接收一个可迭代对象,其中需要包含键/值对数组。 可迭代对象中的每个键/值都会按照迭代顺序插入新实例中

// 初始化是全有或全无的操作 // 只要有一个键无效就会抛出错误,导致整个初始化失败 
const wm2 = new WeakMap([
	[key1, "val1"],
	["BADKEY", "val2"],
	[key3, "val3"] 
]); // TypeError: Invalid value used as WeakMap key 
typeof wm2; // ReferenceError: wm2 is not defined

// 原始值可以先包装成对象再用作键 
const stringKey = new String("key1"); 
const wm3 = new WeakMap([ stringKey, "val1" ]);
alert(wm3.get(stringKey)); // "val1"

初始化之后可以使用 set()再添加键/值对,可以使用 get()has()查询,还可以使用 delete() 删除.

弱键

WeakMap 中“weak”表示弱映射的键是“弱弱地拿着”的。意思就是,这些键不属于正式的引用, 不会阻止垃圾回收。但要注意的是,弱映射中值的引用可不是“弱弱地拿着”的。只要键存在,键/值 对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。

const wm = new WeakMap(); wm.set({}, "val");

// set()方法初始化了一个新对象并将它用作一个字符串的键。因为没有指向这个对象的其他引用, 所以当这行代码执行完成后,这个对象键就会被当作垃圾回收。然后,这个键/值对就从弱映射中消失 了,使其成为一个空映射。在这个例子中,因为值也没有被引用,所以这对键/值被破坏以后,值本身 也会成为垃圾回收的目标。

// 再看一个稍微不同的例子:

const wm = new WeakMap();
const container = {
	key: {} 
};
wm.set(container.key, "val");
function removeReference() {
container.key = null; 
}
// 这一次,container 对象维护着一个对弱映射键的引用,因此这个对象键不会成为垃圾回收的目 标。不过,如果调用了 removeReference(),就会摧毁键对象的最后一个引用,垃圾回收程序就可以 把这个键/值对清理掉。

不可迭代键

因为 WeakMap 中的键/值对任何时候都可能被销毁,所以没必要提供迭代其键/值对的能力。当然, 也用不着像 clear()这样一次性销毁所有键/值的方法。WeakMap 确实没有这个方法。因为不可能迭代, 所以也不可能在不知道对象引用的情况下从弱映射中取得值。即便代码可以访问 WeakMap 实例,也没 办法看到其中的内容。

WeakMap 实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值。如果 允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。

使用弱映射

WeakMap 实例与现有 JavaScript 对象有着很大不同,可能一时不容易说清楚应该怎么使用它。这个 问题没有唯一的答案,但已经出现了很多相关策略。

  1. 私有变量

    const wm = new WeakMap();
    
    class User { 
      constructor(id) {
    	this.idProperty = Symbol('id');
    	this.setId(id);
      }
      setPrivate(property, value) { 
        const privateMembers = wm.get(this) || {};
        privateMembers[property] = value;
        wm.set(this, privateMembers);
      }
    
    	getPrivate(property) { return wm.get(this)[property]; }
    
    	setId(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 包装起来,这样就可以把弱映射与外界完全隔离开了。
    const User = (() => {
      const wm = new WeakMap();
    
      class User { 
        constructor(id) {
        this.idProperty = Symbol('id');
        this.setId(id);
        }
        setPrivate(property, value) { 
          const privateMembers = wm.get(this) || {};
          privateMembers[property] = value;
          wm.set(this, privateMembers);
        }
    
        getPrivate(property) { return wm.get(this)[property]; }
    
        setId(id) { this.setPrivate(this.idProperty, id); }
    
        getId() { 
          return this.getPrivate(this.idProperty); 
        }
      }
      return User;
    })();
    
    
  2. DOM 节点元数据

    // 因为 WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据。来看下面这个例子,其中 使用了常规的 Map:
    const m = new Map();
    
    const loginButton = document.querySelector('#login');
    
    // 给这个节点关联一些元数据
    m.set(loginButton, {disabled: true});
    // 假设在上面的代码执行后,页面被 JavaScript 改变了,原来的登录按钮从 DOM 树中被删掉了。但 由于映射中还保存着按钮的引用,所以对应的 DOM 节点仍然会逗留在内存中,除非明确将其从映射中 删除或者等到映射本身被销毁。
    
    // 如果这里使用的是弱映射,如以下代码所示,那么当节点从 DOM 树中被删除后,垃圾回收程序就 可以立即释放其内存(假设没有其他地方引用这个对象):
    
    const wm = new WeakMap();
    const loginButton = document.querySelector('#login');
    // 给这个节点关联一些元数据
    wm.set(loginButton, {disabled: true});
    

Set

ECMAScript 6 新增的 Set 是一种新集合类型,为这门语言带来集合数据结构。Set 在很多方面都 像是加强的 Map,这是因为它们的大多数 API 和行为都是共有的。

基本API

// 初始化空集合
const m = new 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);

初始化之后,可以使用 add()增加值,使用 has()查询,通过 size 取得元素数量,以及使用 delete() clear()删除元素.

与 Map 类似,Set 可以包含任何 JavaScript 数据类型作为值。集合也使用 SameValueZero 操作 (ECMAScript 内部定义,无法在语言中使用),基本上相当于使用严格对象相等的标准来检查值的匹 配性。

add()和 delete()操作是幂等的。delete()返回一个布尔值,表示集合中是否存在要删除的值:

幂等(idempotent、idempotence): 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数

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

顺序与迭代

Set 会维护值插入时的顺序,因此支持按顺序迭代。

// 集合的 entries()方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元 素是集合中每个值的重复出现:

const s = new Set(["val1", "val2", "val3"]);

for (let pair of s.entries()) { console.log(pair); } 
// ["val1", "val1"] // ["val2", "val2"] // ["val3", "val3"]

const s1 = new Set(["val1", "val2", "val3"]);

s1.forEach((val, dupVal) => alert(`${val} -> ${dupVal}`));
// val1 -> val1 // val2 -> val2 // val3 -> val3

定义正式集合操作: TODO

WeakSet

ECMAScript 6 新增的“弱集合”(WeakSet)是一种新的集合类型,为这门语言带来了集合数据结 构。WeakSet 是 Set 的“兄弟”类型,其 API 也是 Set 的子集。WeakSet 中的“weak”(弱),描述的 是 JavaScript 垃圾回收程序对待“弱集合”中值的方式。

弱集合中的值只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置值会抛出 TypeError。

初始化是全有或全无的操作, 只要有一个值无效就会抛出错误,导致整个初始化失败.

初始化之后可以使用 add()再添加新值,可以使用 has()查询,还可以使用 delete()删除

弱值

const ws = new WeakSet();

ws.add({});
// add()方法初始化了一个新对象,并将它用作一个值。因为没有指向这个对象的其他引用,所以当 这行代码执行完成后,这个对象值就会被当作垃圾回收。然后,这个值就从弱集合中消失了,使其成为 一个空集合。

const ws = new WeakSet();

const container = { val: {} };

ws.add(container.val);

function removeReference() { container.val = null; }

// 这一次,container 对象维护着一个对弱集合值的引用,因此这个对象值不会成为垃圾回收的目 标。不过,如果调用了 removeReference(),就会摧毁值对象的最后一个引用,垃圾回收程序就可以 把这个值清理掉。

不可迭代值

使用弱集合

相比于 WeakMap 实例,WeakSet 实例的用处没有那么大。不过,弱集合在给对象打标签时还是有 价值的。

const disabledElements = new Set();

const loginButton = document.querySelector('#login');

// 通过加入对应集合,给这个节点打上“禁用”标签 
disabledElements.add(loginButton);
// 这样,通过查询元素在不在 disabledElements 中,就可以知道它是不是被禁用了。不过,假如 元素从 DOM 树中被删除了,它的引用却仍然保存在 Set 中,因此垃圾回收程序也不能回收它。


const disabledElements = new WeakSet();

const loginButton = document.querySelector('#login');

// 通过加入对应集合,给这个节点打上“禁用”标签 
disabledElements.add(loginButton);
// 这样,只要 WeakSet 中任何元素从 DOM 树中被删除,垃圾回收程序就可以忽略其存在,而立即 释放其内存(假设没有其他地方引用这个对象)。


迭代与扩展操作

有 4 种原生集合类型定义了默认迭代器:

  • Array
  • 所有定型数组
  • Map
  • Set

很多其他的内置类型也实现了Iterable接口

  • 字符串
  • arguments对象
  • NodeList等 DOM集合类型

三种类型没有实现迭代器工厂函数

  • Number
  • Boolean
  • Object

这意味着上述所有定义了迭代器的类型都支持顺序迭代,都可以传入 for-of 循环。

这也意味着所有这些类型都兼容扩展操作符。