ECMAScript新特性


后海有树的院子,夏代有工的;此时此刻的云,二十来岁的你。
——冯唐《可遇不可求的事》


在这里插入图片描述



一、概述

ECMAScript是JavaScript的语言本身

  • 通过看做JavaScript的标准化规范, 实际上JavaScript 是ECMAScript的扩展语言, 因为ECMAScript只提供了最基本的语法
  • ES2015开始按照年份命名,不再按照版本号命名(ES6 为 ES2015)

在这里插入图片描述

基于ES5.1(ES6)的变化:

  1. 解决原有语法上的一些问题或者不足
  2. 对原有语法进行增强,使之变得更加便捷,易用
  3. 全新的对象、全新的方法、全新的功能
  4. 全新的数据类型和数据结构

二、方法


1. let、const

作用域: 全局作用域、函数作用域、块级作用域(新增)

实践: 能用 const 就用 const,需要被修改的值用 let, 万不得已不要用 var

2. 解构

⑴. 数组的解构
// 数组结构

const arr = [1, 2, 3, 4]

// 根据数组中元素的位置实现赋值
const [a, b, c, d] = arr
console.log(b)
// => 2

// 参数可以为空
const [, w, ,] = arr
console.log([, w, ,])
// => [ <1 empty item>, 2, <1 empty item> ]

// ... => 提取当前元素往后的所有成员
const [foo, ...rest] = arr
console.log(rest)
// => [ 2, 3, 4 ]

// 只会记录数组对应的脚标赋值, 也可以增删数组的值
const [fo, ba = '123', va, pa] = arr
console.log([ba, va, pa, more = '123'])
// => [ 2, 3, 4, '123' ]

⑵. 对象的解构
// 对象的解构

const userInfo = {
  name: '张三',
  age: 34,
  sex: 1
}

// // 根据对象的变量名,匹配对象成员,获取指定的值
// const { name } = userInfo
// console.log(name)
// // => 张三

// 解构的变量名必须是对象内成员的名称, 所以不可避免的会发生重名问题
const name = '李四'
console.log(name)
// SyntaxError: Identifier 'name' has already been declared

// 重命名: 解构的 成员名 后面取一个新的名称
const { name: userName } = userInfo
console.log(userName)
// => 李四

// 可以直观感受简洁方便
const {log} = console
log('12')
// => 12

3. 字符串

⑴. 字符串的拓展方法
  • includes()
  • startsWith()
  • endsWith()
// 字符串的拓展方法

const errorMsg = 'Error: value is not undefined'

console.log(
  // startsWith  判断字符串是否 Error 开头
 errorMsg.startsWith('Error')
 // => true
)

console.log(
 // endsWith  判断字符串是否 undefined 结尾
 errorMsg.endsWith('undefined')
 // => true
)

console.log(
  // includes  判断字符串是否 包含 not 
  errorMsg.includes('not')
  // => true
)

⑵. 模板字符串
  • 模板字符串(反引号)内字符串支持换行
  • 支持插值方式的嵌入
  • 插值内可以嵌入表达式
// 模板字符串

const str = 'hello world'
console.log(str)
// => hello world

const str2 = `hello world`
console.log(str2)
// => hello world

// 反引号(模板字符串的新特性)
// 1. 支持换行
const str3 = `hello
world`
console.log(str3)
// => hello
// => world

// 2. 通过插值的方式进行嵌入(较于传统的字符串拼接方式便捷很多)
const name = '张三'
const msg = `name: ${name}`
console.log(msg)
// => name: 张三

// 3. 插值中可以嵌入 表达式
const number = 1
const pow = `pow: ${number + 1}`
console.log(pow)
// => pow: 2

⑶. 标签模板字符串

功能: 可以在模板字符串前添加个 标签,这个标签其实就是一个函数

书写方式:

const logStr = console.log`hello world`
// => [ 'hello world' ]
// console.log  标签(函数)
// hello world  标签(函数)的参数

示例:

// 模板字符串可以对返回的数据进行加工
const userName = '张三'
const userSex = 0 ? '男' : '女'
function tagFunc(val, userName, userSex) {
  // console.log(val, userName, userSex)
  return userName + val[1] + userSex
}
const result = tagFunc`${userName}: ${userSex}`
// => [ '', ': ', '' ] 张三 0

console.log(result)
// => 张三: 女

4. 参数

⑴. 参数默认值
// 参数默认值

// 设置默认参数 value => true
function foo(value) {
  // 传统方式 / 短路运算
  // value = value || true
  // 问题: 如果传入 false, 也会被设置默认值 true

  value = value == undefined ? true : value

  console.log(value)
}


foo()
// => true
foo(true)
// => true
foo(false)
// => false

⑵. 剩余参数
// 剩余参数

const arr = [1, 2, 3, 4]

function foo(val) {
  // 传统  通过 arguments 这个伪数组来接收
  console.log(arguments)
  // => [Arguments] { '0': [ 1, 2, 3, 4 ] }

  // ... 能接受从当前位置开始,之后所有的实参
  console.log(...val)
  // => 1 2 3 4
}
foo(arr)

⑶. 展开数组
// 展开数组

const arr = ['tom', 'bob', 'lucy']

// 硬方法
console.log(arr[0], arr[1], arr[2])
// => tom bob lucy

// 传统方法
// 函数对象的 apply 方法:  第一个参数是 this 的指向, 第二个参数是实参数组
console.log.apply(console, arr)
// => tom bob lucy

// ES6
// ... 展开运算符
console.log(...arr)
// => tom bob lucy

5. 箭头函数

⑴. 基础语法
// 箭头函数

// 传统函数
function add(val) {
  return val + 1
}

console.log(add(1))
// => 2


// 箭头函数
// const addNumber = (val) => {
//   return val + 1
// }

// 简化 => 
const addNumber = val => val + 1

console.log(addNumber(1))
// => 2

⑵. this 指向
// this 指向

const person = {
  name: 'tom',
  sayHi: function () {
    console.log(`Hi, my name is ${this.name}`)
    // => Hi, my name is tom
  },

  // 箭头函数不会改变 this 的指向(内部没有 this 的机制)
  sayHello: () => {
    console.log(`Hello, my name is ${this.name}`)
    // => Hello, my name is undefined
  },

  sayAsyncHello: function () {
    setTimeout(function () {
      console.log(`Hello, my name is ${this.name}`)
      // => Hello, my name is undefined
      // 原因: setTimeout 内部的函数最终会放到 全局对象 上被调用, 所以这里的 this 访问的是全局作用域
    }, 0)
  },

  // 解决办法1: 定义一个变量, 储存当前 this 对象
  sayAsyncHello2: function () {
    let _this = this
    setTimeout(function () {
      // 基于闭包的机制在内部使用
      console.log(`Hello, my name is ${_this.name}`)
      // => Hello, my name is tom
    }, 0)
  },
  
  // 解决办法2:  使用箭头函数,箭头函数的 this 指向永远是 包裹箭头函数的第一个普通函数
  sayAsyncHello3: function () {
    setTimeout(() => {
      console.log(`Hello, my name is ${this.name}`)
      // => Hello, my name is tom
    }, 0)
  }
}

person.sayHi()
person.sayHello()
person.sayAsyncHello2()
person.sayAsyncHello3()

6. 对象

⑴. 对象字面量增强

简化写法:

// 对象字面量增强

const name = 'zoe'

const obj = {
  // name: name,

  // 如果变量和属性名是一致, 可以等价 name: name === name
  name,
  age: '18',

  // 给属性添加方法

  //传统
  method1: function () {
    console.log('method1')
  },

  // 简化
  method2() {
    console.log('method2')
    // 内部还是使用了 function, this 的指向是当前对象
    console.log(this)
  }
}

console.log(obj)
// => { name: 'zoe', age: '18' }

obj.method1()
// => method1

obj.method2()
// => method2
// => {
// =>   name: 'zoe',
// =>   age: '18',
// =>   method1: [Function: method1],
// =>   method2: [Function: method2]
// => }

动态添加属性名:

// 对象动态添加属性名

const obj = {
  name: 'zoe',
  age: 18,

  // 字面量的方式(计算属性名)  '[]'内部可以是计算表达式
  [Math.random()]: 456,
  [1 + 1]: 789
}

// 添加动态属性名

// 传统方法
// 只能在对象声明过后, 通过索引器的方式动态添加属性值
obj[Math.random()] = 123

console.log(obj)
// => {
// =>   '2': 789,
// =>   name: 'zoe',
// =>   age: 18,
// =>   '0.9406667184590531': 456,
// =>   '0.04643497457906176': 123
// => }

⑵. Object.assign

将多个源对象中的属性,复制到一个对象当中,如果对象中有相同的属性,那么源对象中的属性就会覆盖掉当前对象的属性值

语法:

const source1 = {
  name: 'zoe',
  age: 18
}

const source2 = {
  name: 'bom',
  sex: 0
}

const target = Object.assign(source1, source2)
console.log(target)
// => { name: 'bom', age: 18, sex: 0 }

应用场景:

const data = {
  name: 'zoe',
  age: 18,
  sex: 0
}


// 直接修改, 会同时修改数据源(对象复制的是指针)
const userInfo = data
userInfo.sex = 1
console.log(data)
// => { name: 'zoe', age: 18, sex: 1 }
console.log(userInfo)
// => { name: 'zoe', age: 18, sex: 1 }


// Object.assign
const userInfo2 = Object.assign({}, data)
userInfo2.sex = 0
console.log(data)
// => { name: 'zoe', age: 18, sex: 1 }
console.log(userInfo2)
// => { name: 'zoe', age: 18, sex: 0 }

⑵. Object.is

判断两个对象是否相等

console.log(
  false == 0,
  // => true
  false === 0,
  // => false
  +0 === -0,
  // => true
  NaN === NaN,
  // => false
  
  // 在特殊情况下, 这种等式并不规范
  Object.is(+0, -0),
  // => false
  Object.is(NaN, NaN)
  // => true
)

7. 代理对象

⑴. Proxy

代理: 就像一个门卫,可以知道外部对内部的修改和读取

const person = {
  name: 'zoe',
  age: 18
}

// Proxy 第一个参数是需要代理的对象, 第二个参数是代理的处理对象
const personProxy = new Proxy(person, {
  // 监听属性的访问
  // 接收两个参数, 代理对象, 外部访问的属性名
  get(target, property) {
    return property in target ? target[property] : 'default'
  },
  // 监听属性的修改
  // 接收三个参数, 代理的目标对象, 写入的目标名称, 写入的属性值
  set(target, property, value) {
    console.log(target, property, value)
    target.property = value
  }
})

console.log(personProxy.name)
// => zoe

console.log(personProxy.nothing)
// => default

personProxy.gender = true
// => { name: 'zoe', age: 18 } gender true

⑵. defineProperty
  • defineProperty 只能监听到对象的读取和写入
  • Proxy 是以非入侵的方式监听了对象的读写, 而defineProperty 需要单独对需要监听的属性做额外操作
  • Proxy 能够监听到更多对象的操作
    • delete 方法
    • 对 对象中方法调用
const person = {
  name: 'zoe',
  age: 18
}

const personProxy = {
  // 接收两个参数, 代理对象, 外部访问(删除)的属性名
  defineProperty(target, property) {
    delete target[property]
  }
}

delete person.age
console.log(person)
// => { name: 'zoe' }

在这里插入图片描述

⑶. 数组对象的监视

传统对数组对象监视的方法是 重写数组 , 通过自定义的方法覆盖掉,数组对象原型上的pushshift … 方法,以此来劫持调用这些方法的过程

const list = []

const listProxy = new Proxy(list, {
  // 监听数组的修改
  // 接收三个参数, 代理的目标对象, 写入的目标名称, 写入的属性值
  set(target, property, value) {
    console.log('set', property, value)
    target[property] = value
    return true // 表示操作成功
  }
})


listProxy.push(100)
// => set 0 100    (监听到 push 的值所处的下标, 并返回)
// => set length 1

⑷. Reflect
  • 是一个静态类,不能够通过 new Reflect 的方式构建实例对象,只能够调用静态类中的静态方法
  • 提供了统一的对象操作 API

Proxy 的内部 get 方法:

const person = {
  name: 'zoe',
  age: 18
}

const personProxy = new Proxy(person, {
  // 如果没有在 Proxy 内部定义 get 方法, 就等同于内部定义了 get 方法,并将参数交给 Reflect 的 get 方法
  get(target, property) {
    console.log('watch name')
    return Reflect.get(target, property)
  }
})

console.log(personProxy.name)
// => watch name
// => zoe

统一的对象操作 API:

const person = {
  name: 'zoe',
  age: 18
}

// 判断对象 person 内部是否有 name 属性名
console.log('name' in person)
// 删除对象内部 age 的属性名
console.log(delete person['age'])
// 获取对象中的属性名
console.log(Object.keys(person))


// 提供了统一的对象操作 API
console.log(Reflect.has(person, 'name'))
console.log(Reflect.deleteProperty(person, 'age'))
console.log(Reflect.ownKeys(person))

8. 类

⑴. Class

之前都是通过定义函数、函数的原型对象来实现的类型

// 定义一个名为 person 的函数,作为这个函数的构造函数
function Person (name) {
  // this 访问当前的实例对象
  this.name = name
}

// this 实例中需要共享成员: 通过函数对象的 prototype/原型 来实现
Person.prototype.say = function() {
  console.log(`hi, my name is ${this.name}`)
}

const p = new Person('zoe')
p.say()
// => hi, my name is zoe



// 通过 class 实现
class Person2{
  // 通过 constructor 创建一个构造方法作为函数
  constructor(name) {
    this.name = name
  }

  say() {
    console.log(`hi, my name is ${this.name}`)
  }
}

// 创建类型的构造实例
const p2 = new Person2('zoe')
p2.say()
// => hi, my name is zoe

⑵. static

类的方法: 实例方法、静态方法

  • 以前实现静态方法,就是在构造函数上挂载方法来实现,因为在 JS 中函数也是对象,他也可以添加一些方法成员
  • ES6 中新增了添加静态成员的 static 关键词
class Person{
  constructor(name) {
    this.name = name
  }

  // 创建一个名为 creat 的静态方法, 用于创建 Person 的实例
  static  creat(name) {
    return new Person(name)
  }
} 

const zoe = Person.creat('zoe')
console.log(zoe)
// => Person { name: 'zoe' }


// tip: 静态方法是挂载到类型上的,所以静态方法内部的 this 指向的是当前的类型

⑶. extends 类的继承
  • ES6 之前大多使用原型的方式实现继承
  • ES6 中新增了extends 关键词
class Person{
  constructor(name) {
    this.name = name
  }

  say() {
    console.log(`hi, my name is ${this.name}`)
  }
} 

// 继承的 Students 就会拥有 Person 里面所有的成员了
class Student extends Person {
  // 定义一个构造函数, 接收两个参数
  constructor(name, number) {
    // super对象 永远指向父类, 调用它就调用了 父类的构造函数
    super(name)
    // 定义这个类型特有的成员
    this.number = number
  }

  // 定义一个方法
  hello() {
    super.say()
    console.log(`hello, my school number is ${this.number}`)
  }
}

const h = new Student('bob', '123')
h.hello()
// => hi, my name is bob
// => hello, my school number is 123

9. 数据结构

⑴. Set

基础用法:

const s = new Set()

// add 方法可以添加数据
s.add(1)

// add 方法会返回集合对象本身, 所以可以链式调用
s.add(1)
  .add(2)
  .add(3)
  .add(2)

console.log(s)
// => Set(3) { 1, 2, 3 }
// set 内部不允许出现重复的值, 所以重复添加的值会被忽略


// 遍历集合中的数据
// forEach
s.forEach(i => console.log(i))
// => 1 2 3

// for...of
for (let i of s) console.log(i)
// => 1 2 3


// 获取对象的长度
console.log(s.size)
// => 3


// Set 的其他方法
// has 是否包含 
console.log(s.has(2))

// delete 删除
console.log(s.delete(1))
// => true

// clear 清除
s.clear()
console.log(s)
// => Set(0) {}

去重:

const arr = [1, 2, 3, 4, 2, 3, 5]
const result = new Set(arr)
console.log(result)
// => Set(5) { 1, 2, 3, 4, 5 }


// 对象转数组
// 方法 1: Array.from
const result2 = Array.from(new Set(arr))
console.log(result2)
// => [ 1, 2, 3, 4, 5 ]

// 方法 2: ... 展开运算符
const result3 =[...new Set(arr)]
console.log(result3)
// => [ 1, 2, 3, 4, 5 ]

⑵. Map
const obj= {}

obj[true] = 'value1'
obj[123] = 'value2'
obj[{name: 'zoe'}] = 'value3'

console.log(Object.keys(obj))
// => [ '123', 'true', '[object Object]' ]
// 添加的 键 都变成了字符串


// map 键值对集合,用来映射键值对之间的关系
// 和对象的区别,可以以任意类型的数据作为键
const m = new Map()
const zoe = {name: 'zoe'}
m.set(zoe, 90)
console.log(m)
// => Map(1) { { name: 'zoe' } => 90 }

console.log(m.get(zoe))
// => 90

m.forEach((value, keys) => {
  console.log(value, keys)
})
// => 90 { name: 'zoe' }

// 同 set
m.has()
m.delete()
m.clear()


10. Symbol

  • 一种全新的原始数据类型
  • 独一无二
  • 主要作用:为对象添加独一无二的属性值(属性标识符)

⑴. 出现的意义
// shared.js  全局共享
// 定义一个用于存放数据缓存的对象
const cache = {}

// a.js
// 在缓存中 放入一个键为 foo,值为随机数
cache['foo'] = Math.random()
 
// b.js
// 不知道其他文件夹已经存放了 foo 键的情况下, 再次存放名为 foo 的数据
cache['foo'] = '123'

console.log(cache)
// => { foo: '123' }


// 传统解决方案 - 约定键名

// c.js
cache['c_foo'] = '456'
console.log(cache)
// => { foo: '123', c_foo: '456' }

⑵. 功能
// 可以传入一个字符串作为这个类型描述
console.log(Symbol('foo'))
// => Symbol(foo)
console.log(Symbol('bar'))
// => Symbol(bar)


// 1. 解决 键名冲突问题
const obj = {}
// 再添加键时,就不需要考虑键名冲突的问题
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)
// => { [Symbol()]: '123', [Symbol()]: '456' }


// 2. 创建私有成员

// a.js
const name = Symbol()
const userInfo = { 
  [name]: 'zoe',
  say() {
    console.log(`hi, my name is ${this[name]}`)
  }
}
userInfo.say()
// => hi, my name is zoe

// b.js
// 因为 Symbol 为独一无二, 所以外部无法访问 a.js 内部的私有成员

// 只能调用普通成员
userInfo.say()
// => hi, my name is zoe

⑶. 补充
console.log( Symbol() === Symbol() )
// => false
console.log( Symbol('foo') === Symbol('foo') )
// => false


// Symbol for 方法可以实现相同的描述会有相同的 Symbol 类型(内部维护了一个全局的注册表,为描述和 Symbol 值提供了一一对应的关系)
// 传入的值都会转换为字符串
console.log(
  Symbol.for(true) === Symbol.for('true')
)
// => true


// Symbol 存在内置的常量

const obj = {
  [Symbol.toStringTag]: 'XYZ'
}
console.log(obj.toString())
// => [object XYZ]
// 自定义的 toString 'XYZ'


const obj2 = {
  // 设置特定属性值
  [Symbol()]: 'symbol value',
  foo: 'normal value'
}

// 通过传统 for in 是无法获取到特定属性值的
for(var keys in obj2) {
  console.log(keys)
}
// => foo

// Object.keys 方法同样获取不到
console.log(Object.keys(obj2))
// => [ 'foo' ]


// Object.getOwnPropertySymbols 和 Object.keys 方法类似, 只是专门用来获取 Symbol 类型的属性名
console.log(
  Object.getOwnPropertySymbols(obj2)
)
// => [ Symbol() ]

11. 遍历

  • for 遍历数组
  • for…in 遍历 jianzhidui
  • 对象的遍历方法:forEach…
  • !!! for…of 将作为遍历所有数据结构的统一方式

⑴. 基础用法
const arr = [1, 2, 3]

// 1. for of 方法拿到的是数组中的每一个元素
for (let i of arr) {
  console.log(i)
}
// => 1 2 3 


// 2. for of 可以使用 break 随时终止循环
// 传统终止遍历的方法 forEach + some/every
for (let i of arr) {
  if(i > 2) break
  console.log(i)
}
// => 1 2


// 3. Set 对象遍历
const s = new Set(['foo', 'bar'])
for (let i of s) {
  console.log(i)
}
// => foo bar


// 4. Map 对象遍历
const m = new Map()
m.set('name', 'zoe')
m.set('age', 18)
for (let i of m) {
  console.log(i)
}
// => [ 'name', 'zoe' ] [ 'age', 18 ]

// Map 可以直接循环键值
for(let [key, value] of m) {
  console.log(key, value)
}
// => name zoe   age 18


// 5.  遍历普通对象
const obj = {
  name: 'zoe',
  age: 18
}

for (let i of obj) {
  console.log(i)
}
// => TypeError: obj is not iterable

⑵. iterable - 可迭代接口

接口: 就是实现了相同的规格标准

打开 VScode 的开发者工具:
在这里插入图片描述

因为数组、Set、map 能够遍历,寻找三者相同的规范标准
在这里插入图片描述

创建一个数组,直接调用这个方法
在这里插入图片描述

得到的是一个数组的迭代器对象,并且继续调用 next 方法,会得到一个猜想:

next 方法内部应该维护了一个指针,每调用一次 next,指针就会向后移动一位,而 done 的作用是记录内部的数据是否全部被遍历

手动调用来证实猜想:

const s = new Set(['foo', 'bar', 'bsz', 'foo'])

const iterable = s[Symbol.iterator]()

console.log(iterable.next())
// => { value: 'foo', done: false }
console.log(iterable.next())
// => { value: 'bar', done: false }
console.log(iterable.next())
// =>  value: 'bsz', done: false }
console.log(iterable.next())
// => { value: undefined, done: true }
console.log(iterable.next())
// => { value: undefined, done: true }

// 其实这也是 for...of 内部的工作原理

⑶. 实现可迭代接口
const obj = {
  // 因为 Symbol.iterable 是一个 Symbol 常量, 所以需要通过计算属性名的方式提供到自定义属性当中
  [Symbol.iterator]: function () {
    return {
      // 需要提供一个 next 方法, 实现向后迭代
      next: function () {
        // 需要返回一个结果对象, 对象内有两个参数, 
        return {
          value: 'zoe',
          done: false
        }
      }
    }
  }
}

// 调试
const obj2 = {
  store: ['foo', 'bar', 'baz'],
  [Symbol.iterator]: function () {
    let index = 0
    return {
      next: () => {
        const result = {
          value: this.store[index],
          done: index >= this.store.length
        }
        index++
        return result
      }
    }
  }
}

for (let i of obj2) {
  console.log(i)
}
// => foo bar baz

⑷. 迭代器模式

对外部提供统一遍历的解构,外部不用关注内部的数据结构

// 协同实现一个任务清单

// A:
const todo = {
  life: ['吃饭', '睡觉', '打游戏'],
  work: ['前端', '后端', '数据库'],
  sport: ['篮球']
}

// B:
for (let i of todo.life) {
  console.log(i)
}
for (let i of todo.work) {
  console.log(i)
}
for (let i of todo.sport) {
  console.log(i)
}

// B 调用 A 的数据, 需要根据 A 的数据结构不断调整

// C:
const todo2 = {
  work: ['前端', '后端', '数据库'],
  sport: ['篮球', '跑步'],
  family: ['旺财'],

  // 定义一个名为 each 的方法, 接收外部的回调函数作为参数
  each: (callback) => {
    const all = [].concat(todo2.work, todo2.sport, todo2.family)
    for (let i of all) {
      // 提供了统一遍历的接口
      callback(i)
    }
  },

  // iterable 方法
  [Symbol.iterator]: () => {
    const all = [...todo2.work, ...todo2.sport, ...todo2.family]
    let index = 0
    return {
      next: () => {
        return {
          value: all[index],
          done: index++ >= all.length
        }
      }
    }
  }
}

console.log('----------------------')

// D: 
// 只需要调用 C 提供的接口, 就可以获取到遍历的数据, 并且不需要关系其数据结构
todo2.each(item => {
  console.log(item)
})
// => 前端 后端 数据库 篮球 跑步 旺财

console.log('----------------------')

for(let i of todo2) {
  console.log(i)
}
// => 前端 后端 数据库 篮球 跑步 旺财

12. Promise

  • 一种全新的异步编程解决方案
  • 链式调用的方式解决了,传统异步编程过程中回调函数嵌套过深的问题
  • 因为涉及的内容过多,这里不多概述,相信大家对于 Promise 已经具备了很深的李姐(理解)

13. Async await

  • 解决异步函数回调嵌套过深的问题
  • 本质上还是运用 Promise 的语法糖

14. Generator 生成器

⑴. 概念

作用: 避免异步编程中回调嵌套过深,提供更好的异步编程解决方案

基础语法:

// 定义一个生成器函数
function * foo(){
console.log('foo')
return 100
}

// 生成器函数是静态方法,不能被直接调用
const result = foo()
console.log(result)
// => Object [Generator] {}

console.log(result.next())
// => foo
// => { value: 100, done: true }

基本用法:

  • 规律: 生成器函数会返回一个生成器对象,调用该对象的 next 方法,才会让函数的函数体开始执行,执行过程中一旦遇到 yield 关键词,函数的执行就会被暂停下来,并且 yield 的值将作为 next 的结果返回;继续调用对象 next 方法,函数就会继续向后执行
  • 特点: 惰性执行
function * bar () {
  console.log('111')
  // yield 同 return 可以返回值, 但是不会立即结束
  yield 100

  console.log('222')
  yield 200
}

const generator = bar()
console.log(generator.next())
// => 111
// => { value: 100, done: false }
console.log(generator.next())
// => 222
// => { value: 200, done: false }
console.log(generator.next())
// => 111
// => { value: undefined, done: true }

⑵. 生成器应用
// 发号器

// 创建一个生成器函数
function * creatIdMaker() {
  let id = 0
  // 定义一个死循环,不断 + 1
  while(true) {
    // yield 执行时, 就会暂停, 知道下一次调用 yield
    yield id++
  }
}

// 外部创建一个生成器对象, 实现 '拿号'
const idMaker = creatIdMaker()

// 每次调用 next 方法, 就能获取一个 自增的 id
console.log(idMaker.next().value)
// => 0
console.log(idMaker.next().value)
// => 1
console.log(idMaker.next().value)
// => 2
console.log(idMaker.next().value)
// => 3

15. ES Modules

语言层面的模块化标准

16. ES 2016

  • 数组实例对象 Array.prototype.includes
  • 指数运算符 **

includes 方法

// Array.prototype.includes
const arr = ['foo', 'bar', NaN, 'baz']

console.log(arr.indexOf('bar'))
// => 1  (返回元素对应的下标)
console.log(arr.indexOf('xxx'))
// => -1  (未找到)
console.log(arr.indexOf(NaN))
// => -1

// includes 方法可以直接判断
console.log(arr.includes(NaN))
// => true

指数运算符**

// 传统方法 Math 方法
console.log(Math.pow(4, 2))
// => 16


// ES6 新增
console.log(4 ** 2)
// => 16

17. ES 2017

  • Object.value 值组成的数组
  • Object.entries 以数组的形式返回所有的键值对
  • Object.getOwnPropertyDescriptors 复制对象
  • String.prototype.padStart / String.prototype.padEnd 字符串填充
  • 允许在函数参数中添加尾逗号
const obj = {
  name: 'zoe',
  age: 18
}

// Object.value  值组成的数组
console.log(Object.values(obj))
// => [ 'zoe', 18 ]   (keys 方法返回的是键组成的数组)


// Object.entries  以数组的形式返回所有的键值对
console.log(Object.entries(obj))
// => [ [ 'name', 'zoe' ], [ 'age', 18 ] ]

for (let [key, value] of Object.entries(obj)) {
  console.log(key, value)
}
// => name zoe   age 18


// Object.getOwnPropertyDescriptors  复制对象
const p1 = {
  firstName: 'li',
  lastName: 'hua',
  get fullName() {
    return this.firstName + '' + this.lastName
  }
}

const p2 = Object.assign({}, p1)
p2.firstName = 'Wang'
console.log(p2.fullName)
// => li hua  (Object.assign 在复制时 只是把 p1.fullName 当做普通的属性复制了)

const descriptors = Object.getOwnPropertyDescriptors(p1)
const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'Wang'
console.log(p3.fullName)
// => Wang hua


// String.prototype.padStart / String.prototype.padEnd  字符串填充
const objNumber = {
  html: 18,
  css: 9,
  javaString: 666
}

for (let [key, value] of Object.entries(objNumber)) {
  console.log(key, value)
}
// => html 18
// => css 9
// => javaString 666

for (let [key, value] of Object.entries(objNumber)) {
  // padEnd / padStart 有两个参数, 给定的长度, 用来填充的符号
  console.log(`${key.padEnd(16, '-')}`, `${value.toString().padStart(3, '0')}`)
}
// => html------------ 018
// => css------------- 009
// => javaString------ 666


// 在函数参数中添加尾逗号
const userInfo = {
  name: 'zoe',
  age: 18,
  sex: 0, // 这个就是尾逗号
}
// 1. 在 alt + ↑ / ↓ 时, 不会出现格式问题
// 2. 在尾部添加数据时, 不需要再上一个属性尾部添加 ','


下一篇:TypeScript 详细解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后海 0_o

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值