学习笔记 2020-10-14
JavaScript 高级程序设计(第4版) 阅读记录
集合引用类型
Set
顺序与迭代
Set
会维护值插入时的顺序,支持按顺序迭代。可以通过values()
keys()
方法或是Symbol.iterator
取得这个迭代器。const s = new Set(['val1', 'val2', 'val3']); console.log(s.values === s[Symbol.iterator]); // true console.log(s.keys === s[Symbol.iterator]); // true for (let value of s.values()) { console.log(value); } // val1 val2 val3 for (let value of s[Symbol.iterator]()) { console.log(value); } // val1 val2 val3
values
是默认迭代器,可以通过拓展操作将集合转换为数组。entries
方法返回一个迭代器,按照插入顺序产生包含两个元素的数组。const s = new Set(['val1', 'val2', 'val3']); for (let pair of s.entries()) { console.log(pair); } // ["val1", "val1"] // ["val2", "val2"] // ["val3", "val3"]
console.log(Set.prototype); // add: ƒ add() // clear: ƒ clear() // constructor: ƒ Set() // delete: ƒ delete() // entries: ƒ entries() // forEach: ƒ forEach() // has: ƒ has() // keys: ƒ values() // size: (...) // values: ƒ values() // Symbol(Symbol.iterator): ƒ values() // Symbol(Symbol.toStringTag): "Set" // get size: ƒ size() // __proto__: Object
Set
的forEach
方法与数组的类似,传入回调参数以及可选的this
值。定义正式集合操作
class XSet extends Set { union(...sets) { return XSet.union(this, ...sets); } intersection(...sets) { return XSet.intersection(this, ...sets); } difference(set) { return XSet.difference(this, set); } symmetricDifference(set) { return XSet.symmetricDifference(this, set); } cartesianProduct(set) { return XSet.cartesianProduct(this, set); } powerSet() { return XSet.powerSet(this); } // 返回两个或更多集合的并集 static union(a, ...bSets) { const unionSet = new XSet(a); for (const b of bSets) { for (const bValue of b) { unionSet.add(bValue); } } return unionSet; } // 返回两个或更多集合的交集 static intersection(a, ...bSets) { const intersectionSet = new XSet(a); for (const aValue of intersectionSet) { for (const b of bSets) { if (!b.has(aValue)) { intersectionSet.delete(aValue); } } } return intersectionSet; } // 返回两个集合的差集 static difference(a, b) { const differenceSet = new XSet(a); for (const bValue of b) { if (a.has(bValue)) { differenceSet.delete(bValue); } } return differenceSet; } // 返回两个集合的对称差集 static symmetricDifference(a, b) { // 按照定义,对称差集可以表达为 return a.union(b).difference(a.intersection(b)); } // 返回两个集合(数组对形式)的笛卡儿积 // 必须返回数组集合,因为笛卡儿积可能包含相同值的对 static cartesianProduct(a, b) { const cartesianProductSet = new XSet(); for (const aValue of a) { for (const bValue of b) { cartesianProductSet.add([aValue, bValue]); } } return cartesianProductSet; } // 返回一个集合的幂集 static powerSet(a) { const powerSet = new XSet().add(new XSet()); for (const aValue of a) { for (const set of new XSet(powerSet)) { powerSet.add(new XSet(set).add(aValue)); } } return powerSet; } }
WeakSet
ES6
新增的弱集合类型,其 API
是 Set
的子集。
弱集合的值只能是 Object
或是继承自 Object
的类型。
const val1 = { id: 1 },
val2 = { id: 2 },
val3 = { id: 3 };
// 使用数组初始化弱集合
const ws1 = new WeakSet([val1, val2, val3]);
console.log(ws1.has(val1)); // true
console.log(ws1.has(val2)); // true
console.log(ws1.has(val3)); // true
// 初始化是全有或全无的操作
// 只要有一个值无效就会抛出错误,导致整个初始化失败
const ws2 = new WeakSet([val1, 'BADVAL', val3]);
// TypeError: Invalid value used in WeakSet
typeof ws2;
// ReferenceError: ws2 is not defined
// 原始值可以先包装成对象再用作值
const stringVal = new String('val1');
const ws3 = new WeakSet([stringVal]);
console.log(ws3.has(stringVal)); // true
可用的方法:
add()
has()
delete()
WeakSet
同样可以链式调用,其值不属于正式引用,不会影响垃圾回收,没有迭代能力。可以用于标记 DOM
元素是否处于某种状态。
迭代与拓展操作
ES6
中新增了迭代器与拓展操作符。
Array
、定型数组、Map
、Set
定义了默认迭代器,可以传入 for-of
循环。
let iterableThings = [
Array.of(1, 2),
(typedArr = Int16Array.of(3, 4)),
new Map([
[5, 6],
[7, 8]
]),
new Set([9, 10])
];
for (const iterableThing of iterableThings) {
for (const x of iterableThing) {
console.log(x);
}
}
// 1
// 2
// 3
// 4
// [5, 6]
// [7, 8]
// 9
// 10
同样的,这些类型都兼容拓展操作符。
拓展操作符可用于可迭代对象执行浅复制。
let arr1 = [1, 2, 3];
let arr2 = [...arr1];
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3]
console.log(arr1 === arr2); // false
let arr1 = [{}];
let arr2 = [...arr1];
arr1[0].foo = 'bar';
console.log(arr2[0]); // { foo: 'bar' }
let arr1 = [1, 2, 3];
// 把数组复制到定型数组
let typedArr1 = Int16Array.of(...arr1);
let typedArr2 = Int16Array.from(arr1);
console.log(typedArr1); // Int16Array [1, 2, 3]
console.log(typedArr2); // Int16Array [1, 2, 3]
// 把数组复制到映射
let map = new Map(arr1.map(x => [x, 'val' + x]));
console.log(map); // Map {1 => 'val 1', 2 => 'val 2', 3 => 'val 3'}
// 把数组复制到集合
let set = new Set(typedArr2);
console.log(set); // Set {1, 2, 3}
// 把集合复制回数组
let arr2 = [...set];
console.log(arr2); // [1, 2, 3]
迭代器与生成器
循环是迭代机制的基础,可以指定迭代次数以及每次迭代的操作。
迭代会在一个有序集合上进行。此处有序理解为集合中所有项都可以按照既定的顺序被遍历到,特别是开始和结束项有明确的定义。
循环迭代不是理想的机制,我们需要事先知道如何使用数据结构,通过递增索引来访问数据是数组独有的方式,并不通用。
ES5
新增了 Array.prototype.forEach
方法,解决了单独记录索引来取值的问题,但没有办法标识迭代何时终止。
现代 JavaScript 教程
任务
用箭头函数重写
用箭头函数重写下面的函数表达式:
function ask(question, yes, no) { if (confirm(question)) yes() else no(); } ask( "Do you agree?", function() { alert("You agreed."); }, function() { alert("You canceled the execution."); } );`
let ask = (question, yes, no) => confirm(question) ? yes() : no(); ask( "Do you agree?", () => alert("You agreed."), () => alert("You canceled the execution.") );