[TOC]

# ES6

资料

# Set

  • Set函数可以接受一个数组或者具有iterable接口的其他数据结构作为参数
const set = new Set([1, 2, 3])
[...set]
// [1, 2, 3]

const set = new Set(document.querySelectorAll('div'))
  • 数组去重、字符串去重
[...new Set(array)]
[... new Set('ababbc')].join('')
  • 向Set加入值的时候不会发生类型转换,所以5和"5"是不同的值。与精确相等运算符(===)不同的是,Set认为NaN等于自身,精确运算符认为NaN不等于自身。
let set = new Set()
let a = NaN
let b = NaN
set.add(a)
set.add(b)
set // Set { NaN }
  • 两个对象总是不相等
let set = new Set()
set.add({})
set.add({})
set.size // 2
  • Set实例的属性和方法
    • size
    • add(value):返回Set结构本身,可以链式调用
    • delete(value):返回布尔值
    • has(value)
    • clear()
    • keys():结果与values()一样
    • values()
    • entries()
    • forEach(func)
  • Array.from可以将Set结构转为数组
const items = new Set([1, 2, 3, 4, 5])
const array = Array.from(items)
  • 扩展运算符(...)内部使用for of循环,因此可以用于Set结构

# WeakSet

  • 与Set的区别

    • 成员只能是对象
    • WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用
  • WeakSet不可遍历

  • 以下例子中,成为WeakSet实例对象成员的是变量a的成员,这意味着变量成员必须是对象

// OK
const a = [[1, 2], [3, 4]]
const ws = new WeakSet(a)

// Error
const b = [3, 4]
const ws = new WeakSet(b)
  • 方法:
    • 没有size属性,因为无法遍历
    • add(value)
    • delete(value)
    • has(value)
  • 使用场景
    • 存储DOM节点,当这些节点从文档移除时,不会引发内存泄露
    • 使用场景 (opens new window)
      • TL;DR:
        • 标记通过proxy工厂函数创建的对象
        • 保证实例的方法只能由相应的实例调用

# Map

  • Map数据结构可以解决键名只能为字符串的问题
// 键名会自动转为字符串
const data = {};
const element = document.getElementById('myDiv');

data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"

// 通过对象作为键获得值
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"
  • 可以接受一个数组作为构造函数的参数
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
  • 任何具有Iterator接口、且每个成员都是一个双元素的数组的数组结构都可以作为参数
const set = new Set([
  ['foo', 1],
  ['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // 1

const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
  • 对相同键赋值,会覆盖之前对值
  • 只有对同一个对象的引用,才视为同一个键(相同内存地址)
const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined
  • 实例的属性和方法
    • size
    • set(key, value):返回Map对象,可以采用链式调用
    • get(key)
    • has(key)
    • delete(key)
    • clear()
    • keys()
    • values()
    • entries()
    • forEach()
  • Map的遍历顺序就是插入顺序
  • 可以通过扩展运算符将Map快速转位数组
  • Map本身没有map和filter方法,但是可以结合数组的map和filter方法进行过滤和遍历
const map0 = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

const map1 = new Map(
  [...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}

const map2 = new Map(
  [...map0].map(([k, v]) => [k * 2, '_' + v])
    );
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}

# WeakMap

  • WeakMap只接受对象作为键名(null除外)
  • WeakMap键名指向的对象不计入垃圾回收机制
  • WeakMap没有size属性和遍历操作
  • 方法
    • get()
    • set()
    • has()
    • delete()
  • 用途 (opens new window)
    • 缓存计算结果
    • 管理listeners
    • 保存私有数据

# Promise

  • 传入resolve的参数可以是Promise类型,此时该Promise实例的状态取决于该参数。下面例子中,如果p1的状态为resolved,则p2的回调函数立刻执行;如果p1点状态为pending,则p2的状态也为pending
const p1 = new Promise(function (resolve, reject) {
  // ...
});

const p2 = new Promise(function (resolve, reject) {
  // ...
  resolve(p1);
})
  • then方法返回的是一个Promise实例,因此可以采用链式调用,如果前一个then中返回的是一个Promise类型,那么后一个回调函数就会等待该Promise对象状态变化再进行调用

  • Promise对象的错误具有“冒泡”性质,链式调用的then中,其中一个出错,都会被最后的catch所捕获

  • finally在ES2018中引入,不管Promise实例最后的状态是什么,都会执行,但是无法从finally中知道该实例的状态

  • Promise.all接受一个数组作为参数(或者参数具有Iterator接口,并且每个返回的成员都是Promise实例),如果参数不是Promise实例就会先调用Promise.resolve进行转换

  • 如果Promise.all中实例都fulfilled,实例的返回值就会组成一个数组传递给回调函数

  • 如果作为参数的Promise实例,定义了自己的catch方法,那么它一旦被rejected,就不会出发Promise.all的catch方法

  • Promise.resolve等价于以下写法

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
  • Promise.resolve的参数可以是一个Promise、thenable对象、不是thenable对象或不是对象、不带任何参数

# async

  • 返回Promise对象
  • 内部的return语句会成为then方法回调函数的参数
  • await后面是一个Promise对象并返回该对象的结果,否则直接返回对应的值(也可以是具有then方法的对象)
  • 任何一个await语句后面的Promise对象变为reject状态,整个async函数都会中断执行,如果希望不会中断,要是用try、catch结构
  • 如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject
  • async函数可以保留错误堆栈

# Iterator

  • 默认的Iterator接口部署在数据结构的Symbol.iterator属性。
  • Symbol.iterator属性本身是一个函数,该函数返回一个具有next方法的对象。
const obj = {
  [Symbol.iterator]: function() {
    return {
      next: function() {
        return {
          value: 1,
          done: true
        }
      }
    }
  }
}
  • 具备Iterator接口的数据结构,可以被for ... of循环遍历。
  • 原生具备Iterator接口的数据结构:
    • Array
    • Map
    • Set
    • String
    • TypedArray
    • 函数的arguments对象
    • NodeList对象
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next(); // { value: 'a', done: false }
iter.next(); // { value: 'b', done: false }
iter.next(); // { value: 'c', done: false }
iter.next(); // { value: undefined, done: true }
  • 调用Iterator接口的场合
    • 对数组和Set结构进行解构赋值
    • 扩展运算符
    • yield*。yield*后面跟的是一个可遍历结构的话,会调用该结构的遍历器接口。
    • for of
    • Array.from()
    • Promise.all()
    • Promise.race()
  • 除了next方法,遍历器对象还有可选的return和throw方法。在for of循环中有break和throw new Error()都会触发return方法。return方法返回一个对象,是Generator规定的。
function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      return {
        next() {
          return { done: false };
        },
        return() {
          file.close();
          return { done: true };
        }
      };
    },
  };
}

// 情况一
for (let line of readLinesSync(fileName)) {
  console.log(line);
  break;
}

// 情况二
for (let line of readLinesSync(fileName)) {
  console.log(line);
  throw new Error();
}
  • entries()、keys()、values()返回遍历器对象。
  • 对于字符串来说,for of会正确识别32位的UTF-16字符。
  • 并不是所有类数组的对象都具有Iterator接口,解决方法就是调用Array.from将其转换为数组。
  • forEach没有办法通过break或者return命令跳出循环。

# Symbol

  • 基本类型,不能使用new
  • 参数可以为字符串,相同参数的Symbol返回值不相同
  • Symbol可以转布尔值和字符串,不能转数值
  • ES2019提供description方法,直接返回Symbol描述
  • Symbom作为对象属性名,不能用点运算符,因为点运算符后面总是字符串
const mySymbol = Symbol();
const a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
  • 在对象的内部,使用symbol值定义的属性要放在方括号内
let s = Symbol();

let obj = {
  [s]: function (arg) { ... }
};
  
// 或
  
let obj = {
  [s](arg) { ... }
};


obj[s](123);
  • 实例
    • 消除魔术字符串
  • Symbol属性不会出现在for in、for of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回,可以通过Object.getOwnPropertySymbols获取。Reflect.ownKeys也可以访问Symbol的键名,包括常规键名。
  • Symbol.for()可以接受一个字符串参数,搜索是否有一该参数为名的Symbol值,有则返回,无则新建并返回该Symbol对象。
  • Symbol.keyFor(obj)返回一个已登记的Symbol对象的key
  • 内置Symbol值:
    • Symbol.hasInstance
    • Symbol.isConcatSpreadable
    • Symbol.species
    • Symbol.match
    • Symbol.replace
    • Symbol.search
    • Symbol.search
    • Symbol.split
    • Symbol.iterator
    • Symbol.toPrimitive
    • Symbol.toStringTag
    • Symbol.unscopables