0%

学习笔记 2020 10 14

学习笔记 2020-10-14

JavaScript 高级程序设计(第4版) 阅读记录

集合引用类型

Set

  1. 顺序与迭代

    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

    SetforEach 方法与数组的类似,传入回调参数以及可选的 this 值。

  2. 定义正式集合操作
    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 新增的弱集合类型,其 APISet 的子集。

弱集合的值只能是 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 、定型数组、MapSet 定义了默认迭代器,可以传入 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 教程

任务

  1. 用箭头函数重写

    用箭头函数重写下面的函数表达式:

    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.")
    );