GithubHelp home page GithubHelp logo

javascript-es6plus-learn's Introduction

Hi there 👋

Hello

javascript-es6plus-learn's People

Contributors

clarencec avatar

Stargazers

 avatar

Watchers

 avatar

javascript-es6plus-learn's Issues

Map

Map

Map 也是在 ES6 里面新的数组类型。在 ES6 以前 Object 本质上都是健值对的集合,在键值定义上只能使用字符串做键值。其它类型作为键值都会自动转为字符串。

const data = {}
const element = document.getElementById('myDiv')

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

而 Map 也是类似于对象的键值对集合,但是键值有类型不只限于字符串,其它类型都可以当作键。

const m = new Map()
const o = {p: 'Hello World'}
m.set(o, 'content')
console.log(m) // Map(1) {{…} => "content"}

m.has(o) // true
m.delete(o) // true
m.has(0) // false

Map 也是通过 new 来构建对象

构建 Map 时,可以接受一个数组作为参数,或者任何具有 Iterator 接口的数据结构。在构建时传入的数组必须是键值对,不然会报错。

const mapArr = new Map([3, 6]) 
// Uncaught TypeError: Iterator value 1 is not an entry object

const map = new Map([
  ['name', 'cccc'],
  ['title', 'Author']
])
map.has('name') // true
map.get('name') // 'cccc'

// 同一个键值会覆盖
map.set('name', 'abc')
map.set('name', 'clarence')
map.get('name') // 'clarence'

map.get('num') // undefined

// 键值对象是通过内存地址来表示唯一的。
map.set(['a'], 666)
map.get(['a']) // undefined 看似相同对象其实是不同内存地址的。

对于基础类型的值,只要两个值严格相等 Map 就会视为一个同一个键。

const map = new Map()
map.set(-0, 123)
map.get(+0) // 123

map.set(undefined, 3)
map.set(null, 4)
console.log(map) // Map(3) {0 => "abc", undefined => 3, null => 4}

Map 实例属性和操作方法

  1. size Map 集合的长度。
  2. set(key, value)添加 Map 集合的值。可以链式调用,因为 set 方法返回的是 Map 对象。
const m = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c')
  1. get(key) 获取 Map 集合里面的 。当前没有键值,即会返回 undefined.
  2. has(key) 判断当前 Map 集合里面有没有该键,返回 Boolean。
  3. delete(key) 删除 Map 键的键值对。
  4. clear() 删除全部 Map 的成员,没有返回值。

Map 的遍历方法

  • keys(): 返回键名的可遍历器。
  • values(): 返回键值的可遍历器。
  • entries(): 返回Map键值对的可遍历器。
  • forEach(function, this): 遍历Map所有成员,和 Array.prototype.forEach 很像。forEach 的第二个参数可以用来绑定外部对象。
const map = new Map([
	['F', 'no'],
	['T', 'Yes'],
	['name', 'cc']
])

map.keys() // MapIterator {"F", "T", "name"}
for(let key of map.keys()) {
	console.log(key)
}
// 'F'
// 'T'
// 'name'

map.values() // MapIterator {"no", "Yes", "cc"}
for(let value of map.values()) {
	console.log(value)
}
// 'no'
// 'Yes'
// 'cc'

map.entries() // MapIterator {"F" => "no", "T" => "Yes", "name" => "cc"}
for(let item of map.entries()) {
	console.log(item)
}
// ["F", "no"]
// ["T", "Yes"]
// ["name", "cc"]
// 等同于 
for (let item of map) {
	console.log(item)
}

map.forEach((value, key) => {
	console.log(value, key)
})
// no F
// Yes T
// cc name

有很多时候,Set 和 Map 集合操作遍历和过滤的方法不够,比如没有 mapfilter 方法,可以通过把 Set 和 Map 转换成 Array,再操作。

const map1 = new Map()
 .set(1, 'a')
 .set(2, 'b')
 .set(3, 'c')

const map3 = new Map(
	[...map1].filter(([k, v]) => k < 3)
)
console.log(map3) // Map(2) {1 => "a", 2 => "b"}

WeakMap

WeakMap 的作用和 Map 有相同的键值集合,但它们还是有不一样的地方的。

  1. WeakMap 只能接受对象作为键名,不能使用其它类型作为键名,而 Map 可以使用任何类型作为键名。Symbol 和 null 也不能作为键名。
const map = new WeakMap()
map.set(1, 2) //Uncaught TypeError: Invalid value used as weak map key

map.set(Symbol(), 2) // Uncaught TypeError: Invalid value used as weak map key
map.set(null, 2) // Uncaught TypeError: Invalid value used as weak map key
  1. WeakSet 和 WeakMap 都是指对象弱引用的集合,意思就是当键名对象里面的数据不再被外部对象引用的时候,垃圾回收机制就会自动释放该对象所占的内存空间,不再需要对 WeakMap 对象手动删除引用。WeakMap 的传用场合就是,它的键所对应的对象,可能会在将来消失, WeakMap 结构有助于防止 内存泄漏。另外 WeakMap 弱引用只是对于键名,键值引用依然存在,需要手动清理。

WeakMap 方法

因为 WeakMap 也是没办法知道垃圾回收机制什么时候回收,所以也没办法遍历操作的,只有简单的四个方法。

  • get(key) // 通过键名,获取键值
  • set(key, value) // 设置键名和键值。
  • has(key) // 通过键名,判断集合里有没有该成员,返回 Boolean 值。
  • delete(key) // 通过键名删除集合里的成员。

WeakMap 的使用场景

使用场景主要是内存回收机制的时候减少手动清空的机会,减少内存泄漏的风险。

Set

在ES6以前,js里面只支持 Array 数组,数据保存成数组都是以 Array 形式,在 ES6后,多了两类类型 Set 和 Map。

Set

Set 的数据结构类似于数组,但是成员的值都是唯一的,没有重复的值。

const s = new Set()
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x))

for (let i of s) { //能用 for ...of 遍历 set 数组。
  console.log(i)
}
  1. Set 结构里面添加成员,是通过 add() 方法向里面添加的,并且 Set 结构不会添加重复的值。

  2. Set 函数可以接受一个数组作为参数新建对象。

const set = new Set([1, 2, 3, 4, 4])
[...set] // [1, 2, 3, 4]
set.size //  4
const set = new Set(document.querySelectorAll('div'))
  1. Set 数据数组长度是通过 set.size 来获取,和 Array.length 不一样。

  2. 可以通过 Set 数据类型进行数组去重。

[...new Set(array)] 

[...new Set('abaccc')].join('') // 'abc'
  1. 加入 Set 数据类型内部的值,不会发生类型转换。

Set 实例的属性和方法

其实 Set 是一个构造函数,

  • Set.prototype.constructor: 构造函数,指回 Set 函数。
  • Set.prototype.size: 返回 Set 实例的成员总数。

Set 实例的方法分为两大类

  1. 操作方法(用于操作数据)
  • add(value): 添加值到 Set 数组里面。
  • delete(value): 删除某个值,返回Boolean值,表示删除是否成功。
  • has(value): 返回一个布尔 值,表示该值是否为 Set 的成员。
  • clear(): 清除所有成员,没有返回值。
  1. Set 可以通过 Array.from 把数据转为数组。
const items = new Set(['a', 4, 78])
const array = Array.from(new Set(array))
  1. 遍历方法
  • keys(): 返回键名的遍历器
  • values(): 返回键值的遍历器
  • entries(): 返回键值对的遍历器
  • forEach(): 使用回调函数遍历每个成员

由于 Set 结构没有键名,只有键值,所以 keys 方法和 values 方法的行为完全一致,都是只返回键值。

let set = new Set(['red', 'green', 'blue'])

for (let item of set.keys()) {
  console.log(item) // red green blue
}

for (let item of set.values()) {
  console.log(item) // red green blue
}

for (let item of set.entries()) {
  console.log(item) //['red', 'red'] ['green', 'green'] ['blue', 'blue']
}

set.forEach((value, key) => console.log(key + ':' + value))
// red:red
// green:green
// blue:blue

WeakSet

WeakSet 结构和 Set 很相似,但是他们还是有区别的

  1. WeakSet 数组里面添加的成员,只能是对象,不能是其他类型的值。
  2. WeakSet 里面的成员对象是弱引用,当其它引用不再引用就会在内存回收机制的时候回收调里面的对象,不理 WeakSet 数组里面有引用。
  3. WeakSet 不可遍历,因为不知道回收机制什么时候触发,所以数组不是连续数组。

构造 WeakSet 和 Set 一样,需要通过 new 来造建函数,并且可以接受一个数组或者类似数组的对象作为参数。 任何具有 Iterable 接口的对象,都可以作为 WeakSet 的参数, 并且添加进 WeakSet 的成员也只能唯一,没有重复的对象成员,根据对象地址指针来判断。

const ws = new WeakSet()

const a = [[1, 2], {name: 'c', age: 28}]
const ws = new WeakSet(a) // 直接传数组作为参数,会自动把数组里面的对象转为 WeakSet 的实员对象。
const ws1 = new WeakSet([2,3]) // 如果数组里面是基础类型不是对象,一样会报错 Uncaught TypeError: Invalid value used in weak set

操作WeakSet 的方法和Set一样。

  • WeakSet.prototype.add(value): 向 WeakSet 实例添加一个新成员。
  • WeakSet.prototype.delete(value): 已经 WeakSet 指定成员。
  • WeakSet.prototype.has(value): 查找某个值是否存在 WeakSet 实例中,并返回布尔值。

但是 WeakSet 没有 size 属性,没办法遍历它的成员。

const ws = new WeakSet()
const obj = {}
const foo = {}
ws.add(window)
ws.add(obj)

ws.has(window) // true
ws.has(foo) // false 判断是根据内存地址来判断的。

for...of循环

一个数据结构只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for...of 循环遍历它的成员了。

数组

const arr = ['red', 'green', 'blue']
for (let v of arr) {
	console.log(v)
}
// red green blue
const obj = {}
obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr)
for(let v of obj) {
	console.log(v)
}
// red green blue

arr 数据的 Symbol.iterator 接口绑定arr 对象后,重新接到 obj ,返回一样是 arr 的数据。

for..infor...of 的对比

  1. for...in 是遍历集合的键名 keys 值
    for...of 是遍历集合的键值 values 值
var arr = ['a', 'b', 'c', 'd']
for (let a in arr) {
  console.log(a) // 0 1 2 3
}

for (let a of arr) {
  console.log(a) // a b c d
}
  1. for...of 只会遍历集合的具有数字索引的属性,只能遍历带有 Interator 接口的集合。

for...in 会遍历全部可以遍历的属性,和 for...in 能遍历对象,和prototype原型链上面的属性也能遍历到。

let arr = [3, 5, 7]
arr.foo = 'hello'

for (let i in arr) {
  console.log(i) // '0', '1', '2', 'foo'
}

for (let i of arr) {
  console.log(i) // 3 5 7
}

Symbol

在ES6 中引入了 Sybmol 对象,表未独一无二的值,他是新的基础数据类型,和undefinednullBooleanStringNumberObject成为第七种基础类型,属性名现在可以有两种类型:字符串、和 Symbol,对象属性名字唯一能有效防止属性名的冲突。

1. 如何创建 Symbol

创建 Symbol 时是调用 Symbol 函数生成,不需要 new ,一般传入字符串以表示对生成 symbol 对象的描述。如果相同的字符串生成的Symbol 也不会相同,因为Symbol 是的值是唯一的。

let s = Symbol()
typeof s // "symbol"

let s1 = Symbol('s')
let s2 = Symbol('s')

s1 === s2 // false

如果创建 Symbol 函数时,传的是一个对象,就会先调用该对象的 toString 方法转为字符串,再生成 Symbol 的值

const obj = {
  toString() {
     console.log('toString')
     return 'abc'
  },
  valueOf() {
    console.log('valueOf')
    return '123'
  }
}
const sym = Symbol(obj)
// toString
console.log(sym) // Symbol(abc)

2. Symbol 基础类型的转换

Symbol转换成

  • Symbol To String
    symbol 不能隐式转换,只能显示转换
let sym = Symbol('My symbol')
"your symbol is " + sym
`your symbol is ${sym}` // TypeError
String() // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
  • Symbol to Number
    Symbol 转 Number 会报 TypeError应该是拆箱时只有一个 toString 方法调用可以使用转换后类型错误。
Number(sym) // TypeError
sym + 2 // TypeError
  • Symbol to Boolean
    Symbol 可以转为Boolean值
let sym = Symbol()
let sym1 = Symbol('text')
Boolean(sym) // true
!sym // false

3. symbol 作为属性名访问和取值

要注意symbol 作为属性值名,访问或取值是都不能用点运算符,点运算符当作变量操作。

let mySymbol = Symbol('a')
let a = {}
a[mySymbol] = 'Hello' // method1
let a = { // method2
    [mySymbol]: 'Hello'
}
Object.defineProperty(a, mySymbol, { value: 'Hello' }) // method3

console.log(a[mySymbol]) // Hello
console.log(a.mySymbol) // undefined

// symbol 作为函数名
let s = Symbol()
let obj = {
    [s]: function (arg) {
      console.log(arg)
    }
}
// or 
let obj = {
      [s](arg) {
      console.log(arg)
    }
}

obj[s](123) // 123

4. symbol 可以用来作为常量,来保存常量的值都是不相等且唯一的。

const log = {}
log.levels = {
  DEBUG: Symbol('debug'),
  INFO: Symbol('info'),
  WARN: Symbol('warn')
}
console.log(log.levels.DEBUG, 'debug message')
console.log(log.levels.INFO, 'info message')

5. 属性名的遍历

正常遍历访问属性性的方法对 symbol 都不太适用。可以使用新提供的 Object.getOwnPropertySymbols 来返回 Symbol 数组。

const obj = {}
let a = Symbol('a')
let b = Symbol('b')

obj[a] = 'Hello'
obj[b] = 'World'

console.log(obj) // {Symbol(a): "Hello", Symbol(b): "World"}

// for...in  不执行
for(prop in obj) {
    console.log(prop)
}

// for...of
for(prop of obj) {
    console.log(prop) // TypeError: obj is not iterable
}

// Object.keys 不会自动转换内部属性名为字符串
Object.keys(obj) // [] 

// JSON.stringify
JSON.stringify(obj) // {}

// Object.getOwnPropertyNames
Object.getOwnPropertyNames(obj) // []

// Object.getOwnPropertySymbols
Object.getOwnPropertySymbols(obj) // [Symbol(a), Symbol(b)]

一个新的API,Reflect.ownKeys 可以返回所有类型的键名。

let obj = {
  [Symbol('key')]: 1,
  enum: 2,
  nonEnum: 3
}
console.log(Reflect.ownKeys(obj)) // ["enum", "nonEnum", "Symbol(my_key)"]

6.可以利用 symbol 属性不会被常规方法遍历,定义对象非私有方法。

let size = Symbol('size')
class Collection {
  constructor() {
    this[size] = 0
  }
  add(item) {
    this[this[size]] = item
    this[size]++
  }
  stateic sizeOf(instance) {
    return instance[size]
  }
}

let x = new Collection()
Collection.sizeOf(x) // 0

Object.keys(x) // ['0']
Object.getOwnPropertyNames(x) // ['0']
Object.getOwnPropertySymbols(x) // [Symbol(size)]

7. Symbol.for()Symbol.keyFor()Symbol() 的区别

  1. Symbol.for() 接收一个字符串参数值,如果已经存在这个字符串的 Symbol 值,则登记该值,并返回相同的 Symbol 值,否则新建一个 该字符串的 Symbol 值.而 Symbol() 每次新建都会返回不同的 Symbol 值,就算 字符串一样.
let s1 = Symbol.for('foo') // 创建 Symbol('foo')
let s2 = Symbol.for('foo') // 获取 Symbol('foo')

s1 === s2 // true

let s3 = Symbol('foo')
let s4 = Symbol('foo')

s3 === s4 // false
  1. Symbol.keyFor() 检查 Symbol.for 登记后的值。
let s1 = Symbol.for('SymbolText')
Symbol.keyFor(s1) // SymbolText

let s2 = Symbol('SymbolText1')
Symbol.keyFor(s2) // undefined

8. 内置的 Symbol 值

ES6 后暴露了11个语言内部的Symbol使用方法出来.

  1. Symbol.hasInstance
    Symbol.hasInstance 是一个对象内部的属性,指向一个内部的使用方法,当其它对象调用 instanceof 运算符的时候,就会调用其方法.
class Even {
  static [Symbol.hasInstance](obj) {
    return Number(obj) % 2 === 0 // 判断是否偶数
  }
}

1 instanceof Even // false
2 instanceof Even // true
12345 instanceof Even // false
  1. Symbol.isConcatSpreadable
    Symbol.isConcatSpreadable 是一个对象内的属性设置开关,是一个 Boolean 值,如果设置为 false 就不会再在Array.prototype.concat()方法时把数组展开。
let arr1 = ['c', 'd']
let arr = ['a', 'b']
arr.concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] //  undefined

arr1[Symbol.isConcatSpreadable] = false
arr.concat(arr1, 'e') // ['a','b',['c','d'],'e']
arr1[Symbol.isConcatSpreadable] = true
arr.concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
  1. Symbol.species

对像的 Symbol.species 属性,指向一个构造函数,创建衍生时会使用到该属性。在不修改 Symbol.species 的情况下等同于下面的操作。

class T1 extends Array {
  static get [Symbol.species]() {
    return this
  }
}

自定义修改 Symbol.species 操作:

class T1 extends Array {}
class T2 extends Array {
  static get [Symbol.species] () {
    return Array
  }
}

const ObjT1 = new T1()
const ObjT2 = new T2()


ObjT1 instanceof Array // false
ObjT2 instanceof Array // true
  1. Symbol.match
    对象的 Symbol.match 属性,指向一个函数. String.match() 一般用来匹配字符串正则表达式。
class MyMatcher (
  [Symbol.match](string) {
    return 'hello world'.indexOf(string)
  }
)
'e'.match(new MyMatcher()) // 1
  1. Symbol.replace
    对象的 Symbol.replace 属性,指向 String.prototype.replace 方法。
const x = {}
x[Symbol.replace] = (...s) => console.log(s)
'Hello'.replace(x, 'World') // ['Hello', 'World']
  1. Symbol.search
    对像的 Symbol.search 属性指向 String.prototype.search 方法。用来查找正则表达式的位置。
String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this) 的调用

class MySearch {
  constructor(value) {
    this.value = value
  }
  [Symbol.search] (string) {
    return string.indexOf(this.value)
  }
}
'foorbar'.search(new MySearch('bar')) // 4
  1. Symbol.split

对象的 Symbol.split 属性,指向方法 String.prototype.split

String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)
  1. Symbol.iterator
    对象 Symbol.iterator 属性,指向该对象的默认遍历器方法,对对象进行 for...of 循环时,会调用 Symbol.iterator 方法。比较重要可以通过 Symbol.iterator 重定义遍历方法。
class Collection {
  *[Symbol.iterator] () {
     let i = 0
     while(this[i] ! == undefined) {
        yield this[i]
        ++i
      }
  }
}

let myCollection = new Collection()
myCollection[0] = 1
myCollection[1] = 2
myCollection[2] = 'text'

for (let value of myCollection) {
   console.log(value) // 1 2 hello
}
  1. Symbol.toPrimitive
    对象的 Symbol.toPrimitive 属性,也是指向一个方法。该对象被转为原始类型的值时会调用这个方法。
    在调用 Symbol.toPrimitive 自定义方法时,会传一个字符串参数 hint 进去,表示当前运行的模式。有以下三种模式:
  • Number: 需要转成数值的模式。
  • String: 需要转成字符串的模式。
  • Default: 默认模式可以转成数值,也可以转成字符串。
let obj = {
  [Symbol.toPrimitive](hint) {
    switch (hint) {
      case 'number':
        return 123
      case 'string':
        return 'str'
      case 'default':
        return 'default'
      default:
        throw new Error()
    }
  }
}
2 * obj // 246
'Hello' + obj // Hellodefault
obj == 'default' // true
obj === 'default' // false
String(obj) // 'str'
  1. Symbol.toStringTag
    对象的 Symbol.toStringTag 属性,指向一个方法,调用 Object.prototype.toString 方法时, 可以重新定义返回的字符串中的值.
class Collection {
  get [Symbol.toStringTag] () {
    return 'xxx'
  }
}
let x = new Collection()
Object.prototype.toString.call(x) // [Object xxx]
  1. Symbol.unscopables
    对象的 Symbol.unscopables 属性,指向一个对象,该对象指定了 with 关键字,哪些属性会被 with 环境排除。 with 是比较少使用的 JavaScript 关键词,用为将某个对象添加到作用域连的顶部,然后在这作用域下面运行。
var a, x, y
var r = 10
with (Math) {
 a = PI * r * r
 x = r * cos(PI)
 y = r * sin(PI / 2)
}
class MyClass {
  foo() {return 1}
}

var foo = function() {return 2}
with (MyClass.prototype) {
  foo() // 1
}

// 通过 unscopable 修改当前作用域,使 `with` 语法块不会在当前作用域寻找`foo` 属性,指向外部 `foo` 。
class MyClass {
  foo() { return 1 }
  get [Symbol.unscopables] () {
    return { foo: true }
  }
}

var foo = function() { return 2 }

with (MyClass.prototype) {
  foo() // 2
}

Async

Async

async 函数是什么?可以理解为他就是 Generator 函数的语法糖。

下面分别使用 GeneratorAsync来异步读取两个文件。这其实发现 Async 和 Generator 很像。

const fs = require('fs')
const readFile  = function (fileName) {
  return new Promise(function (resolve, reject) {
    fs.readFile(fileName, funcition(error, data) {
         if (error) return  reject (error)
         resolve(data)
     })
   })
}
// 使用 Generator 函数
const gen = function*  () {
  const f1 = yield readFile('/etc/fstab')
  const f2 = yield readFile('/etc/shells')
  console.log(f1.toString())
  console.log(f2.toString())
}
gen.next()
gen.next()
// 使用 Async
const asyncReadFile = async function () {
  const f1 = await readFile('/etc/fstab')
  const f2 = await readFile('/etc/shells')
  console.log(f1.toString())
  console.log(f2.toString())
}
asyncReadFile()

需然 asyncGenerator很像,但是还是有不同的。

  1. async 函数不需要执行器,直接调用可以会自动执行最后结果。不像 Generator 函数需要调用 next 方法。
  2. 语义上的更好理解, async 表示函数里有异步操作, await 表示紧跟后面的表达式需求等待结果。
  3. Generator yield 命令后面只能 是 Thunk 函数或者 Promise 对象,async 的 await 后面,可以接 Promise 对象和各原始类型的值(原始类型的值会自动转换为 Promise.resolved 形式)
  4. Generator 返回的是 Iterator 集合需要手动遍历,async 返回的是 Promise 对象。

async 的基本用法

async 函数会返回一个 Promise 对象,可以使用 then 方法 添加回调函数。当函数执行的时候,遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

  async function timeout(ms) {
    await new Promise ((resolve) => {
      setTimeout(resolve, ms)
   })
 }
async function asyncPrint(value, ms) {
  await timeout(ms)
  console.log(value)
}

asyncPrint('hello world', 300)

async 语法

async 函数运行的过程中会返回一个 Promise 对象, 如果 async 函数内部有 return 的值,会成为 then 函数的参数。如果 async 函数里面抛错误,会被外层的 catch 接受到。async 函数内部的状态必须等到所有 await 命令后 Promise 对象执行完,才会发生async 函数状态的改变,除非遇到 return 或者错误。

async function f() {
  throw new Error('Error Msg')
}
f()
 .then(v => console.log(v))
 .catch(e => console.log(e))
  1. 如果 await 后面不是一个 Promise 对象,而是基础类型,则会直接返回基础类型。

  2. 如果 await 后面是一个 thenable 对象[即定义了 then 方法的对象], 则会等同于 Promise 对象,调用对象里面的 then 方法。

class Sleep {
	constructor(timeout) {
		this.timeout = timeout
	}
	then(resolve, reject) {
		const startTime = Date.now()
		setTimeout(
			() => resolve(Date.now() - startTime),
			this.timeout
		)
	}
}
(async() => {
	const actualTime = await new Sleep(1000)
	console.log(actualTime)
})()

async 错误处理

在 async 错误处理中,如果 async 函数里面其中一个 await 异步操作错误返回 Promise.reject,整个 async 返回的 Promise 都会返回 reject,所以如果其中一个 await 错误就不能继续执行后面的 await 了。所以如果不想整个 async 函数都处于 reject 状态,需要处理内部 await 的错误处理.

  1. try...catch 可以通过 try...catch 单独包裹 await 操作。
async function main() {
	try {
		const val1 = await firstStep()
         }
	catch (err) {
		console.error(err)
	}
	const val2 = await secondStep(val1)
	const val3 = await thirdStep(val1, val2)
	console.log('Final: ', val3)
}
  1. 通过 Promise.catch 来处理 await 错误.
async function f() {
	await Promise.reject('ErrorMsg')
		.catch(e => console.log(e))
	return await Promise.resolve('hello world')
}
f().then(v => console.log(v))
// ErrorMsg
// hello world
  1. 如果 try...catch 包裹整个模块,await 中断时整个 async 也会中断,起不到执行下一个 async 效果。
async function main() {
	try {
		const val1 = await firstStep()
                const val2 = await secondStep(val1)
	        const val3 = await thirdStep(val1, val2)
         }
	catch (err) {
		console.error(err)
	}
	
	console.log('Final: ', val3)
}

async 写法注意点。

1. async 函数内部每个 await 最好每个后面都捕获错误对象。

async function myFunction() {
	try {
		await somethingThatReturnAPromise()
	} catch (err) {
		console.log(err)
	}
}
// or

async function myFunction() {
	await somethingThatReturnsAPromise()
		.catch((err) => {
			console.log(err)
		})
}

2. 多个 await 命令后面的异步操作,如果不存在继发关系,最好同时触发异步。

let [foo, bar] = await Promise.all([getFoo(), getBar()])
// or 提前运行 Promise 函数
let fooPromise = getFoo()
let barPromise = getBar()
let foo = await fooPromise
let bar = await barPromise

3. await 只能用在 async 函数中,用在其它普通函数就会报错。

4. async 函数可以保留运行堆栈。
如果b或c函数报错, 错误堆栈将包括 a(),a是暂停执行, 上下文环境都保存着。

const a = async () => {
  await b()
  c()
}

async await 使用场景

有很多时候总觉得 async 被滥用,把 async 当成是 Promise 的使用,这其实是对 async 函数的不理解。因为主要是 async 是 Generator 的语法糖,是用来管理批量执行异步函数的。主要是以下三种场景。

  1. 异步继发执行。
    当下一个异步依赖上一个异步的结果的时候 async await 写法会更为简洁。
function getUrls() {
  let url3
  getUrl()
  .then((url1) => {
     getUrl(url1)
       .then(url2 => {
           getUrl(url2)
              .then(url3 => {
               url3 = url3
           })
        })
  })
  return url3
}

async function getUrls() {
  let url1 = await getUrl()
  let url2 = await getUrl(url1)
  let url3 = await getUrl(url2)
  return url3
}
  1. 异步并发执行。
    异步并发执行除了可以使用 Promise.all 外还可以使用 async await
async function getABC() {
 let results = await Promise.all([ getValueA, getValueB, getValueC])
 return results
}

async function getABC() {
  let valueA = getValueA()
  let valueB = getValueB()
  let valueC = getValueC()
  let a = await valueA
  let b = await valueB
  let c = await valueC
  return [a, b, c]
}
  1. 异步继发并发混杂执行。

继发和批量执行同时在执行也是没问题的,使用 async 可以加构自动化。

Decorator

Decorator 装饰器 [Stage-2]

关于 TC39 提案流程

TC39 提案分为以下几个阶段:

  • Stage 0 - 设想(Strawman):只是一个想法,经过 TC39 成员提出即可。
  • Stage 1 - 提案(Proposal):初步尝试。
  • Stage 2 - 草案(Draft):完成初步规范。
  • Stage 3 - 候选(Candidate):完成规范并在浏览器上初步实现。
  • Stage 4 - 完成(Finished):将添加到下一个年度版本发布中。

Iterator 遍历器

Iterator 遍历器的概念和实现

遍历器 (Iterator) 的主要想法是提供一种接口或者机制为 JavaScript,统一处理各种不同的数据结构。在 ES6 后,JavaScript 有共有了四种集合 Array (数组) 、对象 (Object)、Map 、Set都是 可以使用 Iterator 形式访问的,ES6 新提供的 for...of 可以遍历有 Iterator 机制的所有集合。

下面模拟一下 Iterator 实现,返回一个指针对象,对象有一个方法 next,每次调用 next,都会返回当前成员的信息,返回的信息有两个属性 value 当前成员的值, done 一个标志遍历是否结束的值是一个 Boolean 类型的。

var it = makeIterator(['a', 'b'])

it.next() // { value: 'a', done: false }
it.next() // { value: 'b', done: false}
it.next() // { value: undefined, done: true}

function makeIterator(array) {
  var nextIndex = 0
  return {
     next: function() {
      return nextIndex < array.length ?
        {value:  array[nextIndex++], done:  false}  : 
        {value: undefined, done:  true}
    }
  }
}

// 对于遍历器来说 `done: false` 和 `value: undefined` 属性都是可以省略的,所以上面的方法可以简写成下面的形式

function makeIterator(array) {
  let nextIndex = 0
  return {
     next: function() {
        return nextIndex < array.length ?
           { value: array[nextIndex++] } : 
           { done: true }
     }
  }
}

可以看出其实上面的数据是通过函数内部方法对应集合出来的,与集合数据一一对应,其实也可以直接从 Iterator 接口模拟出自定义的数据结构,把集合和遍历出来的结果分离。

var it =  idMarker()
it.next().value // 0
it.next().value // 1
it.next().value // 2

function idMaker() {
  var index = 0
  return {
      next: function () {
          return { value: index++, done: index > 10? true : false}
      }
  }
}

可遍历的 Iterator

在ES6 中,暴露了一部分Symbol 属性方法,Symbol.iterator 也是其中之一。当遍历一个数据结构时,只要配置有 Symbol.iterator 属性,并且返回的对象具有 next方法,调用 next 方法,会 返回具有 valuedone 两个属性的对象特征,就被认为是可遍历的。

const  obj = {
  [Symbol.iterator] : function () {
    return {
      next: function () {
         return {
           value: 1,
           done: true
         }
      }
    }
  }
}

原生具备 Iterator 接口的数据结构有下面这些:

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

才发现原来 Object 对象并不是原生拥有可遍历接口的,原因是对象保存的属性不是线性的,任何非线性的数据结构,部署遍历器接口,等于部署一种线性转换。

下面尝试把 Object 对象添加 Iterator 接口

class RangeIterator {
	constructor(start, stop) {
		this.value = start;
		this.stop = stop;
	}
	[Symbol.iterator]() { return this; }
	next() {
		var value = this.value;
		if (value < this.stop) {
			this.value++;
			return {done: false, value: value};
		}
		return {done: true, value: undefined}
	}
}
var rangeObject = new RangeIterator(0, 5)
// 如果 value 不给变量定义词 let 或 var 会,变成全局变量
for(value of rangeObject) {
	console.log(value); // 0 1 2 3 4
} 

可以调用 Iterator 接口的场合

  1. 对数组和 Set 结构进行解构赋值的时候,默认会调用 Symbol.iterator 方法。
let set = new Set().add('a').add('b').add('c')
let [x, y] = set // x='a'; y='b'

let [first, ...rest] = set
// first='a'; rest=['b','c']
  1. 扩展运算符
    扩展运算符也会调用 Iterator 接口。只要某对象配置了 Iterator 接口数据结构,就可以用扩展运算符转为数组。
var str = 'hello'
[...str] // ['h', 'e', 'l', 'l', 'o']

let arr = ['b', 'c']
['a', ...arr, 'd'] // ['a', 'b', 'c', 'd']
  1. yield* 协程后面跟的是一个可 遍历的结构,它会调用该结构的遍历器接口。
let generator = function* () {
  yield 1;
  yield* [2, 3, 4];
  yield 5;
}
var iterator = generator()
iterator.next() // { value: 1, done: false}
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false}
iterator.next() // { value: 4, done: false}
iterator.next() // { value: 5, done: false}
iterator.next() // { value: undefined,  done: true}

4.其它任何接受数组作为参数和场合,都可以使用 Iterator 接口.

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()
  • Promise.all()
  • Promise.race()

字符串对象具备 Iterator 接口

var someString = "Hi"
[...someString] // ["H", "i"]
var iterator = someString[Symbol.iterator]
iterator.next() // { value: "H", done: false }
iterator.next() // { value: "i", done: false}
iterator.next() // { value: "undefined", done: true}

可以利用 Symbol.iterator 方法改写,遍历器的行为。

var str = new String('hi')
str[Symbol.iterator] = function() {
	return {
		next: function() {
			if (this._first) {
				this._first = false
				return { value: "bye", done: false }
			} else {
				return { done: true }
			}
		},
		_first: true,
	}
}
[...str] // ["bye"]
str // 'hi'

遍历器对象可以配置 return(), throw() 函数来处理其它情况。

return 一般用来处理遍历时出现的错误。
throw 一般用来配合 Generator 函数使用。一般遍历器使用不到这个方法。

像下面这个异步读取文件的函数,在遍历器对象配置了 return 函数,当for...of遇到出错或者 break 时,会立刻清理或释放 file 资源。

function readLinesSync(file) {
  return {
    [Symbol.iterator]() {
      next() {
        return { done: false }
      },
      return () {
        file.close()
        return { done: true }
      }
    }
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.